added previous monthes to vat allocation

This commit is contained in:
Ali 2025-10-07 19:02:13 +04:00
parent d61275ca85
commit 6321a10b7c
6 changed files with 827 additions and 67 deletions

View File

@ -14,6 +14,10 @@ frappe.ui.form.on('VAT allocation', {
// Setup filter for Journal Entry // Setup filter for Journal Entry
setup_journal_entry_query(frm); setup_journal_entry_query(frm);
// Setup filter for Payment Entry
setup_payment_entry_query(frm);
} }
}); });
@ -161,11 +165,11 @@ function show_sales_invoice_filters(frm) {
}, 100); }, 100);
} }
// Function to fill Payment Entries with filters // Function to fill Payment Entries with filters - UPDATED to include previous months
function fill_payment_entries(frm, filters) { function fill_payment_entries(frm, filters) {
// Call server method // Call server method to get both current and previous payment entries
frappe.call({ frappe.call({
method: 'taxes_az.taxes_az.doctype.vat_allocation.vat_allocation.get_payment_entries', method: 'taxes_az.taxes_az.doctype.vat_allocation.vat_allocation.get_payment_entries_with_previous',
args: { args: {
year: frm.doc.year, year: frm.doc.year,
month: frm.doc.month, month: frm.doc.month,
@ -184,8 +188,14 @@ function fill_payment_entries(frm, filters) {
row.customer = payment.customer; row.customer = payment.customer;
row.payment_entry = payment.payment_entry; row.payment_entry = payment.payment_entry;
row.payment_entry_amount = payment.payment_entry_amount; row.payment_entry_amount = payment.payment_entry_amount;
row.vat_free_amount = payment.payment_entry_amount; // Default: all amount is VAT free row.vat_journal_entry = payment.vat_journal_entry || '';
row.unallocated_amount = payment.payment_entry_amount; row.vat_amount = payment.vat_amount || 0;
row.vat_allocated_amount = payment.vat_allocated_amount || 0;
row.vat_free_amount = payment.vat_free_amount || payment.payment_entry_amount;
row.unallocated_amount = payment.unallocated_amount || 0;
row.allocated_amount = payment.allocated_amount || 0;
row.vat_allocated_allocated = payment.vat_allocated_allocated || 0;
row.vat_free_allocated = payment.vat_free_allocated || 0;
}); });
frm.refresh_field('payment_entry_table'); frm.refresh_field('payment_entry_table');
@ -197,9 +207,22 @@ function fill_payment_entries(frm, filters) {
}); });
} }
// Function to fill Sales Invoice Items with filters // Setup query filter for Payment Entry - UPDATED
function setup_payment_entry_query(frm) {
frm.fields_dict.payment_entry_table.grid.get_field('payment_entry').get_query = function(doc, cdt, cdn) {
return {
query: 'taxes_az.taxes_az.doctype.vat_allocation.vat_allocation.get_payment_entry_query',
filters: {
year: doc.year,
month: doc.month
}
};
};
}
// Function to fill Sales Invoice Items with filters - UPDATED to fill both tables
function fill_sales_invoice_items(frm, filters) { function fill_sales_invoice_items(frm, filters) {
// Call server method // Call both server methods - current month and previous months
frappe.call({ frappe.call({
method: 'taxes_az.taxes_az.doctype.vat_allocation.vat_allocation.get_sales_invoice_items', method: 'taxes_az.taxes_az.doctype.vat_allocation.vat_allocation.get_sales_invoice_items',
args: { args: {
@ -229,6 +252,52 @@ function fill_sales_invoice_items(frm, filters) {
} else { } else {
frappe.msgprint(__('No Sales Invoice Items found for selected period')); frappe.msgprint(__('No Sales Invoice Items found for selected period'));
} }
// Now load previous months items
load_previous_months_items(frm, filters);
}
});
}
// Function to load previous months items
function load_previous_months_items(frm, filters) {
frappe.call({
method: 'taxes_az.taxes_az.doctype.vat_allocation.vat_allocation.get_sales_invoice_items_previous_months',
args: {
year: frm.doc.year,
month: frm.doc.month,
customers: filters.customers ? JSON.stringify(filters.customers) : null,
min_amount: filters.min_amount || null,
max_amount: filters.max_amount || null
},
callback: function(r) {
// Clear existing table
frm.clear_table('sales_invoice_previous_table');
if (r.message && r.message.length > 0) {
// Add new rows
r.message.forEach(function(item) {
let row = frm.add_child('sales_invoice_previous_table');
row.customer = item.customer;
row.item_tax_template = item.item_tax_template;
row.tax_article = item.tax_article;
row.amount = item.amount;
row.unallocated_amount = item.unallocated_amount;
row.allocated_amount = item.allocated_amount;
});
frm.refresh_field('sales_invoice_previous_table');
frappe.show_alert({
message: __('Previous months items loaded: {0} records', [r.message.length]),
indicator: 'blue'
});
} else {
frm.refresh_field('sales_invoice_previous_table');
frappe.show_alert({
message: __('No unpaid items from previous months'),
indicator: 'blue'
});
}
} }
}); });
} }
@ -279,6 +348,7 @@ frappe.ui.form.on('VAT allocation payment entry', {
recalculate_totals_manually(frm); recalculate_totals_manually(frm);
frm.refresh_field('payment_entry_table'); frm.refresh_field('payment_entry_table');
frm.refresh_field('sales_invoice_table'); frm.refresh_field('sales_invoice_table');
frm.refresh_field('sales_invoice_previous_table');
}, },
// При изменении Payment Entry - обновить суммы // При изменении Payment Entry - обновить суммы
@ -305,6 +375,7 @@ frappe.ui.form.on('VAT allocation payment entry', {
recalculate_totals_manually(frm); recalculate_totals_manually(frm);
frm.refresh_field('payment_entry_table'); frm.refresh_field('payment_entry_table');
frm.refresh_field('sales_invoice_table'); frm.refresh_field('sales_invoice_table');
frm.refresh_field('sales_invoice_previous_table');
return; return;
} }
@ -365,6 +436,7 @@ frappe.ui.form.on('VAT allocation payment entry', {
recalculate_totals_manually(frm); recalculate_totals_manually(frm);
frm.refresh_field('payment_entry_table'); frm.refresh_field('payment_entry_table');
frm.refresh_field('sales_invoice_table'); frm.refresh_field('sales_invoice_table');
frm.refresh_field('sales_invoice_previous_table');
} }
} }
}); });
@ -389,6 +461,7 @@ frappe.ui.form.on('VAT allocation payment entry', {
recalculate_totals_manually(frm); recalculate_totals_manually(frm);
frm.refresh_field('payment_entry_table'); frm.refresh_field('payment_entry_table');
frm.refresh_field('sales_invoice_table'); frm.refresh_field('sales_invoice_table');
frm.refresh_field('sales_invoice_previous_table');
}, },
vat_journal_entry: function(frm, cdt, cdn) { vat_journal_entry: function(frm, cdt, cdn) {
@ -501,7 +574,10 @@ function show_allocation_dialog(frm, payment_row_idx, customer) {
// Получаем sales invoice items для этого клиента из формы // Получаем sales invoice items для этого клиента из формы
let vat_free_items = []; let vat_free_items = [];
let vat_items = []; let vat_items = [];
let vat_free_items_previous = [];
let vat_items_previous = [];
// Current month items
for (let item of frm.doc.sales_invoice_table || []) { for (let item of frm.doc.sales_invoice_table || []) {
if (item.customer !== customer) { if (item.customer !== customer) {
continue; continue;
@ -529,7 +605,6 @@ function show_allocation_dialog(frm, payment_row_idx, customer) {
let template = (item.item_tax_template || '').toLowerCase(); let template = (item.item_tax_template || '').toLowerCase();
// ИСПРАВЛЕНО: используем .includes() вместо 'in'
if (template.includes('ədv 0%') || template.includes('ədv-dən azadolma')) { if (template.includes('ədv 0%') || template.includes('ədv-dən azadolma')) {
vat_free_items.push(item_data); vat_free_items.push(item_data);
} else if (template.includes('ədv 18%') || template.includes('ədv daxil 18%')) { } else if (template.includes('ədv 18%') || template.includes('ədv daxil 18%')) {
@ -537,8 +612,44 @@ function show_allocation_dialog(frm, payment_row_idx, customer) {
} }
} }
// Previous months items
for (let item of frm.doc.sales_invoice_previous_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();
if (template.includes('ədv 0%') || template.includes('ədv-dən azadolma')) {
vat_free_items_previous.push(item_data);
} else if (template.includes('ədv 18%') || template.includes('ədv daxil 18%')) {
vat_items_previous.push(item_data);
}
}
// Check if there are items to allocate to // Check if there are items to allocate to
if (vat_free_items.length === 0 && vat_items.length === 0) { if (vat_free_items.length === 0 && vat_items.length === 0 &&
vat_free_items_previous.length === 0 && vat_items_previous.length === 0) {
frappe.msgprint(__('No sales invoice items available for this customer')); frappe.msgprint(__('No sales invoice items available for this customer'));
return; return;
} }
@ -554,19 +665,22 @@ function show_allocation_dialog(frm, payment_row_idx, customer) {
vat_free_available: vat_free_amount - vat_free_allocated, vat_free_available: vat_free_amount - vat_free_allocated,
vat_allocated_available: vat_allocated_amount - vat_allocated_allocated, vat_allocated_available: vat_allocated_amount - vat_allocated_allocated,
vat_free_items: vat_free_items, vat_free_items: vat_free_items,
vat_items: vat_items vat_items: vat_items,
vat_free_items_previous: vat_free_items_previous,
vat_items_previous: vat_items_previous
}; };
// Create allocation dialog // Create allocation dialog
create_allocation_dialog(frm, payment_row_idx, data, vat_free_items, vat_items); create_allocation_dialog(frm, payment_row_idx, data);
} }
function create_allocation_dialog(frm, payment_row_idx, data, vat_free_items, vat_items) { function create_allocation_dialog(frm, payment_row_idx, data) {
let allocation_data = {}; let allocation_data_current = {};
let allocation_data_previous = {};
let dialog = new frappe.ui.Dialog({ let dialog = new frappe.ui.Dialog({
title: __('Allocate Amount'), title: __('Allocate Amount'),
size: 'large', size: 'extra-large',
fields: [ fields: [
{ {
fieldname: 'info_section', fieldname: 'info_section',
@ -601,22 +715,39 @@ function create_allocation_dialog(frm, payment_row_idx, data, vat_free_items, va
default: data.vat_allocated_available default: data.vat_allocated_available
}, },
{ {
fieldname: 'items_section', fieldname: 'tabs_section',
fieldtype: 'Section Break', fieldtype: 'Section Break'
label: __('Sales Invoice Items - Edit Allocations')
}, },
{ {
fieldname: 'allocation_html', fieldname: 'tabs_html',
fieldtype: 'HTML' fieldtype: 'HTML'
} }
], ],
primary_action_label: __('Save Allocation'), primary_action_label: __('Save Allocation'),
primary_action: function() { primary_action: function() {
save_allocation_data(frm, payment_row_idx, allocation_data, dialog); save_allocation_data(frm, payment_row_idx, allocation_data_current, allocation_data_previous, dialog);
}, },
secondary_action_label: __('Auto Allocate'), secondary_action_label: __('Auto Allocate'),
secondary_action: function() { secondary_action: function() {
auto_allocate(frm, payment_row_idx, data, vat_free_items, vat_items, allocation_data, dialog); // Determine which tab is active
let active_tab = dialog.$wrapper.find('.nav-tabs .nav-link.active').data('tab');
// Calculate remaining available amounts
let available_amounts = calculate_available_amounts(
data,
allocation_data_current,
allocation_data_previous
);
if (active_tab === 'current') {
auto_allocate(frm, payment_row_idx, data, data.vat_free_items, data.vat_items,
allocation_data_current, dialog, 'current', available_amounts,
allocation_data_previous);
} else {
auto_allocate(frm, payment_row_idx, data, data.vat_free_items_previous, data.vat_items_previous,
allocation_data_previous, dialog, 'previous', available_amounts,
allocation_data_current);
}
} }
}); });
@ -637,17 +768,121 @@ function create_allocation_dialog(frm, payment_row_idx, data, vat_free_items, va
`; `;
dialog.fields_dict.payment_info_html.$wrapper.html(info_html); dialog.fields_dict.payment_info_html.$wrapper.html(info_html);
// Build allocation HTML // Build tabs HTML with Bootstrap
let html = build_allocation_html(vat_free_items, vat_items, allocation_data); let html_current = build_allocation_html(data.vat_free_items, data.vat_items, allocation_data_current, 'current');
dialog.fields_dict.allocation_html.$wrapper.html(html); let html_previous = build_allocation_html(data.vat_free_items_previous, data.vat_items_previous, allocation_data_previous, 'previous');
// Setup input handlers let tabs_html = `
setup_allocation_inputs(dialog, allocation_data, data.vat_free_available, data.vat_allocated_available); <style>
.allocation-tabs .nav-link {
color: #6c757d;
font-weight: 400;
border: none;
border-bottom: 3px solid transparent;
padding: 10px 20px;
cursor: pointer;
transition: all 0.2s ease;
}
.allocation-tabs .nav-link:hover {
color: #495057;
background-color: #f8f9fa;
border-bottom-color: #dee2e6;
}
.allocation-tabs .nav-link.active {
color: #1f272e;
font-weight: 600;
border-bottom-color: #2490ef;
background-color: transparent;
}
.allocation-tabs .nav-tabs {
border-bottom: 1px solid #dee2e6;
}
</style>
<ul class="nav nav-tabs allocation-tabs" role="tablist" style="margin-bottom: 15px;">
<li class="nav-item">
<a class="nav-link active" id="tab-link-current" data-tab="current" href="#tab-current" role="tab">
${__('Current Month')}
</a>
</li>
<li class="nav-item">
<a class="nav-link" id="tab-link-previous" data-tab="previous" href="#tab-previous" role="tab">
${__('Previous Months')}
</a>
</li>
</ul>
<div class="tab-content">
<div class="tab-pane fade show active" id="tab-current" role="tabpanel">
${html_current}
</div>
<div class="tab-pane fade" id="tab-previous" role="tabpanel">
${html_previous}
</div>
</div>
`;
dialog.fields_dict.tabs_html.$wrapper.html(tabs_html);
// Setup custom tab switching (without Bootstrap's data-toggle)
dialog.$wrapper.find('.nav-link').on('click', function(e) {
e.preventDefault();
e.stopPropagation();
let $this = $(this);
let target = $this.attr('href');
// Update nav links
dialog.$wrapper.find('.nav-link').removeClass('active');
$this.addClass('active');
// Update tab panes
dialog.$wrapper.find('.tab-pane').removeClass('show active');
dialog.$wrapper.find(target).addClass('show active');
});
// Setup input handlers for both tabs
setup_allocation_inputs(dialog, allocation_data_current, data.vat_free_available, data.vat_allocated_available, 'current');
setup_allocation_inputs(dialog, allocation_data_previous, data.vat_free_available, data.vat_allocated_available, 'previous');
dialog.show(); dialog.show();
} }
function build_allocation_html(vat_free_items, vat_items, allocation_data) { function calculate_available_amounts(data, allocation_data_current, allocation_data_previous) {
let total_vat_free_current = 0;
let total_vat_allocated_current = 0;
let total_vat_free_previous = 0;
let total_vat_allocated_previous = 0;
// Calculate totals from current month
for (let name in allocation_data_current) {
let alloc = allocation_data_current[name];
if (alloc.type === 'vat_free') {
total_vat_free_current += alloc.value || 0;
} else {
total_vat_allocated_current += alloc.value || 0;
}
}
// Calculate totals from previous months
for (let name in allocation_data_previous) {
let alloc = allocation_data_previous[name];
if (alloc.type === 'vat_free') {
total_vat_free_previous += alloc.value || 0;
} else {
total_vat_allocated_previous += alloc.value || 0;
}
}
// Calculate remaining available
let vat_free_available = data.vat_free_available - total_vat_free_current - total_vat_free_previous;
let vat_allocated_available = data.vat_allocated_available - total_vat_allocated_current - total_vat_allocated_previous;
return {
vat_free_available: Math.max(0, vat_free_available),
vat_allocated_available: Math.max(0, vat_allocated_available)
};
}
function build_allocation_html(vat_free_items, vat_items, allocation_data, tab_type) {
let html = '<div class="allocation-container" style="max-height: 400px; overflow-y: auto;">'; let html = '<div class="allocation-container" style="max-height: 400px; overflow-y: auto;">';
// VAT Free items section // VAT Free items section
@ -671,7 +906,7 @@ function build_allocation_html(vat_free_items, vat_items, allocation_data) {
html += '<td>' + format_currency(item.amount) + '</td>'; html += '<td>' + format_currency(item.amount) + '</td>';
html += '<td>' + format_currency(item.unallocated_amount) + '</td>'; html += '<td>' + format_currency(item.unallocated_amount) + '</td>';
html += '<td style="background-color: #fff3cd;">' + format_currency(item.current_allocation || 0) + '</td>'; html += '<td style="background-color: #fff3cd;">' + format_currency(item.current_allocation || 0) + '</td>';
html += '<td><input type="number" class="form-control allocation-input" data-name="' + item.name + '" data-type="vat_free" data-max="' + (item.unallocated_amount + (item.current_allocation || 0)) + '" value="' + (item.current_allocation || 0) + '" min="0" step="0.01"></td>'; html += '<td><input type="number" class="form-control allocation-input-' + tab_type + '" data-name="' + item.name + '" data-type="vat_free" data-max="' + (item.unallocated_amount + (item.current_allocation || 0)) + '" value="' + (item.current_allocation || 0) + '" min="0" step="0.01"></td>';
html += '</tr>'; html += '</tr>';
}); });
@ -699,7 +934,7 @@ function build_allocation_html(vat_free_items, vat_items, allocation_data) {
html += '<td>' + format_currency(item.amount) + '</td>'; html += '<td>' + format_currency(item.amount) + '</td>';
html += '<td>' + format_currency(item.unallocated_amount) + '</td>'; html += '<td>' + format_currency(item.unallocated_amount) + '</td>';
html += '<td style="background-color: #fff3cd;">' + format_currency(item.current_allocation || 0) + '</td>'; html += '<td style="background-color: #fff3cd;">' + format_currency(item.current_allocation || 0) + '</td>';
html += '<td><input type="number" class="form-control allocation-input" data-name="' + item.name + '" data-type="vat_allocated" data-max="' + (item.unallocated_amount + (item.current_allocation || 0)) + '" value="' + (item.current_allocation || 0) + '" min="0" step="0.01"></td>'; html += '<td><input type="number" class="form-control allocation-input-' + tab_type + '" data-name="' + item.name + '" data-type="vat_allocated" data-max="' + (item.unallocated_amount + (item.current_allocation || 0)) + '" value="' + (item.current_allocation || 0) + '" min="0" step="0.01"></td>';
html += '</tr>'; html += '</tr>';
}); });
@ -709,13 +944,13 @@ function build_allocation_html(vat_free_items, vat_items, allocation_data) {
// Summary section // Summary section
let formatted_zero = $(frappe.format(0, {fieldtype: 'Currency'})).text(); let formatted_zero = $(frappe.format(0, {fieldtype: 'Currency'})).text();
html += '<div class="allocation-summary" style="margin-top: 20px; padding: 10px; background-color: #f8f9fa; border-radius: 5px;">'; html += '<div class="allocation-summary-' + tab_type + '" style="margin-top: 20px; padding: 10px; background-color: #f8f9fa; border-radius: 5px;">';
html += '<div class="row">'; html += '<div class="row">';
html += '<div class="col-md-6"><strong>Total VAT Free to Allocate:</strong> <span id="total_vat_free" class="text-primary">' + formatted_zero + '</span></div>'; html += '<div class="col-md-6"><strong>Total VAT Free to Allocate:</strong> <span id="total_vat_free_' + tab_type + '" class="text-primary">' + formatted_zero + '</span></div>';
html += '<div class="col-md-6"><strong>Total VAT to Allocate:</strong> <span id="total_vat_allocated" class="text-warning">' + formatted_zero + '</span></div>'; html += '<div class="col-md-6"><strong>Total VAT to Allocate:</strong> <span id="total_vat_allocated_' + tab_type + '" class="text-warning">' + formatted_zero + '</span></div>';
html += '</div>'; html += '</div>';
html += '<div class="row" style="margin-top: 10px;">'; html += '<div class="row" style="margin-top: 10px;">';
html += '<div class="col-md-12"><strong>Total:</strong> <span id="total_allocated" class="text-success">' + formatted_zero + '</span></div>'; html += '<div class="col-md-12"><strong>Total:</strong> <span id="total_allocated_' + tab_type + '" class="text-success">' + formatted_zero + '</span></div>';
html += '</div>'; html += '</div>';
html += '</div>'; html += '</div>';
@ -724,15 +959,16 @@ function build_allocation_html(vat_free_items, vat_items, allocation_data) {
return html; return html;
} }
function setup_allocation_inputs(dialog, allocation_data, max_vat_free, max_vat_allocated) { function setup_allocation_inputs(dialog, allocation_data, max_vat_free, max_vat_allocated, tab_type) {
let $wrapper = dialog.fields_dict.allocation_html.$wrapper; // Ищем wrapper по ID вкладки в общем wrapper диалога
let $wrapper = dialog.$wrapper.find('#tab-' + tab_type);
// Initial calculation // Initial calculation
setTimeout(function() { setTimeout(function() {
$wrapper.find('.allocation-input').first().trigger('input'); $wrapper.find('.allocation-input-' + tab_type).first().trigger('input');
}, 100); }, 100);
$wrapper.find('.allocation-input').on('input', function() { $wrapper.find('.allocation-input-' + tab_type).on('input', function() {
let $input = $(this); let $input = $(this);
let name = $input.data('name'); let name = $input.data('name');
let type = $input.data('type'); let type = $input.data('type');
@ -755,11 +991,11 @@ function setup_allocation_inputs(dialog, allocation_data, max_vat_free, max_vat_
type: type type: type
}; };
// Calculate totals // Calculate totals for this tab only
let total_vat_free = 0; let total_vat_free = 0;
let total_vat_allocated = 0; let total_vat_allocated = 0;
$wrapper.find('.allocation-input').each(function() { $wrapper.find('.allocation-input-' + tab_type).each(function() {
let item_type = $(this).data('type'); let item_type = $(this).data('type');
let item_value = parseFloat($(this).val()) || 0; let item_value = parseFloat($(this).val()) || 0;
@ -784,31 +1020,30 @@ function setup_allocation_inputs(dialog, allocation_data, max_vat_free, max_vat_
total_vat_free -= value; total_vat_free -= value;
} }
// Update summary for this tab
// Update summary $wrapper.find('#total_vat_free_' + tab_type).text($(frappe.format(total_vat_free, {fieldtype: 'Currency'})).text());
$wrapper.find('#total_vat_free').text($(frappe.format(total_vat_free, {fieldtype: 'Currency'})).text()); $wrapper.find('#total_vat_allocated_' + tab_type).text($(frappe.format(total_vat_allocated, {fieldtype: 'Currency'})).text());
$wrapper.find('#total_vat_allocated').text($(frappe.format(total_vat_allocated, {fieldtype: 'Currency'})).text()); $wrapper.find('#total_allocated_' + tab_type).text($(frappe.format(total_vat_free + total_vat_allocated, {fieldtype: 'Currency'})).text());
$wrapper.find('#total_allocated').text($(frappe.format(total_vat_free + total_vat_allocated, {fieldtype: 'Currency'})).text());
}); });
} }
function auto_allocate(frm, payment_row_idx, 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, tab_type, available_amounts, other_allocation_data) {
// Call server method to calculate auto allocation // Use calculated available amounts
frappe.call({ frappe.call({
method: 'taxes_az.taxes_az.doctype.vat_allocation.vat_allocation.calculate_auto_allocation', method: 'taxes_az.taxes_az.doctype.vat_allocation.vat_allocation.calculate_auto_allocation',
args: { args: {
vat_free_available: data.vat_free_available, vat_free_available: available_amounts.vat_free_available,
vat_allocated_available: data.vat_allocated_available, vat_allocated_available: available_amounts.vat_allocated_available,
vat_free_items: JSON.stringify(vat_free_items), vat_free_items: JSON.stringify(vat_free_items),
vat_items: JSON.stringify(vat_items) vat_items: JSON.stringify(vat_items)
}, },
callback: function(r) { callback: function(r) {
if (r.message) { if (r.message) {
let allocations = r.message; let allocations = r.message;
let $wrapper = dialog.fields_dict.allocation_html.$wrapper; let $wrapper = dialog.$wrapper.find('#tab-' + tab_type);
// Reset all inputs // Reset all inputs for this tab
$wrapper.find('.allocation-input').val(0); $wrapper.find('.allocation-input-' + tab_type).val(0);
// Update allocation data // Update allocation data
for (let key in allocation_data) { for (let key in allocation_data) {
@ -818,11 +1053,20 @@ function auto_allocate(frm, payment_row_idx, data, vat_free_items, vat_items, al
// Apply auto allocations // Apply auto allocations
allocations.forEach(function(alloc) { allocations.forEach(function(alloc) {
allocation_data[alloc.name].value = alloc.allocated; allocation_data[alloc.name].value = alloc.allocated;
$wrapper.find('.allocation-input[data-name="' + alloc.name + '"]').val(alloc.allocated.toFixed(2)); $wrapper.find('.allocation-input-' + tab_type + '[data-name="' + alloc.name + '"]').val(alloc.allocated.toFixed(2));
}); });
// Trigger input event to update summary // Trigger input event to update summary
$wrapper.find('.allocation-input').first().trigger('input'); $wrapper.find('.allocation-input-' + tab_type).first().trigger('input');
// Update available amounts in dialog fields
let new_available = calculate_available_amounts(data,
tab_type === 'current' ? allocation_data : other_allocation_data,
tab_type === 'previous' ? allocation_data : other_allocation_data
);
dialog.set_value('vat_free_available', new_available.vat_free_available);
dialog.set_value('vat_allocated_available', new_available.vat_allocated_available);
frappe.show_alert({ frappe.show_alert({
message: __('Auto allocation completed'), message: __('Auto allocation completed'),
@ -834,12 +1078,23 @@ function auto_allocate(frm, payment_row_idx, data, vat_free_items, vat_items, al
} }
// Сохранение allocation - работает с несохраненным документом // Сохранение allocation - работает с несохраненным документом
function save_allocation_data(frm, payment_row_idx, allocation_data, dialog) { function save_allocation_data(frm, payment_row_idx, allocation_data_current, allocation_data_previous, dialog) {
// Prepare allocations array // Prepare allocations array - combine both tabs
let allocations = []; let allocations = [];
for (let name in allocation_data) { // Add allocations from current month tab
let data = allocation_data[name]; for (let name in allocation_data_current) {
let data = allocation_data_current[name];
allocations.push({
name: name,
allocated: data.value,
type: data.type
});
}
// Add allocations from previous months tab
for (let name in allocation_data_previous) {
let data = allocation_data_previous[name];
allocations.push({ allocations.push({
name: name, name: name,
allocated: data.value, allocated: data.value,
@ -892,6 +1147,7 @@ function save_allocation_data(frm, payment_row_idx, allocation_data, dialog) {
frm.refresh_field('allocation_links'); frm.refresh_field('allocation_links');
frm.refresh_field('payment_entry_table'); frm.refresh_field('payment_entry_table');
frm.refresh_field('sales_invoice_table'); frm.refresh_field('sales_invoice_table');
frm.refresh_field('sales_invoice_previous_table');
// Помечаем документ как измененный // Помечаем документ как измененный
frm.dirty(); frm.dirty();
@ -899,7 +1155,7 @@ function save_allocation_data(frm, payment_row_idx, allocation_data, dialog) {
dialog.hide(); dialog.hide();
} }
// Функция для пересчета totals без сохранения в БД // Функция для пересчета totals без сохранения в БД - UPDATED
function recalculate_totals_manually(frm) { function recalculate_totals_manually(frm) {
// Reset all allocated amounts // Reset all allocated amounts
for (let pe_row of frm.doc.payment_entry_table || []) { for (let pe_row of frm.doc.payment_entry_table || []) {
@ -912,6 +1168,10 @@ function recalculate_totals_manually(frm) {
si_row.allocated_amount = 0; si_row.allocated_amount = 0;
} }
for (let si_row of frm.doc.sales_invoice_previous_table || []) {
si_row.allocated_amount = 0;
}
// Calculate from links // Calculate from links
for (let link of frm.doc.allocation_links || []) { for (let link of frm.doc.allocation_links || []) {
let allocated = link.allocated_amount || 0; let allocated = link.allocated_amount || 0;
@ -931,13 +1191,24 @@ function recalculate_totals_manually(frm) {
} }
} }
// Update sales invoice row // Update sales invoice row - check both tables
let found = false;
for (let si_row of frm.doc.sales_invoice_table || []) { for (let si_row of frm.doc.sales_invoice_table || []) {
if (si_row.name === link.sales_invoice_row) { if (si_row.name === link.sales_invoice_row) {
si_row.allocated_amount = (si_row.allocated_amount || 0) + allocated; si_row.allocated_amount = (si_row.allocated_amount || 0) + allocated;
found = true;
break; break;
} }
} }
if (!found) {
for (let si_row of frm.doc.sales_invoice_previous_table || []) {
if (si_row.name === link.sales_invoice_row) {
si_row.allocated_amount = (si_row.allocated_amount || 0) + allocated;
break;
}
}
}
} }
// Calculate unallocated amounts // Calculate unallocated amounts
@ -953,6 +1224,12 @@ function recalculate_totals_manually(frm) {
si_row.unallocated_amount = Math.max(0, total - allocated); si_row.unallocated_amount = Math.max(0, total - allocated);
} }
for (let si_row of frm.doc.sales_invoice_previous_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
calculate_tax_template_totals(frm); calculate_tax_template_totals(frm);
} }
@ -966,7 +1243,7 @@ function calculate_tax_template_totals(frm) {
frm.doc.total_edv_18 = 0; frm.doc.total_edv_18 = 0;
frm.doc.total_allocated_sum = 0; frm.doc.total_allocated_sum = 0;
// Calculate totals from sales invoice table // Calculate totals from sales invoice table (only current month)
for (let si_row of frm.doc.sales_invoice_table || []) { for (let si_row of frm.doc.sales_invoice_table || []) {
let allocated = si_row.allocated_amount || 0; let allocated = si_row.allocated_amount || 0;
if (allocated <= 0) continue; if (allocated <= 0) continue;

View File

@ -13,6 +13,8 @@
"payment_entry_table", "payment_entry_table",
"sales_invoice_section", "sales_invoice_section",
"sales_invoice_table", "sales_invoice_table",
"section_break_fhfs",
"sales_invoice_previous_table",
"allocation_links_section", "allocation_links_section",
"allocation_links", "allocation_links",
"total_edv_18", "total_edv_18",
@ -146,12 +148,22 @@
"print_hide": 1, "print_hide": 1,
"read_only": 1, "read_only": 1,
"search_index": 1 "search_index": 1
},
{
"fieldname": "section_break_fhfs",
"fieldtype": "Section Break"
},
{
"fieldname": "sales_invoice_previous_table",
"fieldtype": "Table",
"label": "Sales Invoice Previous Months",
"options": "VAT allocation sales invoice"
} }
], ],
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2025-10-06 16:52:07.347647", "modified": "2025-10-07 14:10:17.385408",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Taxes Az", "module": "Taxes Az",
"name": "VAT allocation", "name": "VAT allocation",

View File

@ -46,6 +46,9 @@ class VATallocation(Document):
for si_row in self.sales_invoice_table: for si_row in self.sales_invoice_table:
si_row.allocated_amount = 0 si_row.allocated_amount = 0
for si_row in self.sales_invoice_previous_table:
si_row.allocated_amount = 0
# Calculate from links # Calculate from links
for link in self.allocation_links: for link in self.allocation_links:
allocated = Decimal(str(link.allocated_amount or 0)) allocated = Decimal(str(link.allocated_amount or 0))
@ -66,13 +69,22 @@ class VATallocation(Document):
pe_row.vat_allocated_allocated = float(current_vat_allocated + allocated) pe_row.vat_allocated_allocated = float(current_vat_allocated + allocated)
break break
# Update sales invoice row # Update sales invoice row - check both tables
found = False
for si_row in self.sales_invoice_table: for si_row in self.sales_invoice_table:
if si_row.name == link.sales_invoice_row: if si_row.name == link.sales_invoice_row:
current_allocated = Decimal(str(si_row.allocated_amount or 0)) current_allocated = Decimal(str(si_row.allocated_amount or 0))
si_row.allocated_amount = float(current_allocated + allocated) si_row.allocated_amount = float(current_allocated + allocated)
found = True
break break
if not found:
for si_row in self.sales_invoice_previous_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 # Calculate unallocated amounts
for pe_row in self.payment_entry_table: for pe_row in self.payment_entry_table:
total = Decimal(str(pe_row.payment_entry_amount or 0)) total = Decimal(str(pe_row.payment_entry_amount or 0))
@ -84,6 +96,11 @@ class VATallocation(Document):
allocated = Decimal(str(si_row.allocated_amount or 0)) allocated = Decimal(str(si_row.allocated_amount or 0))
si_row.unallocated_amount = float(max(Decimal('0'), total - allocated)) si_row.unallocated_amount = float(max(Decimal('0'), total - allocated))
for si_row in self.sales_invoice_previous_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))
def validate_allocations(self): def validate_allocations(self):
""" """
Validate that all allocations are mathematically correct Validate that all allocations are mathematically correct
@ -106,7 +123,7 @@ class VATallocation(Document):
if vat_allocated_spent > vat_allocated_total + ROUNDING_TOLERANCE: if vat_allocated_spent > vat_allocated_total + ROUNDING_TOLERANCE:
frappe.throw(_(f"VAT Allocated spent exceeds total for payment entry {pe_row.payment_entry}")) frappe.throw(_(f"VAT Allocated spent exceeds total for payment entry {pe_row.payment_entry}"))
# Validate sales invoice items # Validate sales invoice items - both tables
for si_row in self.sales_invoice_table: for si_row in self.sales_invoice_table:
amount = Decimal(str(si_row.amount or 0)) amount = Decimal(str(si_row.amount or 0))
allocated = Decimal(str(si_row.allocated_amount or 0)) allocated = Decimal(str(si_row.allocated_amount or 0))
@ -121,6 +138,20 @@ class VATallocation(Document):
if abs(total_check - amount) > ROUNDING_TOLERANCE: if abs(total_check - amount) > ROUNDING_TOLERANCE:
frappe.throw(_(f"Allocated + Unallocated != Amount for {si_row.item_tax_template}")) frappe.throw(_(f"Allocated + Unallocated != Amount for {si_row.item_tax_template}"))
for si_row in self.sales_invoice_previous_table:
amount = Decimal(str(si_row.amount or 0))
allocated = Decimal(str(si_row.allocated_amount or 0))
unallocated = Decimal(str(si_row.unallocated_amount or 0))
# Check non-negative
if allocated < 0 or unallocated < -ROUNDING_TOLERANCE:
frappe.throw(_(f"Allocated amounts cannot be negative for previous month {si_row.item_tax_template}"))
# Check sum equals total (with tolerance)
total_check = allocated + unallocated
if abs(total_check - amount) > ROUNDING_TOLERANCE:
frappe.throw(_(f"Allocated + Unallocated != Amount for previous month {si_row.item_tax_template}"))
@frappe.whitelist() @frappe.whitelist()
def validate_all_allocations(vat_allocation_name): def validate_all_allocations(vat_allocation_name):
""" """
@ -351,6 +382,115 @@ def get_sales_invoice_items(year, month, customers=None, min_amount=None, max_am
return invoice_items return invoice_items
@frappe.whitelist()
def get_sales_invoice_items_previous_months(year, month, customers=None, min_amount=None, max_amount=None):
"""
Get Sales Invoice items from previous VAT Allocation documents where unallocated_amount > 0
Grouped by customer, item_tax_template, and tax_article
"""
# Convert month name to number
months = {
"January": 1, "February": 2, "March": 3, "April": 4,
"May": 5, "June": 6, "July": 7, "August": 8,
"September": 9, "October": 10, "November": 11, "December": 12
}
month_number = months.get(month)
if not month_number:
frappe.throw(_("Invalid month"))
# Parse customers filter
customer_list = None
if customers:
if isinstance(customers, str):
customers = json.loads(customers)
if customers and len(customers) > 0:
customer_list = [c.get('customer') for c in customers if c.get('customer')]
# Find all previous VAT Allocation documents
# We need to find documents where (year < current_year) OR (year == current_year AND month < current_month)
vat_allocations = frappe.db.sql("""
SELECT name, year, month
FROM `tabVAT allocation`
WHERE docstatus = 1
AND (
year < %(year)s
OR (year = %(year)s AND month < %(month)s)
)
ORDER BY year DESC, month DESC
""", {
'year': year,
'month': month_number
}, as_dict=1)
if not vat_allocations:
return []
# Collect all items with unallocated_amount > 0 from previous VAT Allocations
all_items = []
for vat_alloc in vat_allocations:
# Get sales invoice items from this VAT Allocation
items = frappe.db.sql("""
SELECT
customer,
item_tax_template,
tax_article,
unallocated_amount
FROM `tabVAT allocation sales invoice`
WHERE parent = %(parent)s
AND unallocated_amount > 0
""", {
'parent': vat_alloc.name
}, as_dict=1)
all_items.extend(items)
# Group by customer, item_tax_template, tax_article and sum unallocated amounts
grouped_items = {}
for item in all_items:
# Apply customer filter
if customer_list and item.customer not in customer_list:
continue
key = (item.customer, item.item_tax_template or '', item.tax_article or '')
if key not in grouped_items:
grouped_items[key] = {
'customer': item.customer,
'item_tax_template': item.item_tax_template,
'tax_article': item.tax_article,
'amount': Decimal('0')
}
grouped_items[key]['amount'] += Decimal(str(item.unallocated_amount))
# Convert to list and apply amount filters
result = []
for key, item in grouped_items.items():
amount = float(item['amount'])
# Apply min/max amount filters
if min_amount and amount < float(min_amount):
continue
if max_amount and amount > float(max_amount):
continue
result.append({
'customer': item['customer'],
'item_tax_template': item['item_tax_template'],
'tax_article': item['tax_article'],
'amount': amount,
'unallocated_amount': amount, # Initially all is unallocated
'allocated_amount': 0
})
# Sort by customer, item_tax_template, tax_article
result.sort(key=lambda x: (x['customer'], x['item_tax_template'] or '', x['tax_article'] or ''))
return result
@frappe.whitelist() @frappe.whitelist()
def get_allocation_data(vat_allocation_name, payment_entry_row_name, customer): def get_allocation_data(vat_allocation_name, payment_entry_row_name, customer):
""" """
@ -405,6 +545,10 @@ def get_allocation_data(vat_allocation_name, payment_entry_row_name, customer):
# VAT templates (ƏDV 18% and ƏDV daxil 18%) # VAT templates (ƏDV 18% and ƏDV daxil 18%)
vat_items = [] vat_items = []
vat_free_items_previous = []
vat_items_previous = []
# Current month items
for item in doc.sales_invoice_table: for item in doc.sales_invoice_table:
if item.customer == customer: if item.customer == customer:
# Get existing allocation for this item # Get existing allocation for this item
@ -429,12 +573,39 @@ def get_allocation_data(vat_allocation_name, payment_entry_row_name, customer):
elif 'ədv 18%' in template or 'ədv daxil 18%' in template: elif 'ədv 18%' in template or 'ədv daxil 18%' in template:
vat_items.append(item_data) vat_items.append(item_data)
# Previous months items
for item in doc.sales_invoice_previous_table:
if item.customer == customer:
# Get existing allocation for this item
existing_alloc = existing_allocations.get(item.name, {'amount': 0, 'type': None})
item_data = {
'name': item.name,
'item_tax_template': item.item_tax_template or '',
'tax_article': item.tax_article or '',
'amount': item.amount,
'unallocated_amount': item.unallocated_amount,
'allocated_amount': item.allocated_amount or 0,
'current_allocation': existing_alloc['amount'] # Allocation from THIS payment row
}
template = (item.item_tax_template or '').lower()
# Check if it's VAT free template
if 'ədv 0%' in template or 'ədv-dən azadolma' in template:
vat_free_items_previous.append(item_data)
# Check if it's VAT 18% template
elif 'ədv 18%' in template or 'ədv daxil 18%' in template:
vat_items_previous.append(item_data)
return { return {
'payment_row': payment_row, 'payment_row': payment_row,
'vat_free_available': vat_free_available, 'vat_free_available': vat_free_available,
'vat_allocated_available': vat_allocated_available, 'vat_allocated_available': vat_allocated_available,
'vat_free_items': vat_free_items, 'vat_free_items': vat_free_items,
'vat_items': vat_items 'vat_items': vat_items,
'vat_free_items_previous': vat_free_items_previous,
'vat_items_previous': vat_items_previous
} }
def round_decimal(value, precision=2): def round_decimal(value, precision=2):
@ -513,8 +684,9 @@ def save_allocation(vat_allocation_name, payment_entry_row_name, allocations):
"Total VAT Allocated ({0}) exceeds total amount ({1})" "Total VAT Allocated ({0}) exceeds total amount ({1})"
).format(total_vat_allocated_new, vat_allocated_amount)) ).format(total_vat_allocated_new, vat_allocated_amount))
# Validate each SI item # Validate each SI item - check both tables
si_items_map = {item.name: item for item in doc.sales_invoice_table} si_items_map = {item.name: item for item in doc.sales_invoice_table}
si_items_map.update({item.name: item for item in doc.sales_invoice_previous_table})
for allocation in allocations: for allocation in allocations:
si_row_name = allocation['name'] si_row_name = allocation['name']
@ -716,6 +888,9 @@ def recalculate_totals_from_links(self):
for si_row in self.sales_invoice_table: for si_row in self.sales_invoice_table:
si_row.allocated_amount = 0 si_row.allocated_amount = 0
for si_row in self.sales_invoice_previous_table:
si_row.allocated_amount = 0
# Calculate from links # Calculate from links
for link in self.allocation_links: for link in self.allocation_links:
allocated = Decimal(str(link.allocated_amount or 0)) allocated = Decimal(str(link.allocated_amount or 0))
@ -736,13 +911,22 @@ def recalculate_totals_from_links(self):
pe_row.vat_allocated_allocated = float(current_vat_allocated + allocated) pe_row.vat_allocated_allocated = float(current_vat_allocated + allocated)
break break
# Update sales invoice row # Update sales invoice row - check both tables
found = False
for si_row in self.sales_invoice_table: for si_row in self.sales_invoice_table:
if si_row.name == link.sales_invoice_row: if si_row.name == link.sales_invoice_row:
current_allocated = Decimal(str(si_row.allocated_amount or 0)) current_allocated = Decimal(str(si_row.allocated_amount or 0))
si_row.allocated_amount = float(current_allocated + allocated) si_row.allocated_amount = float(current_allocated + allocated)
found = True
break break
if not found:
for si_row in self.sales_invoice_previous_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 # Calculate unallocated amounts
for pe_row in self.payment_entry_table: for pe_row in self.payment_entry_table:
total = Decimal(str(pe_row.payment_entry_amount or 0)) total = Decimal(str(pe_row.payment_entry_amount or 0))
@ -754,6 +938,11 @@ def recalculate_totals_from_links(self):
allocated = Decimal(str(si_row.allocated_amount or 0)) allocated = Decimal(str(si_row.allocated_amount or 0))
si_row.unallocated_amount = float(max(Decimal('0'), total - allocated)) si_row.unallocated_amount = float(max(Decimal('0'), total - allocated))
for si_row in self.sales_invoice_previous_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 # Calculate totals by tax template
self.calculate_tax_template_totals() self.calculate_tax_template_totals()
@ -768,7 +957,7 @@ def calculate_tax_template_totals(self):
self.total_edv_18 = 0 self.total_edv_18 = 0
self.total_allocated_sum = 0 self.total_allocated_sum = 0
# Calculate totals # Calculate totals - only from current month table
for si_row in self.sales_invoice_table: for si_row in self.sales_invoice_table:
allocated = Decimal(str(si_row.allocated_amount or 0)) allocated = Decimal(str(si_row.allocated_amount or 0))
if allocated <= 0: if allocated <= 0:
@ -788,3 +977,187 @@ def calculate_tax_template_totals(self):
# Add to total sum # Add to total sum
self.total_allocated_sum = float(Decimal(str(self.total_allocated_sum or 0)) + allocated) self.total_allocated_sum = float(Decimal(str(self.total_allocated_sum or 0)) + allocated)
@frappe.whitelist()
def get_payment_entries_previous_months(year, month, customers=None, min_amount=None, max_amount=None):
"""
Get Payment Entries from previous VAT Allocation documents where unallocated_amount > 0
Returns the most recent state of each Payment Entry
"""
# Convert month name to number
months = {
"January": 1, "February": 2, "March": 3, "April": 4,
"May": 5, "June": 6, "July": 7, "August": 8,
"September": 9, "October": 10, "November": 11, "December": 12
}
month_number = months.get(month)
if not month_number:
frappe.throw(_("Invalid month"))
# Parse customers filter
customer_list = None
if customers:
if isinstance(customers, str):
customers = json.loads(customers)
if customers and len(customers) > 0:
customer_list = [c.get('customer') for c in customers if c.get('customer')]
# Find all previous VAT Allocation documents
vat_allocations = frappe.db.sql("""
SELECT name, year, month
FROM `tabVAT allocation`
WHERE docstatus = 1
AND (
year < %(year)s
OR (year = %(year)s AND month < %(month)s)
)
ORDER BY year DESC, month DESC
""", {
'year': year,
'month': month_number
}, as_dict=1)
if not vat_allocations:
return []
# Collect all payment entries with unallocated_amount > 0
# Keep only the most recent entry for each payment_entry
payment_entries_map = {}
for vat_alloc in vat_allocations:
# Get payment entries from this VAT Allocation
entries = frappe.db.sql("""
SELECT
payment_entry,
customer,
payment_entry_amount,
vat_journal_entry,
vat_amount,
vat_allocated_amount,
vat_free_amount,
vat_allocated_allocated,
vat_free_allocated,
unallocated_amount
FROM `tabVAT allocation payment entry`
WHERE parent = %(parent)s
AND unallocated_amount > 0
""", {
'parent': vat_alloc.name
}, as_dict=1)
for entry in entries:
# Apply customer filter
if customer_list and entry.customer not in customer_list:
continue
# Apply min/max amount filters to payment_entry_amount
if min_amount and (entry.payment_entry_amount or 0) < float(min_amount):
continue
if max_amount and (entry.payment_entry_amount or 0) > float(max_amount):
continue
# Keep only the most recent (first encountered due to ORDER BY DESC)
if entry.payment_entry not in payment_entries_map:
# Calculate remaining amounts
vat_allocated_remaining = Decimal(str(entry.vat_allocated_amount or 0)) - Decimal(str(entry.vat_allocated_allocated or 0))
vat_free_remaining = Decimal(str(entry.vat_free_amount or 0)) - Decimal(str(entry.vat_free_allocated or 0))
payment_entries_map[entry.payment_entry] = {
'payment_entry': entry.payment_entry,
'customer': entry.customer,
'payment_entry_amount': entry.payment_entry_amount,
'vat_journal_entry': entry.vat_journal_entry,
'vat_amount': entry.vat_amount,
'vat_allocated_amount': float(vat_allocated_remaining),
'vat_free_amount': float(vat_free_remaining),
'unallocated_amount': 0, # Will be calculated
'allocated_amount': 0,
'vat_allocated_allocated': 0,
'vat_free_allocated': 0
}
# Convert to list
result = list(payment_entries_map.values())
# Sort by payment_entry
result.sort(key=lambda x: x['payment_entry'])
return result
@frappe.whitelist()
def get_payment_entries_with_previous(year, month, customers=None, min_amount=None, max_amount=None):
"""
Get Payment Entries - both current month and previous months with unallocated amounts
"""
# Get current month payment entries
current_entries = get_payment_entries(year, month, customers, min_amount, max_amount)
# Get previous months payment entries
previous_entries = get_payment_entries_previous_months(year, month, customers, min_amount, max_amount)
# Create a set of payment_entry names from current month
current_pe_names = {entry['payment_entry'] for entry in current_entries}
# Filter out previous entries that are already in current month
filtered_previous = [entry for entry in previous_entries if entry['payment_entry'] not in current_pe_names]
# Combine and return
return current_entries + filtered_previous
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def get_payment_entry_query(doctype, txt, searchfield, start, page_len, filters):
"""
Custom query for filtering Payment Entries - includes both current month and those with remaining balance
"""
year = filters.get('year')
month = filters.get('month')
if not all([year, month]):
return []
# Convert month name to number
months = {
"January": 1, "February": 2, "March": 3, "April": 4,
"May": 5, "June": 6, "July": 7, "August": 8,
"September": 9, "October": 10, "November": 11, "December": 12
}
month_number = months.get(month)
if not month_number:
return []
# Get Payment Entries from current month OR from previous VAT Allocations with balance
return frappe.db.sql("""
SELECT DISTINCT pe.name, pe.posting_date, pe.paid_amount
FROM `tabPayment Entry` pe
WHERE pe.docstatus = 1
AND pe.payment_type = 'Receive'
AND pe.party_type = 'Customer'
AND pe.name LIKE %(txt)s
AND (
(YEAR(pe.posting_date) = %(year)s AND MONTH(pe.posting_date) = %(month)s)
OR
EXISTS (
SELECT 1
FROM `tabVAT allocation payment entry` vape
INNER JOIN `tabVAT allocation` va ON va.name = vape.parent
WHERE vape.payment_entry = pe.name
AND va.docstatus = 1
AND vape.unallocated_amount > 0
AND (
va.year < %(year)s
OR (va.year = %(year)s AND va.month < %(month)s)
)
)
)
ORDER BY pe.posting_date DESC
LIMIT %(start)s, %(page_len)s
""", {
'year': year,
'month': month_number,
'txt': f"%{txt}%",
'start': start,
'page_len': page_len
})

View File

@ -0,0 +1,89 @@
{
"actions": [],
"allow_rename": 1,
"creation": "2025-10-07 14:07:17.398766",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"customer",
"payment_entry",
"item_tax_template",
"tax_article",
"amount",
"allocated_amount",
"unallocated_amount"
],
"fields": [
{
"fieldname": "customer",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Customer",
"options": "Customer",
"read_only": 1
},
{
"fieldname": "payment_entry",
"fieldtype": "Link",
"hidden": 1,
"in_list_view": 1,
"label": "Payment Entry",
"options": "Payment Entry"
},
{
"fieldname": "item_tax_template",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Item tax template",
"options": "Item Tax Template",
"precision": "2",
"read_only": 1
},
{
"fieldname": "tax_article",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Tax Article",
"options": "Tax Article",
"precision": "2",
"read_only": 1
},
{
"fieldname": "amount",
"fieldtype": "Currency",
"in_list_view": 1,
"label": "Amount",
"precision": "2",
"read_only": 1
},
{
"fieldname": "allocated_amount",
"fieldtype": "Currency",
"in_list_view": 1,
"label": "Allocated amount",
"precision": "2",
"read_only": 1
},
{
"fieldname": "unallocated_amount",
"fieldtype": "Currency",
"in_list_view": 1,
"label": "Unallocated amount",
"precision": "2",
"read_only": 1
}
],
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2025-10-07 14:07:17.398766",
"modified_by": "Administrator",
"module": "Taxes Az",
"name": "VAT allocation sales invoice previous months",
"owner": "Administrator",
"permissions": [],
"sort_field": "creation",
"sort_order": "DESC",
"states": []
}

View File

@ -0,0 +1,9 @@
# Copyright (c) 2025, Jey Soft and contributors
# For license information, please see license.txt
# import frappe
from frappe.model.document import Document
class VATallocationsalesinvoicepreviousmonths(Document):
pass