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'])