Modifed property tax return

This commit is contained in:
Ali 2025-12-11 17:26:57 +04:00
parent 0335d577c7
commit d268d1b7bf
4 changed files with 1557 additions and 33 deletions

View File

@ -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 |
---
**Конец документа**

View File

@ -1,33 +1,176 @@
// Код для экспорта XML // Код для экспорта XML и автоматического заполнения из Asset
frappe.ui.form.on('Property tax return', { frappe.ui.form.on('Property tax return', {
refresh: function(frm) { refresh: function(frm) {
frm.add_custom_button(__('Export XML'), function() { // Add Export XML button
frappe.call({ frm.add_custom_button(__('Export XML'), function() {
method: 'taxes_az.taxes_az.doctype.xml_mapping_tool.xml_mapping_tool.generate_xml', frappe.call({
args: { method: 'taxes_az.taxes_az.doctype.xml_mapping_tool.xml_mapping_tool.generate_xml',
mapping_tool: 'Property tax return', // Замените на имя вашего документа XML Mapping Tool args: {
doctype: 'Property tax return', // Замените на имя DocType mapping_tool: 'Property tax return',
docname: frm.docname doctype: 'Property tax return',
}, docname: frm.docname
callback: function(r) { },
if (r.message) { callback: function(r) {
// Создаем временный элемент для скачивания if (r.message) {
var element = document.createElement('a'); // Создаем временный элемент для скачивания
element.setAttribute('href', 'data:text/xml;charset=utf-8,' + encodeURIComponent(r.message)); var element = document.createElement('a');
element.setAttribute('download', frm.docname + '.xml'); element.setAttribute('href', 'data:text/xml;charset=utf-8,' + encodeURIComponent(r.message));
element.style.display = 'none'; element.setAttribute('download', frm.docname + '.xml');
document.body.appendChild(element); element.style.display = 'none';
element.click(); document.body.appendChild(element);
document.body.removeChild(element); element.click();
document.body.removeChild(element);
frappe.show_alert({
message: __('XML file generated and downloaded'), frappe.show_alert({
indicator: 'green' message: __('XML file generated and downloaded'),
}); indicator: 'green'
} });
} }
}); }
}, __('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);
});
}
});
}

View File

@ -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",

View File

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