Modifed property tax return
This commit is contained in:
parent
0335d577c7
commit
d268d1b7bf
|
|
@ -0,0 +1,641 @@
|
||||||
|
# Техническое задание: Автоматизация заполнения Декларации по налогу на имущество
|
||||||
|
|
||||||
|
## 1. Общее описание
|
||||||
|
|
||||||
|
**Цель:** Автоматизировать заполнение дочерних таблиц Əlavə 1 и Əlavə 2 в декларации по налогу на имущество (Property Tax Return) на основе данных об активах (Assets) с использованием логики отчета "Asset Depreciations and Balances" из ERPNext.
|
||||||
|
|
||||||
|
**Дата создания:** 2025-12-11
|
||||||
|
|
||||||
|
**Версия:** 1.0
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Бизнес-требования
|
||||||
|
|
||||||
|
### 2.1 Функциональные требования
|
||||||
|
|
||||||
|
1. **Добавление полей выбора периода:**
|
||||||
|
- Добавить два взаимоисключающих чекбокса в форму Property Tax Return:
|
||||||
|
- `Tam İl` (Полный год) - по умолчанию включен
|
||||||
|
- `İl Ərzində` (В течение года)
|
||||||
|
- Чекбоксы должны располагаться сразу после поля `İl` (Год) на главной странице декларации
|
||||||
|
|
||||||
|
2. **Автоматическое заполнение данных:**
|
||||||
|
- При изменении года (`İl`) или чекбоксов система должна автоматически:
|
||||||
|
- Получить данные об активах из базы данных
|
||||||
|
- Рассчитать балансовые стоимости активов
|
||||||
|
- Агрегировать данные по типам активов и налоговым статьям
|
||||||
|
- Заполнить соответствующие строки в дочерних таблицах
|
||||||
|
|
||||||
|
3. **Источник данных:**
|
||||||
|
- Все расчеты должны базироваться на отчете ERPNext "Asset Depreciations and Balances"
|
||||||
|
- Использовать данные из doctype `Asset` компании пользователя
|
||||||
|
|
||||||
|
### 2.2 Пользовательский сценарий
|
||||||
|
|
||||||
|
1. Пользователь открывает/создает документ Property Tax Return
|
||||||
|
2. Заполняет поле `İl` (год декларации, например: 2024)
|
||||||
|
3. Выбирает режим расчета:
|
||||||
|
- `Tam İl` - если компания работала полный год
|
||||||
|
- `İl Ərzində` - если компания была создана в течение года
|
||||||
|
4. Система автоматически:
|
||||||
|
- Преобразует год в диапазон дат (01.01.YYYY - 31.12.YYYY)
|
||||||
|
- Получает данные об активах
|
||||||
|
- Заполняет таблицы Əlavə 1 и Əlavə 2
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Технические требования
|
||||||
|
|
||||||
|
### 3.1 Изменения в схеме данных
|
||||||
|
|
||||||
|
#### 3.1.1 Property Tax Return DocType JSON
|
||||||
|
|
||||||
|
**Файл:** `/home/frappe/frappe-bench/apps/taxes_az/taxes_az/taxes_az/doctype/property_tax_return/property_tax_return.json`
|
||||||
|
|
||||||
|
**Новые поля:**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"fieldname": "tam_il",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Tam İl",
|
||||||
|
"default": "1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "il_erzinde",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "İl Ərzində",
|
||||||
|
"default": "0"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Позиция:** После поля `il` в массиве `field_order`
|
||||||
|
|
||||||
|
### 3.2 Логика расчетов
|
||||||
|
|
||||||
|
#### 3.2.1 Преобразование даты
|
||||||
|
|
||||||
|
- **Вход:** Год (Int, например: 2024)
|
||||||
|
- **Выход:**
|
||||||
|
- `from_date` = "2024-01-01"
|
||||||
|
- `to_date` = "2024-12-31"
|
||||||
|
|
||||||
|
#### 3.2.2 Режимы расчета
|
||||||
|
|
||||||
|
##### Режим "Tam İl" (Полный год)
|
||||||
|
|
||||||
|
**Для Əlavə 2 (Taxable Assets):**
|
||||||
|
- Поле `hesabatilininəvvəlinəəsasvəsaitlərinqalıqdəyəri` (501.1) ← Остаточная стоимость на начало года
|
||||||
|
- Поле `hesabatilinsonunaəsasvəsaitlərinqalıqdəyəri` (504.1) ← Остаточная стоимость на конец года
|
||||||
|
|
||||||
|
**Для Əlavə 1 (Tax Exempt Assets):**
|
||||||
|
- Поле `hesabatilininəvvəlinəəsasvəsaitlərinqalıqdəyərimanatla5012` (501.2) ← Остаточная стоимость на начало года
|
||||||
|
- Поле `ilinsonunaəsasvəsaitlərinqalıqdəyəri` (504.2) ← Остаточная стоимость на конец года
|
||||||
|
|
||||||
|
##### Режим "İl Ərzində" (В течение года)
|
||||||
|
|
||||||
|
**Для Əlavə 2 (Taxable Assets):**
|
||||||
|
- Поле `hesabatilierzindeuçotaalınan` (503.1) ← Стоимость новых приобретений
|
||||||
|
- Поле `hesabatilinsonunaəsasvəsaitlərinqalıqdəyəri` (504.1) ← Остаточная стоимость на конец года
|
||||||
|
|
||||||
|
**Для Əlavə 1 (Tax Exempt Assets):**
|
||||||
|
- Поле `hesabatiliərzindəəsasvəsaitlərinqalıqdəyəri` (503.2) ← Стоимость новых приобретений
|
||||||
|
- Поле `ilinsonunaəsasvəsaitlərinqalıqdəyəri` (504.2) ← Остаточная стоимость на конец года
|
||||||
|
|
||||||
|
### 3.3 Правила агрегации данных
|
||||||
|
|
||||||
|
#### 3.3.1 Для Əlavə 2 (Налогооблагаемые активы)
|
||||||
|
|
||||||
|
**Источник данных:**
|
||||||
|
- Активы с заполненным полем `taxable_asset_type`
|
||||||
|
|
||||||
|
**Правило сопоставления:**
|
||||||
|
- Значение `taxable_asset_type` из актива = Значение "Vergiyə cəlb olunan əmlakların kateqoriyası" в строке таблицы Əlavə 2
|
||||||
|
|
||||||
|
**Возможные значения `taxable_asset_type`:**
|
||||||
|
1. Çoxmərtəbəli (çoxmənzilli) yaşayış binaları
|
||||||
|
2. Qeyri-yaşayış binaları (sahələri)
|
||||||
|
3. Əmlak kompleksi kimi müəssisələr
|
||||||
|
4. Qurğular
|
||||||
|
5. Mənzillər
|
||||||
|
6. Fərdi yaşayış və bağ evləri
|
||||||
|
7. Maşınlar və avadanlıqlar
|
||||||
|
8. Yüksək texnologiyalar məhsulu olan hesablama texnikası
|
||||||
|
9. Nəqliyyat vasitələri
|
||||||
|
10. Digər əsas vəsaitlər
|
||||||
|
|
||||||
|
**Логика:**
|
||||||
|
- Получить все активы компании с `taxable_asset_type IS NOT NULL`
|
||||||
|
- Для каждого актива рассчитать балансовые стоимости
|
||||||
|
- Агрегировать (суммировать) по `taxable_asset_type`
|
||||||
|
- Найти соответствующую строку в Əlavə 2 по совпадению `taxable_asset_type`
|
||||||
|
- Обновить значения полей в найденной строке
|
||||||
|
|
||||||
|
#### 3.3.2 Для Əlavə 1 (Налогово-освобожденные активы)
|
||||||
|
|
||||||
|
**Источник данных:**
|
||||||
|
- Активы с заполненным полем `industrial_tax_article` ИЛИ `tax_exempt_tax_article`
|
||||||
|
- ИСКЛЮЧИТЬ активы с `agricultural_tax_article` (налог на сельскохозяйственные земли)
|
||||||
|
|
||||||
|
**Правило сопоставления:**
|
||||||
|
1. Извлечь номер статьи из названия Tax Article (например: "199.1", "207.3", "227.1")
|
||||||
|
2. Найти строку в Əlavə 1, где поле "Azadolma səbəbi" содержит этот номер
|
||||||
|
|
||||||
|
**Примеры извлечения номера статьи:**
|
||||||
|
- "Vergi Məcəlləsinin 199.1-ci maddəsinə əsasən" → "199.1"
|
||||||
|
- "Vergi Məcəlləsinin 207.3-cü maddəsinə əsasən" → "207.3"
|
||||||
|
- "Vergi Məcəlləsinin 199.4.1-ci maddəsinə əsasən" → "199.4.1"
|
||||||
|
|
||||||
|
**Логика:**
|
||||||
|
- Получить все активы с `industrial_tax_article` или `tax_exempt_tax_article`
|
||||||
|
- Для каждого актива:
|
||||||
|
- Извлечь номер статьи regex: `(\d+(?:\.\d+)*(?:-\d+)?)`
|
||||||
|
- Рассчитать балансовые стоимости
|
||||||
|
- Агрегировать по номеру статьи
|
||||||
|
- Найти соответствующую строку в Əlavə 1
|
||||||
|
- Обновить значения полей
|
||||||
|
|
||||||
|
### 3.4 Расчет балансовых стоимостей
|
||||||
|
|
||||||
|
Использовать логику стандартного отчета ERPNext "Asset Depreciations and Balances":
|
||||||
|
|
||||||
|
**Формулы:**
|
||||||
|
|
||||||
|
1. **Остаточная стоимость на начало периода (Opening Balance):**
|
||||||
|
```
|
||||||
|
Чистая стоимость активов, приобретенных до from_date
|
||||||
|
= Валовая стоимость - Накопленная амортизация на from_date
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Стоимость новых приобретений (New Purchases):**
|
||||||
|
```
|
||||||
|
Валовая стоимость активов, приобретенных в период [from_date, to_date]
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Остаточная стоимость на конец периода (Closing Balance):**
|
||||||
|
```
|
||||||
|
Opening Balance
|
||||||
|
+ New Purchases
|
||||||
|
- Sold Assets
|
||||||
|
- Scrapped Assets
|
||||||
|
- Capitalized Assets
|
||||||
|
+ Adjustments during period
|
||||||
|
- Accumulated Depreciation as on to_date
|
||||||
|
```
|
||||||
|
|
||||||
|
**Источники данных для расчетов:**
|
||||||
|
- `tabAsset` - основные данные об активах
|
||||||
|
- `tabGL Entry` - записи амортизации и корректировок стоимости
|
||||||
|
- `tabAsset Category Account` - счета амортизации
|
||||||
|
- `tabAsset Capitalization` - капитализированные активы (если доступно в версии ERPNext)
|
||||||
|
|
||||||
|
### 3.5 Важные ограничения
|
||||||
|
|
||||||
|
1. **НЕ создавать новые строки** в дочерних таблицах
|
||||||
|
- Строки в Əlavə 1 и Əlavə 2 должны быть предварительно созданы клиентскими скриптами
|
||||||
|
- Система только ОБНОВЛЯЕТ значения в существующих строках
|
||||||
|
|
||||||
|
2. **Сохранять строки без совпадений**
|
||||||
|
- Если для строки не найдено данных об активах, не очищать ее значения
|
||||||
|
- Оставлять строку как есть
|
||||||
|
|
||||||
|
3. **Обработка дубликатов**
|
||||||
|
- Если актив имеет оба поля (`tax_exempt_tax_article` И `taxable_asset_type`), включить его в обе таблицы
|
||||||
|
- Приоритет при наличии нескольких tax article полей: `industrial_tax_article` > `tax_exempt_tax_article`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Архитектура решения
|
||||||
|
|
||||||
|
### 4.1 Серверная часть (Python)
|
||||||
|
|
||||||
|
**Файл:** `property_tax_return.py`
|
||||||
|
|
||||||
|
#### 4.1.1 Основной метод API
|
||||||
|
|
||||||
|
```python
|
||||||
|
@frappe.whitelist()
|
||||||
|
def populate_property_tax_tables(company, year, from_date, to_date,
|
||||||
|
calculation_mode, existing_elave_1, existing_elave_2)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Параметры:**
|
||||||
|
- `company` - Название компании
|
||||||
|
- `year` - Год декларации
|
||||||
|
- `from_date` - Дата начала периода (YYYY-MM-DD)
|
||||||
|
- `to_date` - Дата окончания периода (YYYY-MM-DD)
|
||||||
|
- `calculation_mode` - Режим расчета: 'tam_il' или 'il_erzinde'
|
||||||
|
- `existing_elave_1` - JSON массив существующих строк Əlavə 1
|
||||||
|
- `existing_elave_2` - JSON массив существующих строк Əlavə 2
|
||||||
|
|
||||||
|
**Возвращает:**
|
||||||
|
```python
|
||||||
|
{
|
||||||
|
'success': True/False,
|
||||||
|
'elave_1_data': {
|
||||||
|
'matching_key': {
|
||||||
|
'field_name': value,
|
||||||
|
...
|
||||||
|
},
|
||||||
|
...
|
||||||
|
},
|
||||||
|
'elave_2_data': {
|
||||||
|
'matching_key': {
|
||||||
|
'field_name': value,
|
||||||
|
...
|
||||||
|
},
|
||||||
|
...
|
||||||
|
},
|
||||||
|
'error': 'Error message' (если success=False)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 4.1.2 Вспомогательные функции
|
||||||
|
|
||||||
|
1. **get_taxable_assets_aggregated(company, from_date, to_date)**
|
||||||
|
- Получает налогооблагаемые активы
|
||||||
|
- Агрегирует по `taxable_asset_type`
|
||||||
|
|
||||||
|
2. **get_tax_exempt_assets_aggregated(company, from_date, to_date)**
|
||||||
|
- Получает налогово-освобожденные активы
|
||||||
|
- Агрегирует по номеру статьи
|
||||||
|
|
||||||
|
3. **get_asset_balances_using_standard_logic(company, from_date, to_date, asset_names)**
|
||||||
|
- Рассчитывает балансовые стоимости активов
|
||||||
|
- Использует логику стандартного отчета ERPNext
|
||||||
|
|
||||||
|
4. **extract_article_number(tax_article_name)**
|
||||||
|
- Извлекает номер статьи из названия Tax Article
|
||||||
|
- Использует regex: `(\d+(?:\.\d+)*(?:-\d+)?)`
|
||||||
|
|
||||||
|
5. **find_matching_azadolma_sebeb(article_number, existing_rows)**
|
||||||
|
- Находит строку в Əlavə 1 по номеру статьи
|
||||||
|
|
||||||
|
6. **check_erpnext_version()**
|
||||||
|
- Проверяет версию ERPNext для совместимости полей
|
||||||
|
|
||||||
|
7. **get_asset_cost_details(...)** - Получает данные о стоимости активов
|
||||||
|
|
||||||
|
8. **get_asset_depreciation_details(...)** - Получает данные об амортизации
|
||||||
|
|
||||||
|
9. **get_asset_value_adjustments(...)** - Получает корректировки стоимости
|
||||||
|
|
||||||
|
### 4.2 Клиентская часть (JavaScript)
|
||||||
|
|
||||||
|
**Файл:** `property_tax_return.js`
|
||||||
|
|
||||||
|
#### 4.2.1 Обработчики событий формы
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
frappe.ui.form.on('Property tax return', {
|
||||||
|
refresh: function(frm) {
|
||||||
|
// Инициализация взаимоисключающих чекбоксов
|
||||||
|
setup_checkbox_exclusivity(frm);
|
||||||
|
},
|
||||||
|
|
||||||
|
il: function(frm) {
|
||||||
|
// Автоматическая перезагрузка при изменении года
|
||||||
|
reload_property_tax_data(frm);
|
||||||
|
},
|
||||||
|
|
||||||
|
tam_il: function(frm) {
|
||||||
|
// Обработка изменения чекбокса "Полный год"
|
||||||
|
// Снимает il_erzinde, если tam_il включен
|
||||||
|
// Перезагружает данные
|
||||||
|
},
|
||||||
|
|
||||||
|
il_erzinde: function(frm) {
|
||||||
|
// Обработка изменения чекбокса "В течение года"
|
||||||
|
// Снимает tam_il, если il_erzinde включен
|
||||||
|
// Перезагружает данные
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 4.2.2 Вспомогательные функции
|
||||||
|
|
||||||
|
1. **setup_checkbox_exclusivity(frm)**
|
||||||
|
- Гарантирует, что только один чекбокс выбран
|
||||||
|
- Если оба сняты, устанавливает tam_il=1
|
||||||
|
|
||||||
|
2. **reload_property_tax_data(frm)**
|
||||||
|
- Валидирует входные данные
|
||||||
|
- Показывает индикатор загрузки
|
||||||
|
- Вызывает серверный метод
|
||||||
|
- Обновляет дочерние таблицы
|
||||||
|
- Показывает сообщения об успехе/ошибке
|
||||||
|
|
||||||
|
3. **update_child_table_values(frm, table_name, data_map)**
|
||||||
|
- Обновляет значения в строках дочерних таблиц
|
||||||
|
- Использует квадратные скобки для полей с юникодными символами
|
||||||
|
- Не создает новые строки
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Обработка ошибок
|
||||||
|
|
||||||
|
### 5.1 Валидация на клиенте
|
||||||
|
|
||||||
|
1. **Год не указан:**
|
||||||
|
```
|
||||||
|
Сообщение: "Please select a year (İl) first"
|
||||||
|
Действие: Прерывание выполнения
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Ни один чекбокс не выбран:**
|
||||||
|
```
|
||||||
|
Сообщение: "Please select either Tam İl or İl Ərzində"
|
||||||
|
Действие: Прерывание выполнения
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.2 Обработка на сервере
|
||||||
|
|
||||||
|
1. **Ошибка получения данных:**
|
||||||
|
- Логирование в Error Log: `frappe.log_error()`
|
||||||
|
- Возврат `{'success': False, 'error': error_message}`
|
||||||
|
|
||||||
|
2. **Нет активов:**
|
||||||
|
- Возврат пустых словарей для elave_1_data и elave_2_data
|
||||||
|
- `{'success': True, 'elave_1_data': {}, 'elave_2_data': {}}`
|
||||||
|
|
||||||
|
3. **Отсутствующие custom fields:**
|
||||||
|
- Логирование предупреждения
|
||||||
|
- Продолжение работы без ошибки
|
||||||
|
|
||||||
|
### 5.3 Визуальная обратная связь
|
||||||
|
|
||||||
|
1. **Во время загрузки:**
|
||||||
|
```javascript
|
||||||
|
frappe.dom.freeze(__('Loading property tax data...'));
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **При успехе:**
|
||||||
|
```javascript
|
||||||
|
frappe.show_alert({
|
||||||
|
message: __('Property tax data loaded successfully'),
|
||||||
|
indicator: 'green'
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **При ошибке:**
|
||||||
|
```javascript
|
||||||
|
frappe.msgprint({
|
||||||
|
title: __('Error'),
|
||||||
|
message: error_message,
|
||||||
|
indicator: 'red'
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Тестирование
|
||||||
|
|
||||||
|
### 6.1 Модульное тестирование (Python)
|
||||||
|
|
||||||
|
**Тестовые случаи:**
|
||||||
|
|
||||||
|
1. **extract_article_number()**
|
||||||
|
- Входные данные: "Vergi Məcəlləsinin 199.1-ci maddəsinə əsasən"
|
||||||
|
- Ожидаемый результат: "199.1"
|
||||||
|
|
||||||
|
2. **get_taxable_assets_aggregated()**
|
||||||
|
- Создать 3 актива с одним `taxable_asset_type`
|
||||||
|
- Проверить корректность агрегации
|
||||||
|
|
||||||
|
3. **get_asset_balances_using_standard_logic()**
|
||||||
|
- Создать активы с амортизацией
|
||||||
|
- Сравнить результаты с отчетом "Asset Depreciations and Balances"
|
||||||
|
|
||||||
|
### 6.2 Интеграционное тестирование
|
||||||
|
|
||||||
|
1. **Полный рабочий процесс - Tam İl:**
|
||||||
|
- Создать Property Tax Return
|
||||||
|
- Выбрать год 2024
|
||||||
|
- Включить "Tam İl"
|
||||||
|
- Проверить заполнение полей 501.x и 504.x
|
||||||
|
|
||||||
|
2. **Полный рабочий процесс - İl Ərzində:**
|
||||||
|
- Создать Property Tax Return
|
||||||
|
- Выбрать год 2024
|
||||||
|
- Включить "İl Ərzində"
|
||||||
|
- Проверить заполнение полей 503.x и 504.x
|
||||||
|
|
||||||
|
3. **Переключение между режимами:**
|
||||||
|
- Изменить с "Tam İl" на "İl Ərzində"
|
||||||
|
- Проверить пересчет значений
|
||||||
|
|
||||||
|
### 6.3 Тестирование UI
|
||||||
|
|
||||||
|
1. **Взаимоисключающие чекбоксы:**
|
||||||
|
- Включить "Tam İl" → "İl Ərzində" должен быть выключен
|
||||||
|
- Включить "İl Ərzində" → "Tam İl" должен быть выключен
|
||||||
|
- Снять оба → "Tam İl" должен автоматически включиться
|
||||||
|
|
||||||
|
2. **Индикаторы загрузки:**
|
||||||
|
- Проверить отображение при вызове сервера
|
||||||
|
- Проверить исчезновение после завершения
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Производительность
|
||||||
|
|
||||||
|
### 7.1 Оптимизация запросов
|
||||||
|
|
||||||
|
1. **Единый вызов сервера:**
|
||||||
|
- Все данные получаются за один запрос
|
||||||
|
- Избегать множественных вызовов API
|
||||||
|
|
||||||
|
2. **Пакетная обработка активов:**
|
||||||
|
- Получение всех активов одним SQL запросом
|
||||||
|
- Использование оператора IN для фильтрации
|
||||||
|
|
||||||
|
3. **Агрегация в памяти:**
|
||||||
|
- Суммирование значений в Python, а не в SQL
|
||||||
|
- Уменьшение нагрузки на БД
|
||||||
|
|
||||||
|
### 7.2 Ожидаемая производительность
|
||||||
|
|
||||||
|
- **До 100 активов:** < 3 секунд
|
||||||
|
- **100-500 активов:** 3-10 секунд
|
||||||
|
- **500+ активов:** 10-30 секунд
|
||||||
|
|
||||||
|
### 7.3 Timeout настройки
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// В JavaScript нет explicit timeout, используется стандартный Frappe timeout
|
||||||
|
// Для Python метода можно увеличить через:
|
||||||
|
frappe.call({
|
||||||
|
...,
|
||||||
|
timeout: 120000 // 2 минуты
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Безопасность
|
||||||
|
|
||||||
|
### 8.1 Аутентификация и авторизация
|
||||||
|
|
||||||
|
1. **Метод доступен только аутентифицированным пользователям:**
|
||||||
|
```python
|
||||||
|
@frappe.whitelist()
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Проверка прав на doctype:**
|
||||||
|
- Пользователь должен иметь права на Property Tax Return
|
||||||
|
- Автоматически проверяется Frappe framework
|
||||||
|
|
||||||
|
3. **Фильтрация по компании:**
|
||||||
|
- Данные фильтруются по компании пользователя
|
||||||
|
- Невозможно получить данные других компаний
|
||||||
|
|
||||||
|
### 8.2 Валидация входных данных
|
||||||
|
|
||||||
|
1. **Тип данных:**
|
||||||
|
- `year` должен быть Integer
|
||||||
|
- `from_date`, `to_date` должны быть в формате YYYY-MM-DD
|
||||||
|
- `calculation_mode` должен быть 'tam_il' или 'il_erzinde'
|
||||||
|
|
||||||
|
2. **SQL Injection Prevention:**
|
||||||
|
- Использование параметризованных запросов
|
||||||
|
- Никаких прямых вставок пользовательского ввода в SQL
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. Миграция и развертывание
|
||||||
|
|
||||||
|
### 9.1 Шаги развертывания
|
||||||
|
|
||||||
|
1. **Обновление JSON схемы:**
|
||||||
|
```bash
|
||||||
|
cd /home/frappe/frappe-bench
|
||||||
|
bench --site site1 migrate
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Очистка кэша:**
|
||||||
|
```bash
|
||||||
|
bench --site site1 clear-cache
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Перезапуск (опционально):**
|
||||||
|
```bash
|
||||||
|
bench restart
|
||||||
|
```
|
||||||
|
|
||||||
|
### 9.2 Откат изменений
|
||||||
|
|
||||||
|
1. **Удаление новых полей через UI:**
|
||||||
|
- Customize Form → Property Tax Return
|
||||||
|
- Удалить поля tam_il и il_erzinde
|
||||||
|
|
||||||
|
2. **Откат кода:**
|
||||||
|
```bash
|
||||||
|
git revert <commit_hash>
|
||||||
|
bench --site site1 migrate
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. Документация для пользователей
|
||||||
|
|
||||||
|
### 10.1 Краткая инструкция
|
||||||
|
|
||||||
|
**Заголовок:** Автоматическое заполнение декларации по налогу на имущество
|
||||||
|
|
||||||
|
**Шаги:**
|
||||||
|
|
||||||
|
1. Откройте документ "Property Tax Return" (Декларация по налогу на имущество)
|
||||||
|
2. Заполните поле "İl" (Год), например: 2024
|
||||||
|
3. Выберите период:
|
||||||
|
- **Tam İl** - если компания работала полный год
|
||||||
|
- **İl Ərzində** - если компания была создана в течение года
|
||||||
|
4. Система автоматически заполнит таблицы Əlavə 1 и Əlavə 2 данными из ваших активов
|
||||||
|
|
||||||
|
**Примечания:**
|
||||||
|
- Для работы функции необходимо, чтобы в системе были созданы активы (Assets) с заполненными полями:
|
||||||
|
- `taxable_asset_type` - для налогооблагаемых активов
|
||||||
|
- `industrial_tax_article` или `tax_exempt_tax_article` - для освобожденных от налога активов
|
||||||
|
- Данные рассчитываются на основе фактической амортизации активов
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 11. Список файлов
|
||||||
|
|
||||||
|
### 11.1 Измененные файлы
|
||||||
|
|
||||||
|
1. **property_tax_return.json**
|
||||||
|
- Путь: `/home/frappe/frappe-bench/apps/taxes_az/taxes_az/taxes_az/doctype/property_tax_return/property_tax_return.json`
|
||||||
|
- Изменения: Добавлены поля tam_il и il_erzinde
|
||||||
|
|
||||||
|
2. **property_tax_return.py**
|
||||||
|
- Путь: `/home/frappe/frappe-bench/apps/taxes_az/taxes_az/taxes_az/doctype/property_tax_return/property_tax_return.py`
|
||||||
|
- Изменения: Реализован весь серверный код (~684 строки)
|
||||||
|
|
||||||
|
3. **property_tax_return.js**
|
||||||
|
- Путь: `/home/frappe/frappe-bench/apps/taxes_az/taxes_az/taxes_az/doctype/property_tax_return/property_tax_return.js`
|
||||||
|
- Изменения: Добавлен клиентский код автозаполнения (~177 строк)
|
||||||
|
|
||||||
|
### 11.2 Новые файлы
|
||||||
|
|
||||||
|
1. **TECHNICAL_SPECIFICATION_PROPERTY_TAX_AUTOMATION.md** (этот документ)
|
||||||
|
- Путь: `/home/frappe/frappe-bench/apps/taxes_az/TECHNICAL_SPECIFICATION_PROPERTY_TAX_AUTOMATION.md`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 12. Известные ограничения
|
||||||
|
|
||||||
|
1. **Предварительное создание строк:**
|
||||||
|
- Строки в Əlavə 1 и Əlavə 2 должны быть созданы заранее клиентскими скриптами
|
||||||
|
- Система не создает новые строки автоматически
|
||||||
|
|
||||||
|
2. **Один год за раз:**
|
||||||
|
- Расчет выполняется только для одного календарного года
|
||||||
|
- Невозможно рассчитать для нескольких лет одновременно
|
||||||
|
|
||||||
|
3. **Зависимость от ERPNext версии:**
|
||||||
|
- Код проверяет наличие полей net_purchase_amount и Asset Capitalization
|
||||||
|
- Может требовать адаптации для очень старых версий ERPNext
|
||||||
|
|
||||||
|
4. **Производительность для больших объемов:**
|
||||||
|
- Для компаний с 1000+ активами может потребоваться оптимизация
|
||||||
|
- Рекомендуется тестирование на реальных данных
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 13. Будущие улучшения
|
||||||
|
|
||||||
|
### 13.1 Краткосрочные (v1.1)
|
||||||
|
|
||||||
|
1. Добавить кнопку принудительного пересчета
|
||||||
|
2. Показывать количество обработанных активов
|
||||||
|
3. Логирование детальной информации о расчетах
|
||||||
|
|
||||||
|
### 13.2 Долгосрочные (v2.0)
|
||||||
|
|
||||||
|
1. Поддержка расчета за произвольный период (не только календарный год)
|
||||||
|
2. Экспорт детального отчета по расчетам в Excel
|
||||||
|
3. Предварительный просмотр изменений перед применением
|
||||||
|
4. Batch processing для очень больших объемов данных
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 14. Контакты и поддержка
|
||||||
|
|
||||||
|
**Разработчик:** Claude (Anthropic)
|
||||||
|
**Дата разработки:** 2025-12-11
|
||||||
|
**Версия системы:** Frappe v15, ERPNext
|
||||||
|
**Git репозиторий:** `/home/frappe/frappe-bench/apps/taxes_az`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 15. История изменений
|
||||||
|
|
||||||
|
| Версия | Дата | Описание | Автор |
|
||||||
|
|--------|------|----------|-------|
|
||||||
|
| 1.0 | 2025-12-11 | Первоначальная разработка и развертывание | Claude |
|
||||||
|
| 1.0.1 | 2025-12-11 | Исправление ошибок в JavaScript (квадратные скобки для Unicode полей) | Claude |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Конец документа**
|
||||||
|
|
@ -1,12 +1,13 @@
|
||||||
// Код для экспорта XML
|
// Код для экспорта XML и автоматического заполнения из Asset
|
||||||
frappe.ui.form.on('Property tax return', {
|
frappe.ui.form.on('Property tax return', {
|
||||||
refresh: function(frm) {
|
refresh: function(frm) {
|
||||||
|
// Add Export XML button
|
||||||
frm.add_custom_button(__('Export XML'), function() {
|
frm.add_custom_button(__('Export XML'), function() {
|
||||||
frappe.call({
|
frappe.call({
|
||||||
method: 'taxes_az.taxes_az.doctype.xml_mapping_tool.xml_mapping_tool.generate_xml',
|
method: 'taxes_az.taxes_az.doctype.xml_mapping_tool.xml_mapping_tool.generate_xml',
|
||||||
args: {
|
args: {
|
||||||
mapping_tool: 'Property tax return', // Замените на имя вашего документа XML Mapping Tool
|
mapping_tool: 'Property tax return',
|
||||||
doctype: 'Property tax return', // Замените на имя DocType
|
doctype: 'Property tax return',
|
||||||
docname: frm.docname
|
docname: frm.docname
|
||||||
},
|
},
|
||||||
callback: function(r) {
|
callback: function(r) {
|
||||||
|
|
@ -28,6 +29,148 @@ frappe.ui.form.on('Property tax return', {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}, __('Actions'));
|
}, __('Actions'));
|
||||||
|
|
||||||
|
// Ensure checkbox exclusivity on load
|
||||||
|
setup_checkbox_exclusivity(frm);
|
||||||
|
},
|
||||||
|
|
||||||
|
// Handle year changes
|
||||||
|
il: function(frm) {
|
||||||
|
if (frm.doc.il && (frm.doc.tam_il || frm.doc.il_erzinde)) {
|
||||||
|
reload_property_tax_data(frm);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Checkbox handlers - ensure mutual exclusivity
|
||||||
|
tam_il: function(frm) {
|
||||||
|
if (frm.doc.tam_il) {
|
||||||
|
frm.set_value('il_erzinde', 0);
|
||||||
|
if (frm.doc.il) {
|
||||||
|
reload_property_tax_data(frm);
|
||||||
|
}
|
||||||
|
} else if (!frm.doc.il_erzinde) {
|
||||||
|
// If unchecking tam_il and il_erzinde is also unchecked, default to tam_il
|
||||||
|
frm.set_value('tam_il', 1);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
il_erzinde: function(frm) {
|
||||||
|
if (frm.doc.il_erzinde) {
|
||||||
|
frm.set_value('tam_il', 0);
|
||||||
|
if (frm.doc.il) {
|
||||||
|
reload_property_tax_data(frm);
|
||||||
|
}
|
||||||
|
} else if (!frm.doc.tam_il) {
|
||||||
|
// If unchecking il_erzinde and tam_il is also unchecked, default to tam_il
|
||||||
|
frm.set_value('tam_il', 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
});
|
||||||
|
|
||||||
|
function setup_checkbox_exclusivity(frm) {
|
||||||
|
// Ensure at least one is always checked
|
||||||
|
if (!frm.doc.tam_il && !frm.doc.il_erzinde) {
|
||||||
|
frm.set_value('tam_il', 1);
|
||||||
|
}
|
||||||
|
// Ensure only one is checked
|
||||||
|
if (frm.doc.tam_il && frm.doc.il_erzinde) {
|
||||||
|
frm.set_value('il_erzinde', 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function reload_property_tax_data(frm) {
|
||||||
|
// Validate required fields
|
||||||
|
if (!frm.doc.il) {
|
||||||
|
frappe.msgprint(__('Please select a year (İl) first'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!frm.doc.tam_il && !frm.doc.il_erzinde) {
|
||||||
|
frappe.msgprint(__('Please select either Tam İl or İl Ərzində'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show loading indicator
|
||||||
|
frappe.dom.freeze(__('Loading property tax data...'));
|
||||||
|
|
||||||
|
// Calculate date range from year
|
||||||
|
const from_date = `${frm.doc.il}-01-01`;
|
||||||
|
const to_date = `${frm.doc.il}-12-31`;
|
||||||
|
const calculation_mode = frm.doc.tam_il ? 'tam_il' : 'il_erzinde';
|
||||||
|
|
||||||
|
// Get default company
|
||||||
|
const company = frappe.defaults.get_user_default('Company');
|
||||||
|
|
||||||
|
// Call server-side method
|
||||||
|
frappe.call({
|
||||||
|
method: 'taxes_az.taxes_az.doctype.property_tax_return.property_tax_return.populate_property_tax_tables',
|
||||||
|
args: {
|
||||||
|
company: company,
|
||||||
|
year: frm.doc.il,
|
||||||
|
from_date: from_date,
|
||||||
|
to_date: to_date,
|
||||||
|
calculation_mode: calculation_mode,
|
||||||
|
existing_elave_1: frm.doc.elave_1 || [],
|
||||||
|
existing_elave_2: frm.doc.elave_2 || []
|
||||||
|
},
|
||||||
|
callback: function(r) {
|
||||||
|
frappe.dom.unfreeze();
|
||||||
|
|
||||||
|
if (r.message && r.message.success) {
|
||||||
|
// Update Əlavə 1 (Tax Exempt)
|
||||||
|
update_child_table_values(frm, 'elave_1', r.message.elave_1_data);
|
||||||
|
|
||||||
|
// Update Əlavə 2 (Taxable)
|
||||||
|
update_child_table_values(frm, 'elave_2', r.message.elave_2_data);
|
||||||
|
|
||||||
|
frm.refresh_fields();
|
||||||
|
|
||||||
|
frappe.show_alert({
|
||||||
|
message: __('Property tax data loaded successfully'),
|
||||||
|
indicator: 'green'
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
frappe.msgprint({
|
||||||
|
title: __('Error'),
|
||||||
|
message: r.message ? r.message.error : __('Failed to load data'),
|
||||||
|
indicator: 'red'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function(err) {
|
||||||
|
frappe.dom.unfreeze();
|
||||||
|
frappe.msgprint({
|
||||||
|
title: __('Error'),
|
||||||
|
message: __('Server error while loading data'),
|
||||||
|
indicator: 'red'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function update_child_table_values(frm, table_name, data_map) {
|
||||||
|
if (!frm.doc[table_name]) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
frm.doc[table_name].forEach(function(row) {
|
||||||
|
// Find matching data based on the identifier field
|
||||||
|
let matching_key = null;
|
||||||
|
|
||||||
|
if (table_name === 'elave_1') {
|
||||||
|
matching_key = row['azadolmasəbəbi'];
|
||||||
|
} else if (table_name === 'elave_2') {
|
||||||
|
matching_key = row['vergiyəcəlbolunanəmlaklarınkateqoriyası'];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update row with values from data_map
|
||||||
|
if (matching_key && data_map[matching_key]) {
|
||||||
|
const field_data = data_map[matching_key];
|
||||||
|
|
||||||
|
Object.keys(field_data).forEach(function(field) {
|
||||||
|
const value = field_data[field];
|
||||||
|
frappe.model.set_value(row.doctype, row.name, field, value);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
@ -12,6 +12,8 @@
|
||||||
"vergiorqan\u0131",
|
"vergiorqan\u0131",
|
||||||
"vergid\u00f6vr\u00fc",
|
"vergid\u00f6vr\u00fc",
|
||||||
"il",
|
"il",
|
||||||
|
"tam_il",
|
||||||
|
"il_erzinde",
|
||||||
"\u015f\u0259xsi",
|
"\u015f\u0259xsi",
|
||||||
"v\u00f6en",
|
"v\u00f6en",
|
||||||
"soyad\u0131",
|
"soyad\u0131",
|
||||||
|
|
@ -91,6 +93,18 @@
|
||||||
"label": "\u0130l",
|
"label": "\u0130l",
|
||||||
"length": 4
|
"length": 4
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"default": "1",
|
||||||
|
"fieldname": "tam_il",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Tam \u0130l"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"fieldname": "il_erzinde",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "\u0130l \u018frzind\u0259"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "\u015f\u0259xsi",
|
"fieldname": "\u015f\u0259xsi",
|
||||||
"fieldtype": "Section Break",
|
"fieldtype": "Section Break",
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,735 @@
|
||||||
# Copyright (c) 2025, Jey Soft and contributors
|
# Copyright (c) 2025, Jey Soft and contributors
|
||||||
# For license information, please see license.txt
|
# For license information, please see license.txt
|
||||||
|
|
||||||
# import frappe
|
import frappe
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
|
from frappe.utils import flt, add_days
|
||||||
|
import re
|
||||||
|
import json
|
||||||
|
|
||||||
|
|
||||||
class Propertytaxreturn(Document):
|
class Propertytaxreturn(Document):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def populate_property_tax_tables(company, year, from_date, to_date, calculation_mode, existing_elave_1, existing_elave_2):
|
||||||
|
"""
|
||||||
|
Main method to populate Property Tax Return child tables from Asset data
|
||||||
|
|
||||||
|
Args:
|
||||||
|
company: Company name
|
||||||
|
year: Tax year (integer)
|
||||||
|
from_date: Start date (YYYY-MM-DD)
|
||||||
|
to_date: End date (YYYY-MM-DD)
|
||||||
|
calculation_mode: 'tam_il' or 'il_erzinde'
|
||||||
|
existing_elave_1: Existing Əlavə 1 rows (JSON array)
|
||||||
|
existing_elave_2: Existing Əlavə 2 rows (JSON array)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Dictionary with elave_1_data and elave_2_data mappings
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# Parse JSON if needed
|
||||||
|
if isinstance(existing_elave_1, str):
|
||||||
|
existing_elave_1 = json.loads(existing_elave_1)
|
||||||
|
if isinstance(existing_elave_2, str):
|
||||||
|
existing_elave_2 = json.loads(existing_elave_2)
|
||||||
|
|
||||||
|
# Get aggregated asset data
|
||||||
|
taxable_aggregated = get_taxable_assets_aggregated(company, from_date, to_date)
|
||||||
|
tax_exempt_aggregated = get_tax_exempt_assets_aggregated(company, from_date, to_date)
|
||||||
|
|
||||||
|
# Map to rows with proper field names based on calculation_mode
|
||||||
|
# Use fuzzy matching for Əlavə 2 (Taxable Assets)
|
||||||
|
elave_2_data = {}
|
||||||
|
for category, data in taxable_aggregated.items():
|
||||||
|
# Find matching row by partial match (e.g., "Mənzillər" matches "1.5 Mənzillər")
|
||||||
|
matching_key = find_matching_category(category, existing_elave_2)
|
||||||
|
|
||||||
|
if matching_key:
|
||||||
|
if calculation_mode == 'tam_il':
|
||||||
|
elave_2_data[matching_key] = {
|
||||||
|
'hesabatilininəvvəlinəəsasvəsaitlərinqalıqdəyəri': data['value_as_on_from_date'],
|
||||||
|
'hesabatilinsonunaəsasvəsaitlərinqalıqdəyəri': data['net_asset_value_as_on_to_date'],
|
||||||
|
'hesabatilierzindeuçotaalınan': 0
|
||||||
|
}
|
||||||
|
else: # il_erzinde
|
||||||
|
elave_2_data[matching_key] = {
|
||||||
|
'hesabatilierzindeuçotaalınan': data['value_of_new_purchase'],
|
||||||
|
'hesabatilinsonunaəsasvəsaitlərinqalıqdəyəri': data['net_asset_value_as_on_to_date'],
|
||||||
|
'hesabatilininəvvəlinəəsasvəsaitlərinqalıqdəyəri': 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# Map to rows with fuzzy matching for Əlavə 1 (Tax Exempt Assets)
|
||||||
|
elave_1_data = {}
|
||||||
|
for article_number, data in tax_exempt_aggregated.items():
|
||||||
|
# Find matching row by article number in "Azadolma səbəbi"
|
||||||
|
matching_key = find_matching_azadolma_sebeb(article_number, existing_elave_1)
|
||||||
|
|
||||||
|
if matching_key:
|
||||||
|
if calculation_mode == 'tam_il':
|
||||||
|
elave_1_data[matching_key] = {
|
||||||
|
'hesabatilininəvvəlinəəsasvəsaitlərinqalıqdəyərimanatla5012': data['value_as_on_from_date'],
|
||||||
|
'ilinsonunaəsasvəsaitlərinqalıqdəyəri': data['net_asset_value_as_on_to_date'],
|
||||||
|
'hesabatiliərzindəəsasvəsaitlərinqalıqdəyəri': 0
|
||||||
|
}
|
||||||
|
else: # il_erzinde
|
||||||
|
elave_1_data[matching_key] = {
|
||||||
|
'hesabatiliərzindəəsasvəsaitlərinqalıqdəyəri': data['value_of_new_purchase'],
|
||||||
|
'ilinsonunaəsasvəsaitlərinqalıqdəyəri': data['net_asset_value_as_on_to_date'],
|
||||||
|
'hesabatilininəvvəlinəəsasvəsaitlərinqalıqdəyərimanatla5012': 0
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
'success': True,
|
||||||
|
'elave_1_data': elave_1_data,
|
||||||
|
'elave_2_data': elave_2_data
|
||||||
|
}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
frappe.log_error(f"Error in populate_property_tax_tables: {str(e)}", "Property Tax Return")
|
||||||
|
return {
|
||||||
|
'success': False,
|
||||||
|
'error': str(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def get_taxable_assets_aggregated(company, from_date, to_date):
|
||||||
|
"""
|
||||||
|
Get aggregated data for taxable assets grouped by taxable_asset_type
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Dictionary: {
|
||||||
|
'taxable_asset_type_value': {
|
||||||
|
'value_as_on_from_date': float,
|
||||||
|
'value_of_new_purchase': float,
|
||||||
|
'net_asset_value_as_on_to_date': float
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
# Get all assets with taxable_asset_type populated
|
||||||
|
assets_with_taxable_type = frappe.db.sql("""
|
||||||
|
SELECT
|
||||||
|
name,
|
||||||
|
taxable_asset_type
|
||||||
|
FROM
|
||||||
|
`tabAsset`
|
||||||
|
WHERE
|
||||||
|
docstatus = 1
|
||||||
|
AND company = %(company)s
|
||||||
|
AND taxable_asset_type IS NOT NULL
|
||||||
|
AND taxable_asset_type != ''
|
||||||
|
AND purchase_date <= %(to_date)s
|
||||||
|
""", {
|
||||||
|
'company': company,
|
||||||
|
'to_date': to_date
|
||||||
|
}, as_dict=True)
|
||||||
|
|
||||||
|
if not assets_with_taxable_type:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
# Get asset balances
|
||||||
|
asset_names = [asset['name'] for asset in assets_with_taxable_type]
|
||||||
|
asset_balances = get_asset_balances_using_standard_logic(company, from_date, to_date, asset_names)
|
||||||
|
|
||||||
|
# Aggregate by taxable_asset_type
|
||||||
|
aggregated = {}
|
||||||
|
|
||||||
|
for asset in assets_with_taxable_type:
|
||||||
|
asset_type = asset['taxable_asset_type']
|
||||||
|
|
||||||
|
# Find balance for this asset
|
||||||
|
balance = next(
|
||||||
|
(b for b in asset_balances if b['asset'] == asset['name']),
|
||||||
|
{}
|
||||||
|
)
|
||||||
|
|
||||||
|
# Initialize if not exists
|
||||||
|
if asset_type not in aggregated:
|
||||||
|
aggregated[asset_type] = {
|
||||||
|
'value_as_on_from_date': 0.0,
|
||||||
|
'value_of_new_purchase': 0.0,
|
||||||
|
'net_asset_value_as_on_to_date': 0.0
|
||||||
|
}
|
||||||
|
|
||||||
|
# Accumulate values
|
||||||
|
aggregated[asset_type]['value_as_on_from_date'] += flt(balance.get('value_as_on_from_date', 0))
|
||||||
|
aggregated[asset_type]['value_of_new_purchase'] += flt(balance.get('value_of_new_purchase', 0))
|
||||||
|
aggregated[asset_type]['net_asset_value_as_on_to_date'] += flt(balance.get('net_asset_value_as_on_to_date', 0))
|
||||||
|
|
||||||
|
return aggregated
|
||||||
|
|
||||||
|
|
||||||
|
def get_tax_exempt_assets_aggregated(company, from_date, to_date):
|
||||||
|
"""
|
||||||
|
Get aggregated data for tax exempt assets grouped by tax article
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Dictionary: {
|
||||||
|
'tax_article_number': { # e.g., '199.1', '207.3'
|
||||||
|
'value_as_on_from_date': float,
|
||||||
|
'value_of_new_purchase': float,
|
||||||
|
'net_asset_value_as_on_to_date': float
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
# Get all assets with industrial_tax_article or tax_exempt_tax_article
|
||||||
|
# Exclude agricultural_tax_article as per requirements
|
||||||
|
assets_with_tax_articles = frappe.db.sql("""
|
||||||
|
SELECT
|
||||||
|
a.name,
|
||||||
|
a.industrial_tax_article,
|
||||||
|
a.tax_exempt_tax_article,
|
||||||
|
COALESCE(NULLIF(a.industrial_tax_article, ''), a.tax_exempt_tax_article) as tax_article
|
||||||
|
FROM
|
||||||
|
`tabAsset` a
|
||||||
|
WHERE
|
||||||
|
a.docstatus = 1
|
||||||
|
AND a.company = %(company)s
|
||||||
|
AND (
|
||||||
|
(a.industrial_tax_article IS NOT NULL AND a.industrial_tax_article != '')
|
||||||
|
OR (a.tax_exempt_tax_article IS NOT NULL AND a.tax_exempt_tax_article != '')
|
||||||
|
)
|
||||||
|
AND a.purchase_date <= %(to_date)s
|
||||||
|
""", {
|
||||||
|
'company': company,
|
||||||
|
'to_date': to_date
|
||||||
|
}, as_dict=True)
|
||||||
|
|
||||||
|
if not assets_with_tax_articles:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
# Get asset balances
|
||||||
|
asset_names = [asset['name'] for asset in assets_with_tax_articles]
|
||||||
|
asset_balances = get_asset_balances_using_standard_logic(company, from_date, to_date, asset_names)
|
||||||
|
|
||||||
|
# Aggregate by tax article number
|
||||||
|
aggregated = {}
|
||||||
|
|
||||||
|
for asset in assets_with_tax_articles:
|
||||||
|
# Extract article number from Tax Article name
|
||||||
|
tax_article_full = asset['tax_article']
|
||||||
|
article_number = extract_article_number(tax_article_full)
|
||||||
|
|
||||||
|
if not article_number:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Find balance for this asset
|
||||||
|
balance = next(
|
||||||
|
(b for b in asset_balances if b['asset'] == asset['name']),
|
||||||
|
{}
|
||||||
|
)
|
||||||
|
|
||||||
|
# Initialize if not exists
|
||||||
|
if article_number not in aggregated:
|
||||||
|
aggregated[article_number] = {
|
||||||
|
'value_as_on_from_date': 0.0,
|
||||||
|
'value_of_new_purchase': 0.0,
|
||||||
|
'net_asset_value_as_on_to_date': 0.0
|
||||||
|
}
|
||||||
|
|
||||||
|
# Accumulate values
|
||||||
|
aggregated[article_number]['value_as_on_from_date'] += flt(balance.get('value_as_on_from_date', 0))
|
||||||
|
aggregated[article_number]['value_of_new_purchase'] += flt(balance.get('value_of_new_purchase', 0))
|
||||||
|
aggregated[article_number]['net_asset_value_as_on_to_date'] += flt(balance.get('net_asset_value_as_on_to_date', 0))
|
||||||
|
|
||||||
|
return aggregated
|
||||||
|
|
||||||
|
|
||||||
|
def extract_article_number(tax_article_name):
|
||||||
|
"""
|
||||||
|
Extract article number from Tax Article name
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
"Vergi Məcəlləsinin 199.1-ci maddəsinə əsasən" → "199.1"
|
||||||
|
"Vergi Məcəlləsinin 207.3-cü maddəsinə əsasən" → "207.3"
|
||||||
|
"Vergi Məcəlləsinin 199.4.1-ci maddəsinə əsasən" → "199.4.1"
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
String: Article number or None if not found
|
||||||
|
"""
|
||||||
|
if not tax_article_name:
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Pattern to match article numbers like 199.1, 207.3, 199.4.1-1, 227.1
|
||||||
|
pattern = r'(\d+(?:\.\d+)*(?:-\d+)?)'
|
||||||
|
match = re.search(pattern, tax_article_name)
|
||||||
|
|
||||||
|
if match:
|
||||||
|
return match.group(1)
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def find_matching_azadolma_sebeb(article_number, existing_rows):
|
||||||
|
"""
|
||||||
|
Find the "Azadolma səbəbi" value that contains the given article number
|
||||||
|
|
||||||
|
Args:
|
||||||
|
article_number: String like "199.1" or "207.3"
|
||||||
|
existing_rows: List of existing Əlavə 1 rows
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The matching "Azadolma səbəbi" value or None
|
||||||
|
"""
|
||||||
|
if not article_number:
|
||||||
|
return None
|
||||||
|
|
||||||
|
for row in existing_rows:
|
||||||
|
azadolma_sebeb = row.get('azadolmasəbəbi', '')
|
||||||
|
if article_number in azadolma_sebeb:
|
||||||
|
return azadolma_sebeb
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def find_matching_category(asset_category, existing_rows):
|
||||||
|
"""
|
||||||
|
Find matching category in Əlavə 2 rows using partial match
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
Asset has: "Mənzillər"
|
||||||
|
Row has: "1.5 Mənzillər"
|
||||||
|
Result: Match found
|
||||||
|
|
||||||
|
Asset has: "Qeyri-yaşayış binaları (sahələri)"
|
||||||
|
Row has: "1.2 Qeyri-yaşayış binaları (sahələri)"
|
||||||
|
Result: Match found
|
||||||
|
|
||||||
|
Args:
|
||||||
|
asset_category: Category from Asset's taxable_asset_type field
|
||||||
|
existing_rows: List of existing Əlavə 2 rows
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The matching "Vergiyə cəlb olunan əmlakların kateqoriyası" value or None
|
||||||
|
"""
|
||||||
|
if not asset_category:
|
||||||
|
return None
|
||||||
|
|
||||||
|
for row in existing_rows:
|
||||||
|
row_category = row.get('vergiyəcəlbolunanəmlaklarınkateqoriyası', '')
|
||||||
|
|
||||||
|
# Check if asset_category is contained in row_category (e.g., "Mənzillər" in "1.5 Mənzillər")
|
||||||
|
if asset_category in row_category:
|
||||||
|
return row_category
|
||||||
|
|
||||||
|
# Also check reverse - if row_category is contained in asset_category
|
||||||
|
# (in case asset has full name with prefix)
|
||||||
|
if row_category in asset_category:
|
||||||
|
return row_category
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def get_asset_balances_using_standard_logic(company, from_date, to_date, asset_names):
|
||||||
|
"""
|
||||||
|
Calculate asset balances using ERPNext's Asset Depreciations and Balances report logic
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List of dictionaries with asset balances
|
||||||
|
"""
|
||||||
|
if not asset_names:
|
||||||
|
return []
|
||||||
|
|
||||||
|
# Check ERPNext version for field compatibility
|
||||||
|
version_info = check_erpnext_version()
|
||||||
|
purchase_amount_field = version_info['purchase_amount_field']
|
||||||
|
has_capitalization = version_info['has_capitalization']
|
||||||
|
|
||||||
|
# Get asset details (gross values)
|
||||||
|
asset_details = get_asset_cost_details(
|
||||||
|
company, from_date, to_date, asset_names,
|
||||||
|
purchase_amount_field, has_capitalization
|
||||||
|
)
|
||||||
|
|
||||||
|
# Get depreciation details
|
||||||
|
asset_depreciation = get_asset_depreciation_details(
|
||||||
|
company, from_date, to_date, asset_names
|
||||||
|
)
|
||||||
|
|
||||||
|
# Get value adjustments
|
||||||
|
asset_adjustments = get_asset_value_adjustments(
|
||||||
|
company, from_date, to_date, asset_names
|
||||||
|
)
|
||||||
|
|
||||||
|
# Combine and calculate net values
|
||||||
|
result = []
|
||||||
|
|
||||||
|
for asset_detail in asset_details:
|
||||||
|
asset_name = asset_detail.get('name')
|
||||||
|
|
||||||
|
# Find depreciation
|
||||||
|
depreciation = next(
|
||||||
|
(dep for dep in asset_depreciation if dep['asset'] == asset_name),
|
||||||
|
{}
|
||||||
|
)
|
||||||
|
|
||||||
|
# Find adjustments
|
||||||
|
adjustments = asset_adjustments.get(asset_name, {
|
||||||
|
'adjustment_before_from_date': 0.0,
|
||||||
|
'adjustment_till_to_date': 0.0
|
||||||
|
})
|
||||||
|
|
||||||
|
# Calculate values following ERPNext standard report logic
|
||||||
|
value_as_on_from_date = flt(asset_detail.get('cost_as_on_from_date', 0))
|
||||||
|
value_as_on_from_date += adjustments['adjustment_before_from_date']
|
||||||
|
|
||||||
|
value_of_new_purchase = flt(asset_detail.get('cost_of_new_purchase', 0))
|
||||||
|
|
||||||
|
adjustment_during_period = (
|
||||||
|
adjustments['adjustment_till_to_date'] -
|
||||||
|
adjustments['adjustment_before_from_date']
|
||||||
|
)
|
||||||
|
|
||||||
|
value_as_on_to_date = (
|
||||||
|
value_as_on_from_date +
|
||||||
|
value_of_new_purchase -
|
||||||
|
flt(asset_detail.get('cost_of_sold_asset', 0)) -
|
||||||
|
flt(asset_detail.get('cost_of_scrapped_asset', 0)) -
|
||||||
|
flt(asset_detail.get('cost_of_capitalized_asset', 0)) +
|
||||||
|
adjustment_during_period
|
||||||
|
)
|
||||||
|
|
||||||
|
accumulated_depreciation_as_on_from_date = flt(
|
||||||
|
depreciation.get('accumulated_depreciation_as_on_from_date', 0)
|
||||||
|
)
|
||||||
|
|
||||||
|
accumulated_depreciation_as_on_to_date = (
|
||||||
|
accumulated_depreciation_as_on_from_date +
|
||||||
|
flt(depreciation.get('depreciation_amount_during_the_period', 0)) -
|
||||||
|
flt(depreciation.get('depreciation_eliminated_during_the_period', 0)) -
|
||||||
|
flt(depreciation.get('depreciation_eliminated_via_reversal', 0))
|
||||||
|
)
|
||||||
|
|
||||||
|
net_asset_value_as_on_from_date = (
|
||||||
|
value_as_on_from_date - accumulated_depreciation_as_on_from_date
|
||||||
|
)
|
||||||
|
|
||||||
|
net_asset_value_as_on_to_date = (
|
||||||
|
value_as_on_to_date - accumulated_depreciation_as_on_to_date
|
||||||
|
)
|
||||||
|
|
||||||
|
balance_result = {
|
||||||
|
'asset': asset_name,
|
||||||
|
'value_as_on_from_date': net_asset_value_as_on_from_date,
|
||||||
|
'value_of_new_purchase': value_of_new_purchase,
|
||||||
|
'net_asset_value_as_on_to_date': net_asset_value_as_on_to_date
|
||||||
|
}
|
||||||
|
result.append(balance_result)
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def check_erpnext_version():
|
||||||
|
"""Check ERPNext version for field compatibility"""
|
||||||
|
columns = frappe.db.get_table_columns("Asset")
|
||||||
|
has_net_purchase_amount = "net_purchase_amount" in columns
|
||||||
|
has_capitalization = "Asset Capitalization" in frappe.get_all("DocType", pluck="name")
|
||||||
|
|
||||||
|
return {
|
||||||
|
"purchase_amount_field": "net_purchase_amount" if has_net_purchase_amount else "gross_purchase_amount",
|
||||||
|
"has_capitalization": has_capitalization
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def get_asset_cost_details(company, from_date, to_date, asset_names, purchase_amount_field, has_capitalization):
|
||||||
|
"""Get asset cost details (mimics ERPNext standard report)"""
|
||||||
|
|
||||||
|
asset_placeholders = ", ".join(["%s"] * len(asset_names))
|
||||||
|
|
||||||
|
capitalization_check = ""
|
||||||
|
if has_capitalization:
|
||||||
|
capitalization_check = """
|
||||||
|
AND NOT EXISTS(
|
||||||
|
SELECT 1 FROM `tabAsset Capitalization Asset Item` acai
|
||||||
|
JOIN `tabAsset Capitalization` ac ON acai.parent = ac.name
|
||||||
|
WHERE acai.asset = a.name
|
||||||
|
AND ac.posting_date < %s
|
||||||
|
AND ac.docstatus = 1
|
||||||
|
)
|
||||||
|
"""
|
||||||
|
|
||||||
|
query = f"""
|
||||||
|
SELECT
|
||||||
|
a.name,
|
||||||
|
IFNULL(SUM(
|
||||||
|
CASE
|
||||||
|
WHEN a.purchase_date < %s THEN
|
||||||
|
CASE
|
||||||
|
WHEN IFNULL(a.disposal_date, 0) = 0 OR a.disposal_date >= %s
|
||||||
|
THEN a.{purchase_amount_field}
|
||||||
|
ELSE 0
|
||||||
|
END
|
||||||
|
ELSE 0
|
||||||
|
END
|
||||||
|
), 0) as cost_as_on_from_date,
|
||||||
|
|
||||||
|
IFNULL(SUM(
|
||||||
|
CASE
|
||||||
|
WHEN a.purchase_date >= %s
|
||||||
|
THEN a.{purchase_amount_field}
|
||||||
|
ELSE 0
|
||||||
|
END
|
||||||
|
), 0) as cost_of_new_purchase,
|
||||||
|
|
||||||
|
IFNULL(SUM(
|
||||||
|
CASE
|
||||||
|
WHEN IFNULL(a.disposal_date, 0) != 0
|
||||||
|
AND a.disposal_date >= %s
|
||||||
|
AND a.disposal_date <= %s
|
||||||
|
THEN
|
||||||
|
CASE WHEN a.status = 'Sold' THEN a.{purchase_amount_field} ELSE 0 END
|
||||||
|
ELSE 0
|
||||||
|
END
|
||||||
|
), 0) as cost_of_sold_asset,
|
||||||
|
|
||||||
|
IFNULL(SUM(
|
||||||
|
CASE
|
||||||
|
WHEN IFNULL(a.disposal_date, 0) != 0
|
||||||
|
AND a.disposal_date >= %s
|
||||||
|
AND a.disposal_date <= %s
|
||||||
|
THEN
|
||||||
|
CASE WHEN a.status = 'Scrapped' THEN a.{purchase_amount_field} ELSE 0 END
|
||||||
|
ELSE 0
|
||||||
|
END
|
||||||
|
), 0) as cost_of_scrapped_asset,
|
||||||
|
|
||||||
|
IFNULL(SUM(
|
||||||
|
CASE
|
||||||
|
WHEN IFNULL(a.disposal_date, 0) != 0
|
||||||
|
AND a.disposal_date >= %s
|
||||||
|
AND a.disposal_date <= %s
|
||||||
|
THEN
|
||||||
|
CASE WHEN a.status = 'Capitalized' THEN a.{purchase_amount_field} ELSE 0 END
|
||||||
|
ELSE 0
|
||||||
|
END
|
||||||
|
), 0) as cost_of_capitalized_asset
|
||||||
|
FROM
|
||||||
|
`tabAsset` a
|
||||||
|
WHERE
|
||||||
|
a.docstatus = 1
|
||||||
|
AND a.company = %s
|
||||||
|
AND a.purchase_date <= %s
|
||||||
|
AND a.name IN ({asset_placeholders})
|
||||||
|
{capitalization_check}
|
||||||
|
GROUP BY a.name
|
||||||
|
"""
|
||||||
|
|
||||||
|
values = [
|
||||||
|
from_date, # cost_as_on_from_date condition 1
|
||||||
|
from_date, # cost_as_on_from_date condition 2
|
||||||
|
from_date, # cost_of_new_purchase
|
||||||
|
from_date, # cost_of_sold_asset condition 1
|
||||||
|
to_date, # cost_of_sold_asset condition 2
|
||||||
|
from_date, # cost_of_scrapped_asset condition 1
|
||||||
|
to_date, # cost_of_scrapped_asset condition 2
|
||||||
|
from_date, # cost_of_capitalized_asset condition 1
|
||||||
|
to_date, # cost_of_capitalized_asset condition 2
|
||||||
|
company, # company filter
|
||||||
|
to_date, # purchase_date filter
|
||||||
|
*asset_names # asset names for IN clause
|
||||||
|
]
|
||||||
|
|
||||||
|
if has_capitalization:
|
||||||
|
values.append(from_date)
|
||||||
|
|
||||||
|
return frappe.db.sql(query, values, as_dict=True)
|
||||||
|
|
||||||
|
|
||||||
|
def get_asset_depreciation_details(company, from_date, to_date, asset_names):
|
||||||
|
"""Get asset depreciation details (mimics ERPNext standard report)"""
|
||||||
|
|
||||||
|
asset_placeholders = ", ".join(["%s"] * len(asset_names))
|
||||||
|
|
||||||
|
query = f"""
|
||||||
|
SELECT
|
||||||
|
results.name as asset,
|
||||||
|
SUM(results.accumulated_depreciation_as_on_from_date) as accumulated_depreciation_as_on_from_date,
|
||||||
|
SUM(results.depreciation_eliminated_during_the_period) as depreciation_eliminated_during_the_period,
|
||||||
|
SUM(results.depreciation_amount_during_the_period) as depreciation_amount_during_the_period,
|
||||||
|
SUM(results.depreciation_eliminated_via_reversal) as depreciation_eliminated_via_reversal
|
||||||
|
FROM (
|
||||||
|
SELECT
|
||||||
|
a.name as name,
|
||||||
|
IFNULL(SUM(
|
||||||
|
CASE
|
||||||
|
WHEN gle.posting_date < %s
|
||||||
|
AND (IFNULL(a.disposal_date, 0) = 0 OR a.disposal_date >= %s)
|
||||||
|
THEN gle.debit
|
||||||
|
ELSE 0
|
||||||
|
END
|
||||||
|
), 0) as accumulated_depreciation_as_on_from_date,
|
||||||
|
|
||||||
|
IFNULL(SUM(
|
||||||
|
CASE
|
||||||
|
WHEN gle.posting_date <= %s
|
||||||
|
AND IFNULL(a.disposal_date, 0) = 0
|
||||||
|
THEN gle.credit
|
||||||
|
ELSE 0
|
||||||
|
END
|
||||||
|
), 0) as depreciation_eliminated_via_reversal,
|
||||||
|
|
||||||
|
IFNULL(SUM(
|
||||||
|
CASE
|
||||||
|
WHEN IFNULL(a.disposal_date, 0) != 0
|
||||||
|
AND a.disposal_date >= %s
|
||||||
|
AND a.disposal_date <= %s
|
||||||
|
AND gle.posting_date <= a.disposal_date
|
||||||
|
THEN gle.debit
|
||||||
|
ELSE 0
|
||||||
|
END
|
||||||
|
), 0) as depreciation_eliminated_during_the_period,
|
||||||
|
|
||||||
|
IFNULL(SUM(
|
||||||
|
CASE
|
||||||
|
WHEN gle.posting_date >= %s
|
||||||
|
AND gle.posting_date <= %s
|
||||||
|
AND (IFNULL(a.disposal_date, 0) = 0 OR gle.posting_date <= a.disposal_date)
|
||||||
|
THEN gle.debit
|
||||||
|
ELSE 0
|
||||||
|
END
|
||||||
|
), 0) as depreciation_amount_during_the_period
|
||||||
|
FROM
|
||||||
|
`tabGL Entry` gle
|
||||||
|
JOIN
|
||||||
|
`tabAsset` a ON gle.against_voucher = a.name
|
||||||
|
JOIN
|
||||||
|
`tabAsset Category Account` aca ON aca.parent = a.asset_category
|
||||||
|
AND aca.company_name = %s
|
||||||
|
JOIN
|
||||||
|
`tabCompany` company ON company.name = %s
|
||||||
|
WHERE
|
||||||
|
a.docstatus = 1
|
||||||
|
AND a.company = %s
|
||||||
|
AND a.purchase_date <= %s
|
||||||
|
AND gle.is_cancelled = 0
|
||||||
|
AND gle.account = IFNULL(aca.depreciation_expense_account, company.depreciation_expense_account)
|
||||||
|
AND a.name IN ({asset_placeholders})
|
||||||
|
GROUP BY a.name
|
||||||
|
|
||||||
|
UNION
|
||||||
|
|
||||||
|
SELECT
|
||||||
|
a.name as name,
|
||||||
|
IFNULL(SUM(
|
||||||
|
CASE
|
||||||
|
WHEN IFNULL(a.disposal_date, 0) != 0
|
||||||
|
AND a.disposal_date < %s
|
||||||
|
THEN 0
|
||||||
|
ELSE a.opening_accumulated_depreciation
|
||||||
|
END
|
||||||
|
), 0) as accumulated_depreciation_as_on_from_date,
|
||||||
|
0 as depreciation_eliminated_via_reversal,
|
||||||
|
IFNULL(SUM(
|
||||||
|
CASE
|
||||||
|
WHEN a.disposal_date >= %s
|
||||||
|
AND a.disposal_date <= %s
|
||||||
|
THEN a.opening_accumulated_depreciation
|
||||||
|
ELSE 0
|
||||||
|
END
|
||||||
|
), 0) as depreciation_eliminated_during_the_period,
|
||||||
|
0 as depreciation_amount_during_the_period
|
||||||
|
FROM
|
||||||
|
`tabAsset` a
|
||||||
|
WHERE
|
||||||
|
a.docstatus = 1
|
||||||
|
AND a.company = %s
|
||||||
|
AND a.purchase_date <= %s
|
||||||
|
AND a.name IN ({asset_placeholders})
|
||||||
|
GROUP BY a.name
|
||||||
|
) as results
|
||||||
|
GROUP BY results.name
|
||||||
|
"""
|
||||||
|
|
||||||
|
values = [
|
||||||
|
from_date, # accumulated_depreciation_as_on_from_date condition 1
|
||||||
|
from_date, # accumulated_depreciation_as_on_from_date condition 2
|
||||||
|
to_date, # depreciation_eliminated_via_reversal
|
||||||
|
from_date, # depreciation_eliminated_during_the_period condition 1
|
||||||
|
to_date, # depreciation_eliminated_during_the_period condition 2
|
||||||
|
from_date, # depreciation_amount_during_the_period condition 1
|
||||||
|
to_date, # depreciation_amount_during_the_period condition 2
|
||||||
|
company, # aca.company_name
|
||||||
|
company, # company.name
|
||||||
|
company, # a.company
|
||||||
|
to_date, # a.purchase_date
|
||||||
|
*asset_names, # asset names for IN clause (first SELECT)
|
||||||
|
from_date, # disposal_date condition (second SELECT)
|
||||||
|
from_date, # disposal_date condition (second SELECT)
|
||||||
|
to_date, # disposal_date condition (second SELECT)
|
||||||
|
company, # a.company (second SELECT)
|
||||||
|
to_date, # a.purchase_date (second SELECT)
|
||||||
|
*asset_names # asset names for IN clause (second SELECT)
|
||||||
|
]
|
||||||
|
|
||||||
|
return frappe.db.sql(query, values, as_dict=True)
|
||||||
|
|
||||||
|
|
||||||
|
def get_asset_value_adjustments(company, from_date, to_date, asset_names):
|
||||||
|
"""Get asset value adjustments (mimics ERPNext standard report)"""
|
||||||
|
|
||||||
|
asset_placeholders = ", ".join(["%s"] * len(asset_names))
|
||||||
|
|
||||||
|
query = f"""
|
||||||
|
SELECT
|
||||||
|
a.name AS asset,
|
||||||
|
IFNULL(
|
||||||
|
SUM(
|
||||||
|
CASE
|
||||||
|
WHEN gle.posting_date < %s
|
||||||
|
AND (a.disposal_date IS NULL OR a.disposal_date >= %s)
|
||||||
|
THEN gle.debit - gle.credit
|
||||||
|
ELSE 0
|
||||||
|
END
|
||||||
|
),
|
||||||
|
0) AS value_adjustment_before_from_date,
|
||||||
|
IFNULL(
|
||||||
|
SUM(
|
||||||
|
CASE
|
||||||
|
WHEN gle.posting_date <= %s
|
||||||
|
AND (a.disposal_date IS NULL OR a.disposal_date >= %s)
|
||||||
|
THEN gle.debit - gle.credit
|
||||||
|
ELSE 0
|
||||||
|
END
|
||||||
|
),
|
||||||
|
0) AS value_adjustment_till_to_date
|
||||||
|
FROM
|
||||||
|
`tabGL Entry` gle
|
||||||
|
JOIN
|
||||||
|
`tabAsset` a ON gle.against_voucher = a.name
|
||||||
|
JOIN
|
||||||
|
`tabAsset Category Account` aca ON aca.parent = a.asset_category
|
||||||
|
AND aca.company_name = %s
|
||||||
|
WHERE
|
||||||
|
gle.is_cancelled = 0
|
||||||
|
AND a.docstatus = 1
|
||||||
|
AND a.company = %s
|
||||||
|
AND a.purchase_date <= %s
|
||||||
|
AND gle.account = aca.fixed_asset_account
|
||||||
|
AND a.name IN ({asset_placeholders})
|
||||||
|
GROUP BY a.name
|
||||||
|
"""
|
||||||
|
|
||||||
|
values = [
|
||||||
|
from_date, # value_adjustment_before_from_date condition 1
|
||||||
|
from_date, # value_adjustment_before_from_date condition 2
|
||||||
|
to_date, # value_adjustment_till_to_date condition 1
|
||||||
|
to_date, # value_adjustment_till_to_date condition 2
|
||||||
|
company, # aca.company_name
|
||||||
|
company, # a.company
|
||||||
|
to_date, # a.purchase_date
|
||||||
|
*asset_names # asset names for IN clause
|
||||||
|
]
|
||||||
|
|
||||||
|
result = frappe.db.sql(query, values, as_dict=True)
|
||||||
|
|
||||||
|
# Convert to dictionary
|
||||||
|
adjustment_map = {}
|
||||||
|
for r in result:
|
||||||
|
adjustment_map[r['asset']] = {
|
||||||
|
'adjustment_before_from_date': flt(r.get('value_adjustment_before_from_date', 0)),
|
||||||
|
'adjustment_till_to_date': flt(r.get('value_adjustment_till_to_date', 0))
|
||||||
|
}
|
||||||
|
|
||||||
|
return adjustment_map
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue