opening entryies has been added to vat allocation

This commit is contained in:
Ali 2026-02-23 22:20:55 +04:00
parent 57a5b0ac32
commit d5a9a7b14b
5 changed files with 577 additions and 94 deletions

View File

@ -10,6 +10,11 @@ frappe.ui.form.on('VAT allocation', {
frm.add_custom_button(__('Fill Sales Invoice Items'), function() { frm.add_custom_button(__('Fill Sales Invoice Items'), function() {
show_sales_invoice_filters(frm); show_sales_invoice_filters(frm);
}); });
// Add button for filling Opening Entries
frm.add_custom_button(__('Fill Opening Entries'), function() {
show_opening_entry_filters(frm);
});
} }
// Setup filter for Journal Entry // Setup filter for Journal Entry
@ -302,6 +307,108 @@ function load_previous_months_items(frm, filters) {
}); });
} }
// Function to show filter dialog for Opening Entries
function show_opening_entry_filters(frm) {
if (!frm.doc.year || !frm.doc.month) {
frappe.msgprint(__('Please select Year and Month first'));
return;
}
let dialog = new frappe.ui.Dialog({
title: __('Opening Entry Filters'),
fields: [
{
fieldname: 'customers_section',
fieldtype: 'Section Break',
label: __('Select Customers (Optional)')
},
{
fieldname: 'customers',
fieldtype: 'Table',
label: __('Customers'),
description: __('Leave empty to get all customers'),
fields: [
{
fieldname: 'customer',
fieldtype: 'Link',
label: __('Customer'),
options: 'Customer',
in_list_view: 1
}
]
},
{
fieldname: 'amount_section',
fieldtype: 'Section Break',
label: __('Amount Range (Optional)')
},
{
fieldname: 'min_amount',
fieldtype: 'Currency',
label: __('Minimum Amount'),
description: __('Only grouped amounts >= this amount')
},
{
fieldname: 'column_break_1',
fieldtype: 'Column Break'
},
{
fieldname: 'max_amount',
fieldtype: 'Currency',
label: __('Maximum Amount'),
description: __('Only grouped amounts <= this amount')
}
],
primary_action_label: __('Fill Table'),
primary_action: function(values) {
fill_opening_entries(frm, values);
dialog.hide();
}
});
dialog.show();
setTimeout(function() {
dialog.$wrapper.find('.frappe-control[data-fieldname="customers"] .grid-scroll-bar').css({
'visibility': 'hidden',
'height': '0px'
});
}, 100);
}
// Function to fill Opening Entries table
function fill_opening_entries(frm, filters) {
frappe.call({
method: 'taxes_az.taxes_az.doctype.vat_allocation.vat_allocation.get_opening_entries',
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) {
frm.clear_table('opening_entry_table');
if (r.message && r.message.length > 0) {
r.message.forEach(function(item) {
let row = frm.add_child('opening_entry_table');
row.customer = item.customer;
row.amount = item.amount;
row.unallocated_amount = item.amount;
row.allocated_amount = 0;
});
frm.refresh_field('opening_entry_table');
frappe.msgprint(__('Opening Entries loaded successfully: {0} records', [r.message.length]));
} else {
frm.refresh_field('opening_entry_table');
frappe.msgprint(__('No Opening Entries found for selected period'));
}
}
});
}
// Setup query filter for Journal Entry // Setup query filter for Journal Entry
function setup_journal_entry_query(frm) { function setup_journal_entry_query(frm) {
frm.fields_dict.payment_entry_table.grid.get_field('vat_journal_entry').get_query = function(doc, cdt, cdn) { frm.fields_dict.payment_entry_table.grid.get_field('vat_journal_entry').get_query = function(doc, cdt, cdn) {
@ -349,8 +456,9 @@ frappe.ui.form.on('VAT allocation payment entry', {
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.refresh_field('sales_invoice_previous_table');
frm.refresh_field('opening_entry_table');
}, },
// При изменении Payment Entry - обновить суммы // При изменении Payment Entry - обновить суммы
payment_entry: function(frm, cdt, cdn) { payment_entry: function(frm, cdt, cdn) {
let row = locals[cdt][cdn]; let row = locals[cdt][cdn];
@ -376,10 +484,11 @@ frappe.ui.form.on('VAT allocation payment entry', {
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.refresh_field('sales_invoice_previous_table');
frm.refresh_field('opening_entry_table');
return; return;
} }
// Получаем данные из Payment Entry // Получаем данные из Payment Entry
frappe.call({ frappe.call({
method: 'frappe.client.get_value', method: 'frappe.client.get_value',
@ -437,11 +546,12 @@ frappe.ui.form.on('VAT allocation payment entry', {
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.refresh_field('sales_invoice_previous_table');
frm.refresh_field('opening_entry_table');
} }
} }
}); });
}, },
// При удалении строки - удалить связанные links // При удалении строки - удалить связанные links
payment_entry_table_remove: function(frm, cdt, cdn) { payment_entry_table_remove: function(frm, cdt, cdn) {
// Собираем список всех существующих row names из текущей таблицы // Собираем список всех существующих row names из текущей таблицы
@ -462,6 +572,7 @@ frappe.ui.form.on('VAT allocation payment entry', {
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.refresh_field('sales_invoice_previous_table');
frm.refresh_field('opening_entry_table');
}, },
vat_journal_entry: function(frm, cdt, cdn) { vat_journal_entry: function(frm, cdt, cdn) {
@ -647,19 +758,45 @@ function show_allocation_dialog(frm, payment_row_idx, customer) {
} }
} }
// Opening entry items
let opening_entry_items = [];
for (let item of frm.doc.opening_entry_table || []) {
if (item.customer !== customer) {
continue;
}
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;
}
}
opening_entry_items.push({
name: item.name,
amount: item.amount || 0,
unallocated_amount: item.unallocated_amount || 0,
allocated_amount: item.allocated_amount || 0,
current_allocation: current_allocation
});
}
// 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) { vat_free_items_previous.length === 0 && vat_items_previous.length === 0 &&
opening_entry_items.length === 0) {
frappe.msgprint(__('No sales invoice items available for this customer')); frappe.msgprint(__('No sales invoice items available for this customer'));
return; return;
} }
// Вычисляем available amounts из текущих данных формы // Вычисляем available amounts из текущих данных формы
let vat_free_amount = payment_row.vat_free_amount || 0; let vat_free_amount = payment_row.vat_free_amount || 0;
let vat_free_allocated = payment_row.vat_free_allocated || 0; let vat_free_allocated = payment_row.vat_free_allocated || 0;
let vat_allocated_amount = payment_row.vat_allocated_amount || 0; let vat_allocated_amount = payment_row.vat_allocated_amount || 0;
let vat_allocated_allocated = payment_row.vat_allocated_allocated || 0; let vat_allocated_allocated = payment_row.vat_allocated_allocated || 0;
let data = { let data = {
payment_row: payment_row, payment_row: payment_row,
vat_free_available: vat_free_amount - vat_free_allocated, vat_free_available: vat_free_amount - vat_free_allocated,
@ -667,9 +804,10 @@ function show_allocation_dialog(frm, payment_row_idx, customer) {
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_free_items_previous: vat_free_items_previous,
vat_items_previous: vat_items_previous vat_items_previous: vat_items_previous,
opening_entry_items: opening_entry_items
}; };
// Create allocation dialog // Create allocation dialog
create_allocation_dialog(frm, payment_row_idx, data); create_allocation_dialog(frm, payment_row_idx, data);
} }
@ -677,7 +815,8 @@ function show_allocation_dialog(frm, payment_row_idx, customer) {
function create_allocation_dialog(frm, payment_row_idx, data) { function create_allocation_dialog(frm, payment_row_idx, data) {
let allocation_data_current = {}; let allocation_data_current = {};
let allocation_data_previous = {}; let allocation_data_previous = {};
let allocation_data_opening = {};
let dialog = new frappe.ui.Dialog({ let dialog = new frappe.ui.Dialog({
title: __('Allocate Amount'), title: __('Allocate Amount'),
size: 'extra-large', size: 'extra-large',
@ -725,32 +864,37 @@ function create_allocation_dialog(frm, payment_row_idx, data) {
], ],
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_current, allocation_data_previous, dialog); save_allocation_data(frm, payment_row_idx, allocation_data_current, allocation_data_previous, allocation_data_opening, dialog);
}, },
secondary_action_label: __('Auto Allocate'), secondary_action_label: __('Auto Allocate'),
secondary_action: function() { secondary_action: function() {
// Determine which tab is active // Determine which tab is active
let active_tab = dialog.$wrapper.find('.nav-tabs .nav-link.active').data('tab'); let active_tab = dialog.$wrapper.find('.nav-tabs .nav-link.active').data('tab');
// Calculate remaining available amounts // Calculate remaining available amounts
let available_amounts = calculate_available_amounts( let available_amounts = calculate_available_amounts(
data, data,
allocation_data_current, allocation_data_current,
allocation_data_previous allocation_data_previous,
allocation_data_opening
); );
if (active_tab === 'current') { if (active_tab === 'current') {
auto_allocate(frm, payment_row_idx, data, data.vat_free_items, data.vat_items, auto_allocate(frm, payment_row_idx, data, data.vat_free_items, data.vat_items,
allocation_data_current, dialog, 'current', available_amounts, allocation_data_current, dialog, 'current', available_amounts,
allocation_data_previous); allocation_data_previous);
} else if (active_tab === 'opening') {
auto_allocate(frm, payment_row_idx, data, data.opening_entry_items, [],
allocation_data_opening, dialog, 'opening', available_amounts,
allocation_data_current);
} else { } else {
auto_allocate(frm, payment_row_idx, data, data.vat_free_items_previous, data.vat_items_previous, 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_previous, dialog, 'previous', available_amounts,
allocation_data_current); allocation_data_current);
} }
} }
}); });
// Build payment info HTML // Build payment info HTML
let info_html = ` let info_html = `
<div style="padding: 10px; background-color: #f8f9fa; border-radius: 5px; margin-bottom: 10px;"> <div style="padding: 10px; background-color: #f8f9fa; border-radius: 5px; margin-bottom: 10px;">
@ -767,11 +911,12 @@ function create_allocation_dialog(frm, payment_row_idx, data) {
</div> </div>
`; `;
dialog.fields_dict.payment_info_html.$wrapper.html(info_html); dialog.fields_dict.payment_info_html.$wrapper.html(info_html);
// Build tabs HTML with Bootstrap // Build tabs HTML with Bootstrap
let html_current = build_allocation_html(data.vat_free_items, data.vat_items, allocation_data_current, 'current'); let html_current = build_allocation_html(data.vat_free_items, data.vat_items, allocation_data_current, 'current');
let html_previous = build_allocation_html(data.vat_free_items_previous, data.vat_items_previous, allocation_data_previous, 'previous'); let html_previous = build_allocation_html(data.vat_free_items_previous, data.vat_items_previous, allocation_data_previous, 'previous');
let html_opening = build_allocation_html(data.opening_entry_items, [], allocation_data_opening, 'opening');
let tabs_html = ` let tabs_html = `
<style> <style>
.allocation-tabs .nav-link { .allocation-tabs .nav-link {
@ -809,6 +954,11 @@ function create_allocation_dialog(frm, payment_row_idx, data) {
${__('Previous Months')} ${__('Previous Months')}
</a> </a>
</li> </li>
<li class="nav-item">
<a class="nav-link" id="tab-link-opening" data-tab="opening" href="#tab-opening" role="tab">
${__('Opening Entries')}
</a>
</li>
</ul> </ul>
<div class="tab-content"> <div class="tab-content">
<div class="tab-pane fade show active" id="tab-current" role="tabpanel"> <div class="tab-pane fade show active" id="tab-current" role="tabpanel">
@ -817,41 +967,47 @@ function create_allocation_dialog(frm, payment_row_idx, data) {
<div class="tab-pane fade" id="tab-previous" role="tabpanel"> <div class="tab-pane fade" id="tab-previous" role="tabpanel">
${html_previous} ${html_previous}
</div> </div>
<div class="tab-pane fade" id="tab-opening" role="tabpanel">
${html_opening}
</div>
</div> </div>
`; `;
dialog.fields_dict.tabs_html.$wrapper.html(tabs_html); dialog.fields_dict.tabs_html.$wrapper.html(tabs_html);
// Setup custom tab switching (without Bootstrap's data-toggle) // Setup custom tab switching (without Bootstrap's data-toggle)
dialog.$wrapper.find('.nav-link').on('click', function(e) { dialog.$wrapper.find('.nav-link').on('click', function(e) {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
let $this = $(this); let $this = $(this);
let target = $this.attr('href'); let target = $this.attr('href');
// Update nav links // Update nav links
dialog.$wrapper.find('.nav-link').removeClass('active'); dialog.$wrapper.find('.nav-link').removeClass('active');
$this.addClass('active'); $this.addClass('active');
// Update tab panes // Update tab panes
dialog.$wrapper.find('.tab-pane').removeClass('show active'); dialog.$wrapper.find('.tab-pane').removeClass('show active');
dialog.$wrapper.find(target).addClass('show active'); dialog.$wrapper.find(target).addClass('show active');
}); });
// Setup input handlers for both tabs // Setup input handlers for all tabs
setup_allocation_inputs(dialog, allocation_data_current, data.vat_free_available, data.vat_allocated_available, 'current'); 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'); setup_allocation_inputs(dialog, allocation_data_previous, data.vat_free_available, data.vat_allocated_available, 'previous');
setup_allocation_inputs(dialog, allocation_data_opening, data.vat_free_available, data.vat_allocated_available, 'opening');
dialog.show(); dialog.show();
} }
function calculate_available_amounts(data, allocation_data_current, allocation_data_previous) { function calculate_available_amounts(data, allocation_data_current, allocation_data_previous, allocation_data_opening) {
let total_vat_free_current = 0; let total_vat_free_current = 0;
let total_vat_allocated_current = 0; let total_vat_allocated_current = 0;
let total_vat_free_previous = 0; let total_vat_free_previous = 0;
let total_vat_allocated_previous = 0; let total_vat_allocated_previous = 0;
let total_vat_free_opening = 0;
let total_vat_allocated_opening = 0;
// Calculate totals from current month // Calculate totals from current month
for (let name in allocation_data_current) { for (let name in allocation_data_current) {
let alloc = allocation_data_current[name]; let alloc = allocation_data_current[name];
@ -861,7 +1017,7 @@ function calculate_available_amounts(data, allocation_data_current, allocation_d
total_vat_allocated_current += alloc.value || 0; total_vat_allocated_current += alloc.value || 0;
} }
} }
// Calculate totals from previous months // Calculate totals from previous months
for (let name in allocation_data_previous) { for (let name in allocation_data_previous) {
let alloc = allocation_data_previous[name]; let alloc = allocation_data_previous[name];
@ -871,11 +1027,21 @@ function calculate_available_amounts(data, allocation_data_current, allocation_d
total_vat_allocated_previous += alloc.value || 0; total_vat_allocated_previous += alloc.value || 0;
} }
} }
// Calculate totals from opening entries
for (let name in (allocation_data_opening || {})) {
let alloc = allocation_data_opening[name];
if (alloc.type === 'vat_free') {
total_vat_free_opening += alloc.value || 0;
} else {
total_vat_allocated_opening += alloc.value || 0;
}
}
// Calculate remaining available // Calculate remaining available
let vat_free_available = data.vat_free_available - total_vat_free_current - total_vat_free_previous; let vat_free_available = data.vat_free_available - total_vat_free_current - total_vat_free_previous - total_vat_free_opening;
let vat_allocated_available = data.vat_allocated_available - total_vat_allocated_current - total_vat_allocated_previous; let vat_allocated_available = data.vat_allocated_available - total_vat_allocated_current - total_vat_allocated_previous - total_vat_allocated_opening;
return { return {
vat_free_available: Math.max(0, vat_free_available), vat_free_available: Math.max(0, vat_free_available),
vat_allocated_available: Math.max(0, vat_allocated_available) vat_allocated_available: Math.max(0, vat_allocated_available)
@ -884,7 +1050,49 @@ function calculate_available_amounts(data, allocation_data_current, allocation_d
function build_allocation_html(vat_free_items, vat_items, allocation_data, tab_type) { 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;">';
// Opening entries tab — simplified table without template/article columns
if (tab_type === 'opening') {
if (vat_free_items.length > 0) {
html += '<h5 style="margin-top: 10px; color: #2490ef;">' + __('Opening Entry Items') + '</h5>';
html += '<table class="table table-bordered" style="margin-bottom: 20px;">';
html += '<thead><tr>';
html += '<th>' + __('Customer') + '</th>';
html += '<th>' + __('Total Amount') + '</th>';
html += '<th>' + __('Unallocated') + '</th>';
html += '<th>' + __('Current Alloc') + '</th>';
html += '<th style="width: 150px;">' + __('New Allocation') + '</th>';
html += '</tr></thead><tbody>';
vat_free_items.forEach(function(item) {
allocation_data[item.name] = { value: item.current_allocation || 0, type: 'vat_free' };
html += '<tr>';
html += '<td>' + (item.customer || '-') + '</td>';
html += '<td>' + format_currency(item.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><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 += '</tbody></table>';
}
// Summary section for opening tab
let formatted_zero = $(frappe.format(0, {fieldtype: 'Currency'})).text();
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="col-md-12"><strong>' + __('Total to Allocate') + ':</strong> <span id="total_vat_free_' + tab_type + '" class="text-primary">' + formatted_zero + '</span></div>';
html += '</div>';
html += '<div class="row" style="margin-top: 10px;">';
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>';
return html;
}
// VAT Free items section // VAT Free items section
if (vat_free_items.length > 0) { if (vat_free_items.length > 0) {
html += '<h5 style="margin-top: 10px; color: #2490ef;">VAT Free Items (ƏDV 0%, ƏDV-dən azadolma)</h5>'; html += '<h5 style="margin-top: 10px; color: #2490ef;">VAT Free Items (ƏDV 0%, ƏDV-dən azadolma)</h5>';
@ -897,7 +1105,7 @@ function build_allocation_html(vat_free_items, vat_items, allocation_data, tab_t
html += '<th>Current Alloc</th>'; html += '<th>Current Alloc</th>';
html += '<th style="width: 150px;">New Allocation</th>'; html += '<th style="width: 150px;">New Allocation</th>';
html += '</tr></thead><tbody>'; html += '</tr></thead><tbody>';
vat_free_items.forEach(function(item) { vat_free_items.forEach(function(item) {
allocation_data[item.name] = { value: item.current_allocation || 0, type: 'vat_free' }; allocation_data[item.name] = { value: item.current_allocation || 0, type: 'vat_free' };
html += '<tr>'; html += '<tr>';
@ -909,7 +1117,7 @@ function build_allocation_html(vat_free_items, vat_items, allocation_data, tab_t
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 += '<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>';
}); });
html += '</tbody></table>'; html += '</tbody></table>';
} }
@ -1060,9 +1268,10 @@ function auto_allocate(frm, payment_row_idx, data, vat_free_items, vat_items, al
$wrapper.find('.allocation-input-' + tab_type).first().trigger('input'); $wrapper.find('.allocation-input-' + tab_type).first().trigger('input');
// Update available amounts in dialog fields // Update available amounts in dialog fields
let new_available = calculate_available_amounts(data, let new_available = calculate_available_amounts(data,
tab_type === 'current' ? allocation_data : other_allocation_data, tab_type === 'current' ? allocation_data : other_allocation_data,
tab_type === 'previous' ? allocation_data : other_allocation_data tab_type === 'previous' ? allocation_data : other_allocation_data,
tab_type === 'opening' ? allocation_data : {}
); );
dialog.set_value('vat_free_available', new_available.vat_free_available); dialog.set_value('vat_free_available', new_available.vat_free_available);
@ -1078,10 +1287,10 @@ 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_current, allocation_data_previous, dialog) { function save_allocation_data(frm, payment_row_idx, allocation_data_current, allocation_data_previous, allocation_data_opening, dialog) {
// Prepare allocations array - combine both tabs // Prepare allocations array - combine all tabs
let allocations = []; let allocations = [];
// Add allocations from current month tab // Add allocations from current month tab
for (let name in allocation_data_current) { for (let name in allocation_data_current) {
let data = allocation_data_current[name]; let data = allocation_data_current[name];
@ -1091,7 +1300,7 @@ function save_allocation_data(frm, payment_row_idx, allocation_data_current, all
type: data.type type: data.type
}); });
} }
// Add allocations from previous months tab // Add allocations from previous months tab
for (let name in allocation_data_previous) { for (let name in allocation_data_previous) {
let data = allocation_data_previous[name]; let data = allocation_data_previous[name];
@ -1101,7 +1310,17 @@ function save_allocation_data(frm, payment_row_idx, allocation_data_current, all
type: data.type type: data.type
}); });
} }
// Add allocations from opening entries tab
for (let name in (allocation_data_opening || {})) {
let data = allocation_data_opening[name];
allocations.push({
name: name,
allocated: data.value,
type: data.type
});
}
// Найдем payment row по idx // Найдем payment row по idx
let payment_row = null; let payment_row = null;
for (let row of frm.doc.payment_entry_table || []) { for (let row of frm.doc.payment_entry_table || []) {
@ -1110,21 +1329,21 @@ function save_allocation_data(frm, payment_row_idx, allocation_data_current, all
break; break;
} }
} }
if (!payment_row) { if (!payment_row) {
frappe.msgprint(__('Payment entry row not found')); frappe.msgprint(__('Payment entry row not found'));
return; return;
} }
// Удаляем старые links для этого payment row // Удаляем старые links для этого payment row
frm.doc.allocation_links = (frm.doc.allocation_links || []).filter(function(link) { frm.doc.allocation_links = (frm.doc.allocation_links || []).filter(function(link) {
return link.payment_entry_row !== payment_row.name; return link.payment_entry_row !== payment_row.name;
}); });
// Добавляем новые links // Добавляем новые links
let total_vat_free = 0; let total_vat_free = 0;
let total_vat_allocated = 0; let total_vat_allocated = 0;
allocations.forEach(function(alloc) { allocations.forEach(function(alloc) {
if (alloc.allocated > 0) { if (alloc.allocated > 0) {
let link = frm.add_child('allocation_links'); let link = frm.add_child('allocation_links');
@ -1132,7 +1351,7 @@ function save_allocation_data(frm, payment_row_idx, allocation_data_current, all
link.sales_invoice_row = alloc.name; link.sales_invoice_row = alloc.name;
link.allocated_amount = alloc.allocated; link.allocated_amount = alloc.allocated;
link.allocation_type = alloc.type; link.allocation_type = alloc.type;
if (alloc.type === 'vat_free') { if (alloc.type === 'vat_free') {
total_vat_free += alloc.allocated; total_vat_free += alloc.allocated;
} else { } else {
@ -1140,18 +1359,19 @@ function save_allocation_data(frm, payment_row_idx, allocation_data_current, all
} }
} }
}); });
// Пересчитываем totals вручную (так как validate еще не вызван) // Пересчитываем totals вручную (так как validate еще не вызван)
recalculate_totals_manually(frm); recalculate_totals_manually(frm);
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.refresh_field('sales_invoice_previous_table');
frm.refresh_field('opening_entry_table');
// Помечаем документ как измененный // Помечаем документ как измененный
frm.dirty(); frm.dirty();
dialog.hide(); dialog.hide();
} }
@ -1163,25 +1383,29 @@ function recalculate_totals_manually(frm) {
pe_row.vat_allocated_allocated = 0; pe_row.vat_allocated_allocated = 0;
pe_row.allocated_amount = 0; pe_row.allocated_amount = 0;
} }
for (let si_row of frm.doc.sales_invoice_table || []) { for (let si_row of frm.doc.sales_invoice_table || []) {
si_row.allocated_amount = 0; si_row.allocated_amount = 0;
} }
for (let si_row of frm.doc.sales_invoice_previous_table || []) { for (let si_row of frm.doc.sales_invoice_previous_table || []) {
si_row.allocated_amount = 0; si_row.allocated_amount = 0;
} }
for (let oe_row of frm.doc.opening_entry_table || []) {
oe_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;
if (allocated <= 0) continue; if (allocated <= 0) continue;
// Update payment entry row // Update payment entry row
for (let pe_row of frm.doc.payment_entry_table || []) { for (let pe_row of frm.doc.payment_entry_table || []) {
if (pe_row.name === link.payment_entry_row) { if (pe_row.name === link.payment_entry_row) {
pe_row.allocated_amount = (pe_row.allocated_amount || 0) + allocated; pe_row.allocated_amount = (pe_row.allocated_amount || 0) + allocated;
if (link.allocation_type === 'vat_free') { if (link.allocation_type === 'vat_free') {
pe_row.vat_free_allocated = (pe_row.vat_free_allocated || 0) + allocated; pe_row.vat_free_allocated = (pe_row.vat_free_allocated || 0) + allocated;
} else { } else {
@ -1190,8 +1414,8 @@ function recalculate_totals_manually(frm) {
break; break;
} }
} }
// Update sales invoice row - check both tables // Update sales invoice row - check all three tables
let found = false; 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) {
@ -1200,36 +1424,52 @@ function recalculate_totals_manually(frm) {
break; break;
} }
} }
if (!found) { if (!found) {
for (let si_row of frm.doc.sales_invoice_previous_table || []) { for (let si_row of frm.doc.sales_invoice_previous_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;
}
}
}
if (!found) {
for (let oe_row of frm.doc.opening_entry_table || []) {
if (oe_row.name === link.sales_invoice_row) {
oe_row.allocated_amount = (oe_row.allocated_amount || 0) + allocated;
break; break;
} }
} }
} }
} }
// Calculate unallocated amounts // Calculate unallocated amounts
for (let pe_row of frm.doc.payment_entry_table || []) { for (let pe_row of frm.doc.payment_entry_table || []) {
let total = pe_row.payment_entry_amount || 0; let total = pe_row.payment_entry_amount || 0;
let allocated = pe_row.allocated_amount || 0; let allocated = pe_row.allocated_amount || 0;
pe_row.unallocated_amount = Math.max(0, total - allocated); pe_row.unallocated_amount = Math.max(0, total - allocated);
} }
for (let si_row of frm.doc.sales_invoice_table || []) { for (let si_row of frm.doc.sales_invoice_table || []) {
let total = si_row.amount || 0; let total = si_row.amount || 0;
let allocated = si_row.allocated_amount || 0; let allocated = si_row.allocated_amount || 0;
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 || []) { for (let si_row of frm.doc.sales_invoice_previous_table || []) {
let total = si_row.amount || 0; let total = si_row.amount || 0;
let allocated = si_row.allocated_amount || 0; let allocated = si_row.allocated_amount || 0;
si_row.unallocated_amount = Math.max(0, total - allocated); si_row.unallocated_amount = Math.max(0, total - allocated);
} }
for (let oe_row of frm.doc.opening_entry_table || []) {
let total = oe_row.amount || 0;
let allocated = oe_row.allocated_amount || 0;
oe_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);
} }
@ -1297,6 +1537,18 @@ function calculate_tax_template_totals(frm) {
} }
} }
// Add opening entry rows to totals (no VAT template, contribute to totals only)
for (let oe_row of frm.doc.opening_entry_table || []) {
let allocated = oe_row.allocated_amount || 0;
let unallocated = oe_row.unallocated_amount || 0;
if (allocated > 0) {
frm.doc.total_allocated_sum += allocated;
}
if (unallocated > 0) {
frm.doc.total_unallocated_sum += unallocated;
}
}
// Refresh the allocated total fields // Refresh the allocated total fields
frm.refresh_field('total_edv_daxil_18'); frm.refresh_field('total_edv_daxil_18');
frm.refresh_field('total_edv_azadolma'); frm.refresh_field('total_edv_azadolma');
@ -1310,6 +1562,9 @@ function calculate_tax_template_totals(frm) {
frm.refresh_field('total_edv_0_unallocated'); frm.refresh_field('total_edv_0_unallocated');
frm.refresh_field('total_edv_18_unallocated'); frm.refresh_field('total_edv_18_unallocated');
frm.refresh_field('total_unallocated_sum'); frm.refresh_field('total_unallocated_sum');
// Refresh opening entry table
frm.refresh_field('opening_entry_table');
} }
function format_currency(value) { function format_currency(value) {

View File

@ -14,6 +14,8 @@
"sales_invoice_table", "sales_invoice_table",
"section_break_fhfs", "section_break_fhfs",
"sales_invoice_previous_table", "sales_invoice_previous_table",
"opening_entry_section",
"opening_entry_table",
"allocation_links_section", "allocation_links_section",
"allocation_links", "allocation_links",
"total_section_break", "total_section_break",
@ -80,6 +82,16 @@
"label": "Sales Invoice", "label": "Sales Invoice",
"options": "VAT allocation sales invoice" "options": "VAT allocation sales invoice"
}, },
{
"fieldname": "opening_entry_section",
"fieldtype": "Section Break"
},
{
"fieldname": "opening_entry_table",
"fieldtype": "Table",
"label": "Opening Entry",
"options": "VAT allocation opening entry"
},
{ {
"fieldname": "allocation_links_section", "fieldname": "allocation_links_section",
"fieldtype": "Section Break", "fieldtype": "Section Break",
@ -235,7 +247,7 @@
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2026-02-06 15:29:21.644114", "modified": "2026-02-23 22:13:47.185401",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Taxes Az", "module": "Taxes Az",
"name": "VAT allocation", "name": "VAT allocation",

View File

@ -79,7 +79,10 @@ class VATallocation(Document):
for si_row in self.sales_invoice_previous_table: for si_row in self.sales_invoice_previous_table:
si_row.allocated_amount = 0 si_row.allocated_amount = 0
for oe_row in self.opening_entry_table:
oe_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))
@ -114,24 +117,37 @@ class VATallocation(Document):
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 oe_row in self.opening_entry_table:
if oe_row.name == link.sales_invoice_row:
current_allocated = Decimal(str(oe_row.allocated_amount or 0))
oe_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))
allocated = Decimal(str(pe_row.allocated_amount or 0)) allocated = Decimal(str(pe_row.allocated_amount or 0))
pe_row.unallocated_amount = float(max(Decimal('0'), total - allocated)) pe_row.unallocated_amount = float(max(Decimal('0'), total - allocated))
for si_row in self.sales_invoice_table: for si_row in self.sales_invoice_table:
total = Decimal(str(si_row.amount or 0)) total = 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))
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: for si_row in self.sales_invoice_previous_table:
total = Decimal(str(si_row.amount or 0)) total = 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))
si_row.unallocated_amount = float(max(Decimal('0'), total - allocated)) si_row.unallocated_amount = float(max(Decimal('0'), total - allocated))
for oe_row in self.opening_entry_table:
total = Decimal(str(oe_row.amount or 0))
allocated = Decimal(str(oe_row.allocated_amount or 0))
oe_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()
@ -188,6 +204,15 @@ class VATallocation(Document):
if unallocated > 0: if unallocated > 0:
self.total_unallocated_sum = float(Decimal(str(self.total_unallocated_sum or 0)) + unallocated) self.total_unallocated_sum = float(Decimal(str(self.total_unallocated_sum or 0)) + unallocated)
# Add opening entry rows to totals (no VAT template, contribute to totals only)
for oe_row in self.opening_entry_table:
allocated = Decimal(str(oe_row.allocated_amount or 0))
unallocated = Decimal(str(oe_row.unallocated_amount or 0))
if allocated > 0:
self.total_allocated_sum = float(Decimal(str(self.total_allocated_sum or 0)) + allocated)
if unallocated > 0:
self.total_unallocated_sum = float(Decimal(str(self.total_unallocated_sum or 0)) + unallocated)
def validate_allocations(self): def validate_allocations(self):
""" """
Validate that all allocations are mathematically correct Validate that all allocations are mathematically correct
@ -229,16 +254,30 @@ class VATallocation(Document):
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))
unallocated = Decimal(str(si_row.unallocated_amount or 0)) unallocated = Decimal(str(si_row.unallocated_amount or 0))
# Check non-negative # Check non-negative
if allocated < 0 or unallocated < -ROUNDING_TOLERANCE: if allocated < 0 or unallocated < -ROUNDING_TOLERANCE:
frappe.throw(_(f"Allocated amounts cannot be negative for previous month {si_row.item_tax_template}")) frappe.throw(_(f"Allocated amounts cannot be negative for previous month {si_row.item_tax_template}"))
# Check sum equals total (with tolerance) # Check sum equals total (with tolerance)
total_check = allocated + unallocated total_check = allocated + unallocated
if abs(total_check - amount) > ROUNDING_TOLERANCE: if abs(total_check - amount) > ROUNDING_TOLERANCE:
frappe.throw(_(f"Allocated + Unallocated != Amount for previous month {si_row.item_tax_template}")) frappe.throw(_(f"Allocated + Unallocated != Amount for previous month {si_row.item_tax_template}"))
for oe_row in self.opening_entry_table:
amount = Decimal(str(oe_row.amount or 0))
allocated = Decimal(str(oe_row.allocated_amount or 0))
unallocated = Decimal(str(oe_row.unallocated_amount or 0))
# Check non-negative
if allocated < 0 or unallocated < -ROUNDING_TOLERANCE:
frappe.throw(_(f"Allocated amounts cannot be negative for opening entry {oe_row.customer}"))
# Check sum equals total (with tolerance)
total_check = allocated + unallocated
if abs(total_check - amount) > ROUNDING_TOLERANCE:
frappe.throw(_(f"Allocated + Unallocated != Amount for opening entry {oe_row.customer}"))
@frappe.whitelist() @frappe.whitelist()
def validate_all_allocations(vat_allocation_name): def validate_all_allocations(vat_allocation_name):
""" """
@ -685,7 +724,7 @@ def get_allocation_data(vat_allocation_name, payment_entry_row_name, customer):
if item.customer == customer: if item.customer == customer:
# Get existing allocation for this item # Get existing allocation for this item
existing_alloc = existing_allocations.get(item.name, {'amount': 0, 'type': None}) existing_alloc = existing_allocations.get(item.name, {'amount': 0, 'type': None})
item_data = { item_data = {
'name': item.name, 'name': item.name,
'item_tax_template': item.item_tax_template or '', 'item_tax_template': item.item_tax_template or '',
@ -695,16 +734,32 @@ def get_allocation_data(vat_allocation_name, payment_entry_row_name, customer):
'allocated_amount': item.allocated_amount or 0, 'allocated_amount': item.allocated_amount or 0,
'current_allocation': existing_alloc['amount'] # Allocation from THIS payment row 'current_allocation': existing_alloc['amount'] # Allocation from THIS payment row
} }
template = (item.item_tax_template or '').lower() template = (item.item_tax_template or '').lower()
# Check if it's VAT free template # Check if it's VAT free template
if 'ədv 0%' in template or 'ədv-dən azadolma' in template: if 'ədv 0%' in template or 'ədv-dən azadolma' in template:
vat_free_items_previous.append(item_data) vat_free_items_previous.append(item_data)
# Check if it's VAT 18% template # Check if it's VAT 18% template
elif 'ədv 18%' in template or 'ədv daxil 18%' in template: elif 'ədv 18%' in template or 'ədv daxil 18%' in template:
vat_items_previous.append(item_data) vat_items_previous.append(item_data)
# Opening entry items
opening_entry_items = []
for item in doc.opening_entry_table:
if item.customer == customer:
existing_alloc = existing_allocations.get(item.name, {'amount': 0, 'type': None})
item_data = {
'name': item.name,
'amount': item.amount,
'unallocated_amount': item.unallocated_amount,
'allocated_amount': item.allocated_amount or 0,
'current_allocation': existing_alloc['amount']
}
opening_entry_items.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,
@ -712,7 +767,8 @@ def get_allocation_data(vat_allocation_name, payment_entry_row_name, customer):
'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_free_items_previous': vat_free_items_previous,
'vat_items_previous': vat_items_previous 'vat_items_previous': vat_items_previous,
'opening_entry_items': opening_entry_items
} }
def round_decimal(value, precision=2): def round_decimal(value, precision=2):
@ -791,9 +847,10 @@ 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 - check both tables # Validate each SI item - check all three 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}) si_items_map.update({item.name: item for item in doc.sales_invoice_previous_table})
si_items_map.update({item.name: item for item in doc.opening_entry_table})
for allocation in allocations: for allocation in allocations:
si_row_name = allocation['name'] si_row_name = allocation['name']
@ -982,6 +1039,63 @@ def calculate_auto_allocation(vat_free_available, vat_allocated_available, vat_f
return allocations return allocations
@frappe.whitelist()
def get_opening_entries(year, month, customers=None, min_amount=None, max_amount=None):
"""
Get Journal Entries with voucher_type='Opening Entry', grouped by customer.
Customer = party from Journal Entry Account rows where party_type='Customer'.
Amount = SUM(debit) for those rows.
"""
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"))
conditions = """
je.docstatus = 1
AND je.voucher_type = 'Opening Entry'
AND YEAR(je.posting_date) = %(year)s
AND MONTH(je.posting_date) = %(month)s
AND jea.party_type = 'Customer'
AND jea.party IS NOT NULL AND jea.party != ''
"""
params = {'year': year, 'month': month_number}
if customers:
if isinstance(customers, str):
customers = json.loads(customers)
customer_list = [c.get('customer') for c in customers if c.get('customer')]
if customer_list:
conditions += " AND jea.party IN %(customers)s"
params['customers'] = customer_list
having_conditions = []
if min_amount:
having_conditions.append("SUM(jea.debit) >= %(min_amount)s")
params['min_amount'] = min_amount
if max_amount:
having_conditions.append("SUM(jea.debit) <= %(max_amount)s")
params['max_amount'] = max_amount
having_clause = (" HAVING " + " AND ".join(having_conditions)) if having_conditions else ""
return frappe.db.sql(f"""
SELECT
jea.party AS customer,
SUM(jea.debit) AS amount
FROM `tabJournal Entry` je
JOIN `tabJournal Entry Account` jea ON jea.parent = je.name
WHERE {conditions}
GROUP BY jea.party
{having_clause}
ORDER BY jea.party
""", params, as_dict=1)
def recalculate_totals_from_links(self): def recalculate_totals_from_links(self):
""" """
Recalculate all allocated/unallocated amounts from allocation links Recalculate all allocated/unallocated amounts from allocation links
@ -991,25 +1105,28 @@ def recalculate_totals_from_links(self):
pe_row.vat_free_allocated = 0 pe_row.vat_free_allocated = 0
pe_row.vat_allocated_allocated = 0 pe_row.vat_allocated_allocated = 0
pe_row.allocated_amount = 0 pe_row.allocated_amount = 0
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: for si_row in self.sales_invoice_previous_table:
si_row.allocated_amount = 0 si_row.allocated_amount = 0
for oe_row in self.opening_entry_table:
oe_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))
if allocated <= 0: if allocated <= 0:
continue continue
# Update payment entry row # Update payment entry row
for pe_row in self.payment_entry_table: for pe_row in self.payment_entry_table:
if pe_row.name == link.payment_entry_row: if pe_row.name == link.payment_entry_row:
current_allocated = Decimal(str(pe_row.allocated_amount or 0)) current_allocated = Decimal(str(pe_row.allocated_amount or 0))
pe_row.allocated_amount = float(current_allocated + allocated) pe_row.allocated_amount = float(current_allocated + allocated)
if link.allocation_type == 'vat_free': if link.allocation_type == 'vat_free':
current_vat_free = Decimal(str(pe_row.vat_free_allocated or 0)) current_vat_free = Decimal(str(pe_row.vat_free_allocated or 0))
pe_row.vat_free_allocated = float(current_vat_free + allocated) pe_row.vat_free_allocated = float(current_vat_free + allocated)
@ -1017,8 +1134,8 @@ def recalculate_totals_from_links(self):
current_vat_allocated = Decimal(str(pe_row.vat_allocated_allocated or 0)) current_vat_allocated = Decimal(str(pe_row.vat_allocated_allocated or 0))
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 - check both tables # Update sales invoice row - check all three tables
found = False 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:
@ -1026,30 +1143,43 @@ def recalculate_totals_from_links(self):
si_row.allocated_amount = float(current_allocated + allocated) si_row.allocated_amount = float(current_allocated + allocated)
found = True found = True
break break
if not found: if not found:
for si_row in self.sales_invoice_previous_table: for si_row in self.sales_invoice_previous_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 oe_row in self.opening_entry_table:
if oe_row.name == link.sales_invoice_row:
current_allocated = Decimal(str(oe_row.allocated_amount or 0))
oe_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))
allocated = Decimal(str(pe_row.allocated_amount or 0)) allocated = Decimal(str(pe_row.allocated_amount or 0))
pe_row.unallocated_amount = float(max(Decimal('0'), total - allocated)) pe_row.unallocated_amount = float(max(Decimal('0'), total - allocated))
for si_row in self.sales_invoice_table: for si_row in self.sales_invoice_table:
total = Decimal(str(si_row.amount or 0)) total = 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))
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: for si_row in self.sales_invoice_previous_table:
total = Decimal(str(si_row.amount or 0)) total = 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))
si_row.unallocated_amount = float(max(Decimal('0'), total - allocated)) si_row.unallocated_amount = float(max(Decimal('0'), total - allocated))
for oe_row in self.opening_entry_table:
total = Decimal(str(oe_row.amount or 0))
allocated = Decimal(str(oe_row.allocated_amount or 0))
oe_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()
@ -1106,6 +1236,15 @@ def calculate_tax_template_totals(self):
if unallocated > 0: if unallocated > 0:
self.total_unallocated_sum = float(Decimal(str(self.total_unallocated_sum or 0)) + unallocated) self.total_unallocated_sum = float(Decimal(str(self.total_unallocated_sum or 0)) + unallocated)
# Add opening entry rows to totals (no VAT template, contribute to totals only)
for oe_row in self.opening_entry_table:
allocated = Decimal(str(oe_row.allocated_amount or 0))
unallocated = Decimal(str(oe_row.unallocated_amount or 0))
if allocated > 0:
self.total_allocated_sum = float(Decimal(str(self.total_allocated_sum or 0)) + allocated)
if unallocated > 0:
self.total_unallocated_sum = float(Decimal(str(self.total_unallocated_sum or 0)) + unallocated)
@frappe.whitelist() @frappe.whitelist()
def get_payment_entries_previous_months(year, month, customers=None, min_amount=None, max_amount=None): def get_payment_entries_previous_months(year, month, customers=None, min_amount=None, max_amount=None):
""" """

View File

@ -0,0 +1,68 @@
{
"actions": [],
"allow_rename": 1,
"creation": "2026-02-23 00:00:00.000000",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"customer",
"journal_entry",
"amount",
"allocated_amount",
"unallocated_amount"
],
"fields": [
{
"fieldname": "customer",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Customer",
"options": "Customer",
"read_only": 1
},
{
"fieldname": "journal_entry",
"fieldtype": "Link",
"hidden": 1,
"label": "Journal Entry",
"options": "Journal Entry"
},
{
"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
},
{
"fieldname": "amount",
"fieldtype": "Currency",
"in_list_view": 1,
"label": "Amount",
"precision": "2",
"read_only": 1
}
],
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2026-02-23 00:00:00.000000",
"modified_by": "Administrator",
"module": "Taxes Az",
"name": "VAT allocation opening entry",
"owner": "Administrator",
"permissions": [],
"sort_field": "creation",
"sort_order": "DESC",
"states": []
}

View File

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