added vat allocation doctype and modifed cob report
This commit is contained in:
parent
f121789432
commit
c22dabf943
|
|
@ -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 + '<br>Total: ' + format_currency(r.message.total_allocated) +
|
||||
'<br>VAT Free: ' + format_currency(r.message.vat_free_spent) +
|
||||
'<br>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) {
|
||||
|
|
|
|||
|
|
@ -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": [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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
|
||||
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)
|
||||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -64,6 +64,23 @@ frappe.query_reports["Customer Outstanding Balance"] = {
|
|||
value = "<span style='color: #d62728; font-weight: bold;'>" + value + "</span>";
|
||||
}
|
||||
|
||||
// Highlight VAT allocation columns
|
||||
if (column.fieldname == "edv_daxil_18" && data && data.edv_daxil_18 > 0) {
|
||||
value = "<span style='color: #17a2b8; font-weight: bold;'>" + value + "</span>";
|
||||
}
|
||||
|
||||
if (column.fieldname == "edv_azadolma" && data && data.edv_azadolma > 0) {
|
||||
value = "<span style='color: #28a745; font-weight: bold;'>" + value + "</span>";
|
||||
}
|
||||
|
||||
if (column.fieldname == "edv_0" && data && data.edv_0 > 0) {
|
||||
value = "<span style='color: #ffc107; font-weight: bold;'>" + value + "</span>";
|
||||
}
|
||||
|
||||
if (column.fieldname == "edv_18" && data && data.edv_18 > 0) {
|
||||
value = "<span style='color: #dc3545; font-weight: bold;'>" + value + "</span>";
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
};
|
||||
|
|
@ -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'])
|
||||
|
|
|
|||
Loading…
Reference in New Issue