From c22dabf9431dc35e2e25bcaa1a2c1248767ce47f Mon Sep 17 00:00:00 2001
From: Ali <010109ali@gmail.com>
Date: Mon, 6 Oct 2025 17:32:06 +0400
Subject: [PATCH] added vat allocation doctype and modifed cob report
---
.../doctype/vat_allocation/vat_allocation.js | 437 +++++++++++++++---
.../vat_allocation/vat_allocation.json | 90 +++-
.../doctype/vat_allocation/vat_allocation.py | 90 +++-
.../vat_allocation_link.json | 10 +-
.../vat_allocation_payment_entry.json | 14 +-
.../vat_allocation_sales_invoice.json | 21 +-
.../customer_outstanding_balance.js | 17 +
.../customer_outstanding_balance.py | 142 +++++-
8 files changed, 721 insertions(+), 100 deletions(-)
diff --git a/taxes_az/taxes_az/doctype/vat_allocation/vat_allocation.js b/taxes_az/taxes_az/doctype/vat_allocation/vat_allocation.js
index 9211bed..690099e 100644
--- a/taxes_az/taxes_az/doctype/vat_allocation/vat_allocation.js
+++ b/taxes_az/taxes_az/doctype/vat_allocation/vat_allocation.js
@@ -251,6 +251,146 @@ function setup_journal_entry_query(frm) {
// Setup for VAT Journal Entry
frappe.ui.form.on('VAT allocation payment entry', {
+ // При изменении Customer - очистить все поля и удалить связанные links
+ customer: function(frm, cdt, cdn) {
+ let row = locals[cdt][cdn];
+ let old_row_name = row.name;
+
+ // Очищаем все поля
+ frappe.model.set_value(cdt, cdn, 'payment_entry', '');
+ frappe.model.set_value(cdt, cdn, 'payment_entry_amount', 0);
+ frappe.model.set_value(cdt, cdn, 'vat_journal_entry', '');
+ frappe.model.set_value(cdt, cdn, 'vat_amount', 0);
+ frappe.model.set_value(cdt, cdn, 'vat_allocated_amount', 0);
+ frappe.model.set_value(cdt, cdn, 'vat_allocated_allocated', 0);
+ frappe.model.set_value(cdt, cdn, 'vat_free_amount', 0);
+ frappe.model.set_value(cdt, cdn, 'vat_free_allocated', 0);
+ frappe.model.set_value(cdt, cdn, 'allocated_amount', 0);
+ frappe.model.set_value(cdt, cdn, 'unallocated_amount', 0);
+
+ // Удаляем все allocation links связанные с этой строкой
+ frm.doc.allocation_links = (frm.doc.allocation_links || []).filter(function(link) {
+ return link.payment_entry_row !== old_row_name;
+ });
+
+ frm.refresh_field('allocation_links');
+
+ // Пересчитываем totals
+ recalculate_totals_manually(frm);
+ frm.refresh_field('payment_entry_table');
+ frm.refresh_field('sales_invoice_table');
+ },
+
+ // При изменении Payment Entry - обновить суммы
+ payment_entry: function(frm, cdt, cdn) {
+ let row = locals[cdt][cdn];
+ let old_row_name = row.name;
+
+ // Всегда удаляем allocation links связанные с этой строкой при изменении/очистке Payment Entry
+ frm.doc.allocation_links = (frm.doc.allocation_links || []).filter(function(link) {
+ return link.payment_entry_row !== old_row_name;
+ });
+
+ frm.refresh_field('allocation_links');
+
+ if (!row.payment_entry) {
+ // Если Payment Entry очищен, обнуляем все поля
+ frappe.model.set_value(cdt, cdn, 'payment_entry_amount', 0);
+ frappe.model.set_value(cdt, cdn, 'vat_allocated_amount', 0);
+ frappe.model.set_value(cdt, cdn, 'vat_free_amount', 0);
+ frappe.model.set_value(cdt, cdn, 'allocated_amount', 0);
+ frappe.model.set_value(cdt, cdn, 'unallocated_amount', 0);
+
+ // Пересчитываем totals
+ recalculate_totals_manually(frm);
+ frm.refresh_field('payment_entry_table');
+ frm.refresh_field('sales_invoice_table');
+
+ return;
+ }
+
+ // Получаем данные из Payment Entry
+ frappe.call({
+ method: 'frappe.client.get_value',
+ args: {
+ doctype: 'Payment Entry',
+ filters: { name: row.payment_entry },
+ fieldname: ['paid_amount', 'party', 'docstatus']
+ },
+ callback: function(r) {
+ if (r.message) {
+ // Проверяем что Payment Entry submitted
+ if (r.message.docstatus !== 1) {
+ frappe.msgprint(__('Warning: Selected Payment Entry is not submitted'));
+ frappe.model.set_value(cdt, cdn, 'payment_entry', '');
+ return;
+ }
+
+ // Проверяем что customer совпадает
+ if (row.customer && r.message.party !== row.customer) {
+ frappe.msgprint(__('Payment Entry customer does not match the selected customer'));
+ frappe.model.set_value(cdt, cdn, 'payment_entry', '');
+ return;
+ }
+
+ // Устанавливаем payment_entry_amount
+ let paid_amount = r.message.paid_amount || 0;
+ frappe.model.set_value(cdt, cdn, 'payment_entry_amount', paid_amount);
+
+ // Если есть vat_journal_entry, пересчитываем на его основе
+ if (row.vat_journal_entry && row.vat_amount) {
+ let vat_allocated = row.vat_amount / 0.18;
+ frappe.model.set_value(cdt, cdn, 'vat_allocated_amount', vat_allocated);
+ frappe.model.set_value(cdt, cdn, 'vat_free_amount', paid_amount - vat_allocated);
+ } else {
+ // Иначе вся сумма идет в vat_free
+ frappe.model.set_value(cdt, cdn, 'vat_allocated_amount', 0);
+ frappe.model.set_value(cdt, cdn, 'vat_free_amount', paid_amount);
+ }
+
+ // Обнуляем allocated amounts (так как links удалены)
+ frappe.model.set_value(cdt, cdn, 'allocated_amount', 0);
+ frappe.model.set_value(cdt, cdn, 'vat_free_allocated', 0);
+ frappe.model.set_value(cdt, cdn, 'vat_allocated_allocated', 0);
+
+ // Unallocated = payment_entry_amount (так как allocated = 0)
+ frappe.model.set_value(cdt, cdn, 'unallocated_amount', paid_amount);
+
+ // Автоматически заполняем customer, если он не был заполнен
+ if (!row.customer) {
+ frappe.model.set_value(cdt, cdn, 'customer', r.message.party);
+ }
+
+ // Пересчитываем totals
+ recalculate_totals_manually(frm);
+ frm.refresh_field('payment_entry_table');
+ frm.refresh_field('sales_invoice_table');
+ }
+ }
+ });
+ },
+
+ // При удалении строки - удалить связанные links
+ payment_entry_table_remove: function(frm, cdt, cdn) {
+ // Собираем список всех существующих row names из текущей таблицы
+ let existing_rows = {};
+ for (let pe_row of frm.doc.payment_entry_table || []) {
+ existing_rows[pe_row.name] = true;
+ }
+
+ // Удаляем все allocation links, которые ссылаются на несуществующие строки
+ frm.doc.allocation_links = (frm.doc.allocation_links || []).filter(function(link) {
+ return existing_rows[link.payment_entry_row];
+ });
+
+ frm.refresh_field('allocation_links');
+
+ // Пересчитываем totals для оставшихся строк
+ recalculate_totals_manually(frm);
+ frm.refresh_field('payment_entry_table');
+ frm.refresh_field('sales_invoice_table');
+ },
+
vat_journal_entry: function(frm, cdt, cdn) {
let row = locals[cdt][cdn];
@@ -335,46 +475,93 @@ frappe.ui.form.on('VAT allocation payment entry', {
frappe.msgprint(__('Cannot allocate to disabled customer'));
return;
}
- // Open allocation dialog
- show_allocation_dialog(frm, row.name, row.customer);
+ // Open allocation dialog - используем idx для несохраненных строк
+ show_allocation_dialog(frm, row.idx, row.customer);
}
});
}
});
-// Show allocation dialog
-function show_allocation_dialog(frm, payment_row_name, customer) {
- // Get allocation data from server
- frappe.call({
- method: 'taxes_az.taxes_az.doctype.vat_allocation.vat_allocation.get_allocation_data',
- args: {
- vat_allocation_name: frm.doc.name,
- payment_entry_row_name: payment_row_name,
- customer: customer
- },
- callback: function(r) {
- if (!r.message) {
- frappe.msgprint(__('No data available for allocation'));
- return;
- }
-
- let data = r.message;
- let vat_free_items = data.vat_free_items;
- let vat_items = data.vat_items;
-
- // Check if there are items to allocate to
- if (vat_free_items.length === 0 && vat_items.length === 0) {
- frappe.msgprint(__('No sales invoice items available for this customer'));
- return;
- }
-
- // Create allocation dialog
- create_allocation_dialog(frm, payment_row_name, data, vat_free_items, vat_items);
+// Show allocation dialog - работает с несохраненными данными
+function show_allocation_dialog(frm, payment_row_idx, customer) {
+ // Получаем данные напрямую из формы (не из БД!)
+ let payment_row = null;
+ for (let row of frm.doc.payment_entry_table || []) {
+ if (row.idx === payment_row_idx) {
+ payment_row = row;
+ break;
}
- });
+ }
+
+ if (!payment_row) {
+ frappe.msgprint(__('Payment entry row not found'));
+ return;
+ }
+
+ // Получаем sales invoice items для этого клиента из формы
+ let vat_free_items = [];
+ let vat_items = [];
+
+ for (let item of frm.doc.sales_invoice_table || []) {
+ if (item.customer !== customer) {
+ continue;
+ }
+
+ // Получаем существующие аллокации для этого item от текущего payment row
+ let current_allocation = 0;
+ for (let link of frm.doc.allocation_links || []) {
+ if (link.payment_entry_row === payment_row.name &&
+ link.sales_invoice_row === item.name) {
+ current_allocation = link.allocated_amount || 0;
+ break;
+ }
+ }
+
+ let item_data = {
+ name: item.name,
+ item_tax_template: item.item_tax_template || '',
+ tax_article: item.tax_article || '',
+ amount: item.amount || 0,
+ unallocated_amount: item.unallocated_amount || 0,
+ allocated_amount: item.allocated_amount || 0,
+ current_allocation: current_allocation
+ };
+
+ let template = (item.item_tax_template || '').toLowerCase();
+
+ // ИСПРАВЛЕНО: используем .includes() вместо 'in'
+ if (template.includes('ədv 0%') || template.includes('ədv-dən azadolma')) {
+ vat_free_items.push(item_data);
+ } else if (template.includes('ədv 18%') || template.includes('ədv daxil 18%')) {
+ vat_items.push(item_data);
+ }
+ }
+
+ // Check if there are items to allocate to
+ if (vat_free_items.length === 0 && vat_items.length === 0) {
+ frappe.msgprint(__('No sales invoice items available for this customer'));
+ return;
+ }
+
+ // Вычисляем available amounts из текущих данных формы
+ let vat_free_amount = payment_row.vat_free_amount || 0;
+ let vat_free_allocated = payment_row.vat_free_allocated || 0;
+ let vat_allocated_amount = payment_row.vat_allocated_amount || 0;
+ let vat_allocated_allocated = payment_row.vat_allocated_allocated || 0;
+
+ let data = {
+ payment_row: payment_row,
+ vat_free_available: vat_free_amount - vat_free_allocated,
+ vat_allocated_available: vat_allocated_amount - vat_allocated_allocated,
+ vat_free_items: vat_free_items,
+ vat_items: vat_items
+ };
+
+ // Create allocation dialog
+ create_allocation_dialog(frm, payment_row_idx, data, vat_free_items, vat_items);
}
-function create_allocation_dialog(frm, payment_row_name, data, vat_free_items, vat_items) {
+function create_allocation_dialog(frm, payment_row_idx, data, vat_free_items, vat_items) {
let allocation_data = {};
let dialog = new frappe.ui.Dialog({
@@ -425,11 +612,11 @@ function create_allocation_dialog(frm, payment_row_name, data, vat_free_items, v
],
primary_action_label: __('Save Allocation'),
primary_action: function() {
- save_allocation_data(frm, payment_row_name, allocation_data, dialog);
+ save_allocation_data(frm, payment_row_idx, allocation_data, dialog);
},
secondary_action_label: __('Auto Allocate'),
secondary_action: function() {
- auto_allocate(frm, payment_row_name, data, vat_free_items, vat_items, allocation_data, dialog);
+ auto_allocate(frm, payment_row_idx, data, vat_free_items, vat_items, allocation_data, dialog);
}
});
@@ -605,7 +792,7 @@ function setup_allocation_inputs(dialog, allocation_data, max_vat_free, max_vat_
});
}
-function auto_allocate(frm, payment_row_name, data, vat_free_items, vat_items, allocation_data, dialog) {
+function auto_allocate(frm, payment_row_idx, data, vat_free_items, vat_items, allocation_data, dialog) {
// Call server method to calculate auto allocation
frappe.call({
method: 'taxes_az.taxes_az.doctype.vat_allocation.vat_allocation.calculate_auto_allocation',
@@ -646,7 +833,8 @@ function auto_allocate(frm, payment_row_name, data, vat_free_items, vat_items, a
});
}
-function save_allocation_data(frm, payment_row_name, allocation_data, dialog) {
+// Сохранение allocation - работает с несохраненным документом
+function save_allocation_data(frm, payment_row_idx, allocation_data, dialog) {
// Prepare allocations array
let allocations = [];
@@ -659,46 +847,153 @@ function save_allocation_data(frm, payment_row_name, allocation_data, dialog) {
});
}
- // Save to server with loading indicator
- frappe.dom.freeze(__('Saving allocation...'));
+ // Найдем payment row по idx
+ let payment_row = null;
+ for (let row of frm.doc.payment_entry_table || []) {
+ if (row.idx === payment_row_idx) {
+ payment_row = row;
+ break;
+ }
+ }
- frappe.call({
- method: 'taxes_az.taxes_az.doctype.vat_allocation.vat_allocation.save_allocation',
- args: {
- vat_allocation_name: frm.doc.name,
- payment_entry_row_name: payment_row_name,
- allocations: JSON.stringify(allocations)
- },
- callback: function(r) {
- frappe.dom.unfreeze();
+ if (!payment_row) {
+ frappe.msgprint(__('Payment entry row not found'));
+ return;
+ }
+
+ // Удаляем старые links для этого payment row
+ frm.doc.allocation_links = (frm.doc.allocation_links || []).filter(function(link) {
+ return link.payment_entry_row !== payment_row.name;
+ });
+
+ // Добавляем новые links
+ let total_vat_free = 0;
+ let total_vat_allocated = 0;
+
+ allocations.forEach(function(alloc) {
+ if (alloc.allocated > 0) {
+ let link = frm.add_child('allocation_links');
+ link.payment_entry_row = payment_row.name;
+ link.sales_invoice_row = alloc.name;
+ link.allocated_amount = alloc.allocated;
+ link.allocation_type = alloc.type;
- if (r.message && r.message.success) {
- frappe.show_alert({
- message: r.message.message + '
Total: ' + format_currency(r.message.total_allocated) +
- '
VAT Free: ' + format_currency(r.message.vat_free_spent) +
- '
VAT 18%: ' + format_currency(r.message.vat_allocated_spent),
- indicator: 'green'
- }, 7);
-
- dialog.hide();
- frm.reload_doc();
- } else if (r.message && !r.message.success) {
- frappe.msgprint({
- title: __('Error'),
- message: r.message.message,
- indicator: 'red'
- });
+ if (alloc.type === 'vat_free') {
+ total_vat_free += alloc.allocated;
+ } else {
+ total_vat_allocated += alloc.allocated;
}
- },
- error: function(r) {
- frappe.dom.unfreeze();
- frappe.msgprint({
- title: __('Error'),
- message: __('Failed to save allocation. Please try again.'),
- indicator: 'red'
- });
}
});
+
+ // Пересчитываем totals вручную (так как validate еще не вызван)
+ recalculate_totals_manually(frm);
+
+ frm.refresh_field('allocation_links');
+ frm.refresh_field('payment_entry_table');
+ frm.refresh_field('sales_invoice_table');
+
+ // Помечаем документ как измененный
+ frm.dirty();
+
+ dialog.hide();
+}
+
+// Функция для пересчета totals без сохранения в БД
+function recalculate_totals_manually(frm) {
+ // Reset all allocated amounts
+ for (let pe_row of frm.doc.payment_entry_table || []) {
+ pe_row.vat_free_allocated = 0;
+ pe_row.vat_allocated_allocated = 0;
+ pe_row.allocated_amount = 0;
+ }
+
+ for (let si_row of frm.doc.sales_invoice_table || []) {
+ si_row.allocated_amount = 0;
+ }
+
+ // Calculate from links
+ for (let link of frm.doc.allocation_links || []) {
+ let allocated = link.allocated_amount || 0;
+ if (allocated <= 0) continue;
+
+ // Update payment entry row
+ for (let pe_row of frm.doc.payment_entry_table || []) {
+ if (pe_row.name === link.payment_entry_row) {
+ pe_row.allocated_amount = (pe_row.allocated_amount || 0) + allocated;
+
+ if (link.allocation_type === 'vat_free') {
+ pe_row.vat_free_allocated = (pe_row.vat_free_allocated || 0) + allocated;
+ } else {
+ pe_row.vat_allocated_allocated = (pe_row.vat_allocated_allocated || 0) + allocated;
+ }
+ break;
+ }
+ }
+
+ // Update sales invoice row
+ for (let si_row of frm.doc.sales_invoice_table || []) {
+ if (si_row.name === link.sales_invoice_row) {
+ si_row.allocated_amount = (si_row.allocated_amount || 0) + allocated;
+ break;
+ }
+ }
+ }
+
+ // Calculate unallocated amounts
+ for (let pe_row of frm.doc.payment_entry_table || []) {
+ let total = pe_row.payment_entry_amount || 0;
+ let allocated = pe_row.allocated_amount || 0;
+ pe_row.unallocated_amount = Math.max(0, total - allocated);
+ }
+
+ for (let si_row of frm.doc.sales_invoice_table || []) {
+ let total = si_row.amount || 0;
+ let allocated = si_row.allocated_amount || 0;
+ si_row.unallocated_amount = Math.max(0, total - allocated);
+ }
+
+ // Calculate tax template totals
+ calculate_tax_template_totals(frm);
+}
+
+// Новая функция для расчета итогов по tax templates
+function calculate_tax_template_totals(frm) {
+ // Reset totals
+ frm.doc.total_edv_daxil_18 = 0;
+ frm.doc.total_edv_azadolma = 0;
+ frm.doc.total_edv_0 = 0;
+ frm.doc.total_edv_18 = 0;
+ frm.doc.total_allocated_sum = 0;
+
+ // Calculate totals from sales invoice table
+ for (let si_row of frm.doc.sales_invoice_table || []) {
+ let allocated = si_row.allocated_amount || 0;
+ if (allocated <= 0) continue;
+
+ let template = (si_row.item_tax_template || '').trim();
+
+ // Match by template name
+ if (template === 'ƏDV daxil 18%') {
+ frm.doc.total_edv_daxil_18 += allocated;
+ } else if (template === 'ƏDV-dən azadolma') {
+ frm.doc.total_edv_azadolma += allocated;
+ } else if (template === 'ƏDV 0%') {
+ frm.doc.total_edv_0 += allocated;
+ } else if (template === 'ƏDV 18%') {
+ frm.doc.total_edv_18 += allocated;
+ }
+
+ // Add to total sum
+ frm.doc.total_allocated_sum += allocated;
+ }
+
+ // Refresh the total fields
+ frm.refresh_field('total_edv_daxil_18');
+ frm.refresh_field('total_edv_azadolma');
+ frm.refresh_field('total_edv_0');
+ frm.refresh_field('total_edv_18');
+ frm.refresh_field('total_allocated_sum');
}
function format_currency(value) {
diff --git a/taxes_az/taxes_az/doctype/vat_allocation/vat_allocation.json b/taxes_az/taxes_az/doctype/vat_allocation/vat_allocation.json
index 824c528..f7f35cc 100644
--- a/taxes_az/taxes_az/doctype/vat_allocation/vat_allocation.json
+++ b/taxes_az/taxes_az/doctype/vat_allocation/vat_allocation.json
@@ -1,6 +1,6 @@
{
"actions": [],
- "allow_rename": 1,
+ "autoname": "format:VAT-{year}-{month}",
"creation": "2025-09-29 19:03:22.767217",
"doctype": "DocType",
"engine": "InnoDB",
@@ -14,7 +14,18 @@
"sales_invoice_section",
"sales_invoice_table",
"allocation_links_section",
- "allocation_links"
+ "allocation_links",
+ "total_edv_18",
+ "total_section_break",
+ "total_allocated_sum",
+ "totals_section",
+ "total_edv_daxil_18",
+ "column_break_totals_1",
+ "total_edv_azadolma",
+ "column_break_totals_2",
+ "total_edv_0",
+ "column_break_totals_3",
+ "amended_from"
],
"fields": [
{
@@ -65,17 +76,86 @@
{
"fieldname": "allocation_links",
"fieldtype": "Table",
- "hidden": 1,
"label": "Allocation Links",
- "options": "VAT Allocation Link"
+ "options": "VAT Allocation Link",
+ "read_only": 1
+ },
+ {
+ "fieldname": "totals_section",
+ "fieldtype": "Section Break",
+ "label": "Sales Invoice Totals by Tax Template"
+ },
+ {
+ "fieldname": "total_edv_daxil_18",
+ "fieldtype": "Currency",
+ "label": "\u018fDV daxil 18%",
+ "precision": "2",
+ "read_only": 1
+ },
+ {
+ "fieldname": "column_break_totals_1",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "total_edv_azadolma",
+ "fieldtype": "Currency",
+ "label": "\u018fDV-d\u0259n azadolma",
+ "precision": "2",
+ "read_only": 1
+ },
+ {
+ "fieldname": "column_break_totals_2",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "total_edv_0",
+ "fieldtype": "Currency",
+ "label": "\u018fDV 0%",
+ "precision": "2",
+ "read_only": 1
+ },
+ {
+ "fieldname": "column_break_totals_3",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "total_edv_18",
+ "fieldtype": "Currency",
+ "label": "\u018fDV 18%",
+ "precision": "2",
+ "read_only": 1
+ },
+ {
+ "fieldname": "total_section_break",
+ "fieldtype": "Section Break"
+ },
+ {
+ "bold": 1,
+ "fieldname": "total_allocated_sum",
+ "fieldtype": "Currency",
+ "label": "Total Allocated Amount",
+ "precision": "2",
+ "read_only": 1
+ },
+ {
+ "fieldname": "amended_from",
+ "fieldtype": "Link",
+ "label": "Amended From",
+ "no_copy": 1,
+ "options": "VAT allocation",
+ "print_hide": 1,
+ "read_only": 1,
+ "search_index": 1
}
],
"index_web_pages_for_search": 1,
+ "is_submittable": 1,
"links": [],
- "modified": "2025-10-01 21:26:07.923819",
+ "modified": "2025-10-06 16:52:07.347647",
"modified_by": "Administrator",
"module": "Taxes Az",
"name": "VAT allocation",
+ "naming_rule": "Expression",
"owner": "Administrator",
"permissions": [
{
diff --git a/taxes_az/taxes_az/doctype/vat_allocation/vat_allocation.py b/taxes_az/taxes_az/doctype/vat_allocation/vat_allocation.py
index 6a8faac..67ad2c3 100644
--- a/taxes_az/taxes_az/doctype/vat_allocation/vat_allocation.py
+++ b/taxes_az/taxes_az/doctype/vat_allocation/vat_allocation.py
@@ -12,7 +12,7 @@ class VATallocation(Document):
# Check if document with same year and month already exists
self.validate_unique_year_month()
# Recalculate all totals from links
- self.recalculate_totals_from_links()
+ #self.recalculate_totals_from_links()
# Validate all allocations
self.validate_allocations()
@@ -701,4 +701,90 @@ def calculate_auto_allocation(vat_free_available, vat_allocated_available, vat_f
})
remaining -= allocated
- return allocations
\ No newline at end of file
+ return allocations
+
+def recalculate_totals_from_links(self):
+ """
+ Recalculate all allocated/unallocated amounts from allocation links
+ """
+ # Reset all allocated amounts
+ for pe_row in self.payment_entry_table:
+ pe_row.vat_free_allocated = 0
+ pe_row.vat_allocated_allocated = 0
+ pe_row.allocated_amount = 0
+
+ for si_row in self.sales_invoice_table:
+ si_row.allocated_amount = 0
+
+ # Calculate from links
+ for link in self.allocation_links:
+ allocated = Decimal(str(link.allocated_amount or 0))
+ if allocated <= 0:
+ continue
+
+ # Update payment entry row
+ for pe_row in self.payment_entry_table:
+ if pe_row.name == link.payment_entry_row:
+ current_allocated = Decimal(str(pe_row.allocated_amount or 0))
+ pe_row.allocated_amount = float(current_allocated + allocated)
+
+ if link.allocation_type == 'vat_free':
+ current_vat_free = Decimal(str(pe_row.vat_free_allocated or 0))
+ pe_row.vat_free_allocated = float(current_vat_free + allocated)
+ else:
+ current_vat_allocated = Decimal(str(pe_row.vat_allocated_allocated or 0))
+ pe_row.vat_allocated_allocated = float(current_vat_allocated + allocated)
+ break
+
+ # Update sales invoice row
+ for si_row in self.sales_invoice_table:
+ if si_row.name == link.sales_invoice_row:
+ current_allocated = Decimal(str(si_row.allocated_amount or 0))
+ si_row.allocated_amount = float(current_allocated + allocated)
+ break
+
+ # Calculate unallocated amounts
+ for pe_row in self.payment_entry_table:
+ total = Decimal(str(pe_row.payment_entry_amount or 0))
+ allocated = Decimal(str(pe_row.allocated_amount or 0))
+ pe_row.unallocated_amount = float(max(Decimal('0'), total - allocated))
+
+ for si_row in self.sales_invoice_table:
+ total = Decimal(str(si_row.amount or 0))
+ allocated = Decimal(str(si_row.allocated_amount or 0))
+ si_row.unallocated_amount = float(max(Decimal('0'), total - allocated))
+
+ # Calculate totals by tax template
+ self.calculate_tax_template_totals()
+
+def calculate_tax_template_totals(self):
+ """
+ Calculate totals by tax template from sales invoice table
+ """
+ # Reset totals
+ self.total_edv_daxil_18 = 0
+ self.total_edv_azadolma = 0
+ self.total_edv_0 = 0
+ self.total_edv_18 = 0
+ self.total_allocated_sum = 0
+
+ # Calculate totals
+ for si_row in self.sales_invoice_table:
+ allocated = Decimal(str(si_row.allocated_amount or 0))
+ if allocated <= 0:
+ continue
+
+ template = (si_row.item_tax_template or '').strip()
+
+ # Match by template name
+ if template == 'ƏDV daxil 18%':
+ self.total_edv_daxil_18 = float(Decimal(str(self.total_edv_daxil_18 or 0)) + allocated)
+ elif template == 'ƏDV-dən azadolma':
+ self.total_edv_azadolma = float(Decimal(str(self.total_edv_azadolma or 0)) + allocated)
+ elif template == 'ƏDV 0%':
+ self.total_edv_0 = float(Decimal(str(self.total_edv_0 or 0)) + allocated)
+ elif template == 'ƏDV 18%':
+ self.total_edv_18 = float(Decimal(str(self.total_edv_18 or 0)) + allocated)
+
+ # Add to total sum
+ self.total_allocated_sum = float(Decimal(str(self.total_allocated_sum or 0)) + allocated)
\ No newline at end of file
diff --git a/taxes_az/taxes_az/doctype/vat_allocation_link/vat_allocation_link.json b/taxes_az/taxes_az/doctype/vat_allocation_link/vat_allocation_link.json
index d90e056..5e8497f 100644
--- a/taxes_az/taxes_az/doctype/vat_allocation_link/vat_allocation_link.json
+++ b/taxes_az/taxes_az/doctype/vat_allocation_link/vat_allocation_link.json
@@ -1,7 +1,7 @@
{
"actions": [],
"allow_rename": 1,
- "creation": "2025-10-01 20:00:00.000000",
+ "creation": "2025-10-01 20:00:00",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
@@ -31,20 +31,22 @@
"fieldtype": "Currency",
"in_list_view": 1,
"label": "Allocated Amount",
- "precision": "2"
+ "precision": "2",
+ "read_only": 1
},
{
"fieldname": "allocation_type",
"fieldtype": "Select",
"in_list_view": 1,
"label": "Type",
- "options": "vat_free\nvat_allocated"
+ "options": "vat_free\nvat_allocated",
+ "read_only": 1
}
],
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
- "modified": "2025-10-01 20:00:00.000000",
+ "modified": "2025-10-03 18:42:25.411199",
"modified_by": "Administrator",
"module": "Taxes Az",
"name": "VAT Allocation Link",
diff --git a/taxes_az/taxes_az/doctype/vat_allocation_payment_entry/vat_allocation_payment_entry.json b/taxes_az/taxes_az/doctype/vat_allocation_payment_entry/vat_allocation_payment_entry.json
index 3a721ba..7974bdb 100644
--- a/taxes_az/taxes_az/doctype/vat_allocation_payment_entry/vat_allocation_payment_entry.json
+++ b/taxes_az/taxes_az/doctype/vat_allocation_payment_entry/vat_allocation_payment_entry.json
@@ -39,14 +39,16 @@
"fieldtype": "Currency",
"in_list_view": 1,
"label": "Payment Entry Amount",
- "precision": "2"
+ "precision": "2",
+ "read_only": 1
},
{
"fieldname": "vat_amount",
"fieldtype": "Currency",
"in_list_view": 1,
"label": "VAT Amount",
- "precision": "2"
+ "precision": "2",
+ "read_only": 1
},
{
"fieldname": "vat_journal_entry",
@@ -91,14 +93,16 @@
"fieldtype": "Currency",
"in_list_view": 1,
"label": "Allocated amount",
- "precision": "2"
+ "precision": "2",
+ "read_only": 1
},
{
"fieldname": "unallocated_amount",
"fieldtype": "Currency",
"in_list_view": 1,
"label": "Unallocated amount",
- "precision": "2"
+ "precision": "2",
+ "read_only": 1
},
{
"fieldname": "allocate_button",
@@ -110,7 +114,7 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
- "modified": "2025-10-01 19:51:09.389265",
+ "modified": "2025-10-03 17:09:35.593479",
"modified_by": "Administrator",
"module": "Taxes Az",
"name": "VAT allocation payment entry",
diff --git a/taxes_az/taxes_az/doctype/vat_allocation_sales_invoice/vat_allocation_sales_invoice.json b/taxes_az/taxes_az/doctype/vat_allocation_sales_invoice/vat_allocation_sales_invoice.json
index 5d07bb8..b150c8c 100644
--- a/taxes_az/taxes_az/doctype/vat_allocation_sales_invoice/vat_allocation_sales_invoice.json
+++ b/taxes_az/taxes_az/doctype/vat_allocation_sales_invoice/vat_allocation_sales_invoice.json
@@ -20,11 +20,13 @@
"fieldtype": "Link",
"in_list_view": 1,
"label": "Customer",
- "options": "Customer"
+ "options": "Customer",
+ "read_only": 1
},
{
"fieldname": "payment_entry",
"fieldtype": "Link",
+ "hidden": 1,
"in_list_view": 1,
"label": "Payment Entry",
"options": "Payment Entry"
@@ -34,14 +36,16 @@
"fieldtype": "Currency",
"in_list_view": 1,
"label": "Allocated amount",
- "precision": "2"
+ "precision": "2",
+ "read_only": 1
},
{
"fieldname": "unallocated_amount",
"fieldtype": "Currency",
"in_list_view": 1,
"label": "Unallocated amount",
- "precision": "2"
+ "precision": "2",
+ "read_only": 1
},
{
"fieldname": "item_tax_template",
@@ -49,7 +53,8 @@
"in_list_view": 1,
"label": "Item tax template",
"options": "Item Tax Template",
- "precision": "2"
+ "precision": "2",
+ "read_only": 1
},
{
"fieldname": "tax_article",
@@ -57,20 +62,22 @@
"in_list_view": 1,
"label": "Tax Article",
"options": "Tax Article",
- "precision": "2"
+ "precision": "2",
+ "read_only": 1
},
{
"fieldname": "amount",
"fieldtype": "Currency",
"in_list_view": 1,
"label": "Amount",
- "precision": "2"
+ "precision": "2",
+ "read_only": 1
}
],
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
- "modified": "2025-09-29 20:36:21.708944",
+ "modified": "2025-10-03 17:19:43.856209",
"modified_by": "Administrator",
"module": "Taxes Az",
"name": "VAT allocation sales invoice",
diff --git a/taxes_az/taxes_az/report/customer_outstanding_balance/customer_outstanding_balance.js b/taxes_az/taxes_az/report/customer_outstanding_balance/customer_outstanding_balance.js
index 26beec2..ad67c2c 100644
--- a/taxes_az/taxes_az/report/customer_outstanding_balance/customer_outstanding_balance.js
+++ b/taxes_az/taxes_az/report/customer_outstanding_balance/customer_outstanding_balance.js
@@ -64,6 +64,23 @@ frappe.query_reports["Customer Outstanding Balance"] = {
value = "" + value + "";
}
+ // Highlight VAT allocation columns
+ if (column.fieldname == "edv_daxil_18" && data && data.edv_daxil_18 > 0) {
+ value = "" + value + "";
+ }
+
+ if (column.fieldname == "edv_azadolma" && data && data.edv_azadolma > 0) {
+ value = "" + value + "";
+ }
+
+ if (column.fieldname == "edv_0" && data && data.edv_0 > 0) {
+ value = "" + value + "";
+ }
+
+ if (column.fieldname == "edv_18" && data && data.edv_18 > 0) {
+ value = "" + value + "";
+ }
+
return value;
}
};
\ No newline at end of file
diff --git a/taxes_az/taxes_az/report/customer_outstanding_balance/customer_outstanding_balance.py b/taxes_az/taxes_az/report/customer_outstanding_balance/customer_outstanding_balance.py
index 97a7ef9..27244a0 100644
--- a/taxes_az/taxes_az/report/customer_outstanding_balance/customer_outstanding_balance.py
+++ b/taxes_az/taxes_az/report/customer_outstanding_balance/customer_outstanding_balance.py
@@ -3,6 +3,7 @@
import frappe
from frappe import _
+from datetime import datetime
def execute(filters=None):
"""Main function for report execution"""
@@ -50,6 +51,30 @@ def get_columns():
"fieldname": "closing_balance",
"fieldtype": "Currency",
"width": 140
+ },
+ {
+ "label": _("ƏDV daxil 18%"),
+ "fieldname": "edv_daxil_18",
+ "fieldtype": "Currency",
+ "width": 140
+ },
+ {
+ "label": _("ƏDV-dən azadolma"),
+ "fieldname": "edv_azadolma",
+ "fieldtype": "Currency",
+ "width": 140
+ },
+ {
+ "label": _("ƏDV 0%"),
+ "fieldname": "edv_0",
+ "fieldtype": "Currency",
+ "width": 140
+ },
+ {
+ "label": _("ƏDV 18%"),
+ "fieldname": "edv_18",
+ "fieldtype": "Currency",
+ "width": 140
}
]
@@ -120,8 +145,11 @@ def get_data(filters):
ABS(closing_balance) >= 0.01
""", filters, as_dict=1)
+ # Get VAT allocation data
+ vat_data = get_vat_allocation_data(filters)
+
# Combine all data
- result = combine_data(opening_balance_data, period_data, closing_balance_data)
+ result = combine_data(opening_balance_data, period_data, closing_balance_data, vat_data)
# Get customer names and other details
add_customer_details(result)
@@ -141,7 +169,64 @@ def get_conditions(filters):
return conditions
-def combine_data(opening_data, period_data, closing_data):
+def get_vat_allocation_data(filters):
+ """Get VAT allocation data for customers in the period"""
+ if not filters.get("from_date") or not filters.get("to_date"):
+ return []
+
+ # Determine year and month from the date range
+ from_date = datetime.strptime(str(filters.get("from_date")), "%Y-%m-%d")
+ to_date = datetime.strptime(str(filters.get("to_date")), "%Y-%m-%d")
+
+ # Get month name mapping
+ month_names = [
+ "January", "February", "March", "April", "May", "June",
+ "July", "August", "September", "October", "November", "December"
+ ]
+
+ # Use the to_date for determining the period
+ year = to_date.year
+ month = month_names[to_date.month - 1]
+
+ # Query VAT allocation sales invoice data from submitted documents
+ vat_query = """
+ SELECT
+ vasi.customer,
+ vasi.item_tax_template,
+ SUM(vasi.allocated_amount) as allocated_amount
+ FROM
+ `tabVAT allocation sales invoice` vasi
+ INNER JOIN
+ `tabVAT allocation` va ON va.name = vasi.parent
+ WHERE
+ va.docstatus = 1
+ AND va.year = %(year)s
+ AND va.month = %(month)s
+ """
+
+ # Add customer filter if specified
+ customer_condition = ""
+ if filters.get("customer"):
+ customer_list = "', '".join(filters.get("customer"))
+ customer_condition = f" AND vasi.customer IN ('{customer_list}')"
+
+ if filters.get("customer_group"):
+ customer_condition += " AND vasi.customer IN (SELECT name FROM `tabCustomer` WHERE customer_group = %(customer_group)s)"
+
+ vat_query += customer_condition + """
+ GROUP BY
+ vasi.customer, vasi.item_tax_template
+ """
+
+ vat_data = frappe.db.sql(vat_query, {
+ "year": year,
+ "month": month,
+ "customer_group": filters.get("customer_group")
+ }, as_dict=1)
+
+ return vat_data
+
+def combine_data(opening_data, period_data, closing_data, vat_data):
"""Combine all datasets into final result"""
result_dict = {}
@@ -153,7 +238,11 @@ def combine_data(opening_data, period_data, closing_data):
'opening_balance': row['opening_balance'] or 0.0,
'payments': 0.0,
'new_invoices': 0.0,
- 'closing_balance': 0.0
+ 'closing_balance': 0.0,
+ 'edv_daxil_18': 0.0,
+ 'edv_azadolma': 0.0,
+ 'edv_0': 0.0,
+ 'edv_18': 0.0
}
# Add period movements
@@ -165,7 +254,11 @@ def combine_data(opening_data, period_data, closing_data):
'opening_balance': 0.0,
'payments': 0.0,
'new_invoices': 0.0,
- 'closing_balance': 0.0
+ 'closing_balance': 0.0,
+ 'edv_daxil_18': 0.0,
+ 'edv_azadolma': 0.0,
+ 'edv_0': 0.0,
+ 'edv_18': 0.0
}
result_dict[customer]['payments'] = row['payments'] or 0.0
@@ -180,11 +273,44 @@ def combine_data(opening_data, period_data, closing_data):
'opening_balance': 0.0,
'payments': 0.0,
'new_invoices': 0.0,
- 'closing_balance': 0.0
+ 'closing_balance': 0.0,
+ 'edv_daxil_18': 0.0,
+ 'edv_azadolma': 0.0,
+ 'edv_0': 0.0,
+ 'edv_18': 0.0
}
result_dict[customer]['closing_balance'] = row['closing_balance'] or 0.0
+ # Add VAT allocation data
+ for row in vat_data:
+ customer = row['customer']
+ if customer not in result_dict:
+ result_dict[customer] = {
+ 'customer': customer,
+ 'opening_balance': 0.0,
+ 'payments': 0.0,
+ 'new_invoices': 0.0,
+ 'closing_balance': 0.0,
+ 'edv_daxil_18': 0.0,
+ 'edv_azadolma': 0.0,
+ 'edv_0': 0.0,
+ 'edv_18': 0.0
+ }
+
+ # Map item tax template to field names
+ template = row['item_tax_template']
+ allocated = row['allocated_amount'] or 0.0
+
+ if 'daxil 18' in template or 'daxil18' in template.lower().replace(' ', ''):
+ result_dict[customer]['edv_daxil_18'] += allocated
+ elif 'azadolma' in template.lower():
+ result_dict[customer]['edv_azadolma'] += allocated
+ elif '0%' in template or 'edv 0' in template.lower():
+ result_dict[customer]['edv_0'] += allocated
+ elif '18%' in template or 'edv 18' in template.lower():
+ result_dict[customer]['edv_18'] += allocated
+
# Convert to list and filter out zero balances
result = []
for customer, data in result_dict.items():
@@ -192,7 +318,11 @@ def combine_data(opening_data, period_data, closing_data):
if (abs(data['opening_balance']) >= 0.01 or
abs(data['closing_balance']) >= 0.01 or
abs(data['new_invoices']) >= 0.01 or
- abs(data['payments']) >= 0.01):
+ abs(data['payments']) >= 0.01 or
+ abs(data['edv_daxil_18']) >= 0.01 or
+ abs(data['edv_azadolma']) >= 0.01 or
+ abs(data['edv_0']) >= 0.01 or
+ abs(data['edv_18']) >= 0.01):
result.append(data)
return sorted(result, key=lambda x: x['customer'])