added previous monthes to vat allocation
This commit is contained in:
parent
d61275ca85
commit
6321a10b7c
|
|
@ -14,6 +14,10 @@ frappe.ui.form.on('VAT allocation', {
|
|||
|
||||
// Setup filter for Journal Entry
|
||||
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);
|
||||
}
|
||||
|
||||
// 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) {
|
||||
// Call server method
|
||||
// Call server method to get both current and previous payment entries
|
||||
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: {
|
||||
year: frm.doc.year,
|
||||
month: frm.doc.month,
|
||||
|
|
@ -184,8 +188,14 @@ function fill_payment_entries(frm, filters) {
|
|||
row.customer = payment.customer;
|
||||
row.payment_entry = payment.payment_entry;
|
||||
row.payment_entry_amount = payment.payment_entry_amount;
|
||||
row.vat_free_amount = payment.payment_entry_amount; // Default: all amount is VAT free
|
||||
row.unallocated_amount = payment.payment_entry_amount;
|
||||
row.vat_journal_entry = payment.vat_journal_entry || '';
|
||||
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');
|
||||
|
|
@ -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) {
|
||||
// Call server method
|
||||
// Call both server methods - current month and previous months
|
||||
frappe.call({
|
||||
method: 'taxes_az.taxes_az.doctype.vat_allocation.vat_allocation.get_sales_invoice_items',
|
||||
args: {
|
||||
|
|
@ -229,6 +252,52 @@ function fill_sales_invoice_items(frm, filters) {
|
|||
} else {
|
||||
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);
|
||||
frm.refresh_field('payment_entry_table');
|
||||
frm.refresh_field('sales_invoice_table');
|
||||
frm.refresh_field('sales_invoice_previous_table');
|
||||
},
|
||||
|
||||
// При изменении Payment Entry - обновить суммы
|
||||
|
|
@ -305,6 +375,7 @@ frappe.ui.form.on('VAT allocation payment entry', {
|
|||
recalculate_totals_manually(frm);
|
||||
frm.refresh_field('payment_entry_table');
|
||||
frm.refresh_field('sales_invoice_table');
|
||||
frm.refresh_field('sales_invoice_previous_table');
|
||||
|
||||
return;
|
||||
}
|
||||
|
|
@ -365,6 +436,7 @@ frappe.ui.form.on('VAT allocation payment entry', {
|
|||
recalculate_totals_manually(frm);
|
||||
frm.refresh_field('payment_entry_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);
|
||||
frm.refresh_field('payment_entry_table');
|
||||
frm.refresh_field('sales_invoice_table');
|
||||
frm.refresh_field('sales_invoice_previous_table');
|
||||
},
|
||||
|
||||
vat_journal_entry: function(frm, cdt, cdn) {
|
||||
|
|
@ -501,7 +574,10 @@ function show_allocation_dialog(frm, payment_row_idx, customer) {
|
|||
// Получаем sales invoice items для этого клиента из формы
|
||||
let vat_free_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 || []) {
|
||||
if (item.customer !== customer) {
|
||||
continue;
|
||||
|
|
@ -529,7 +605,6 @@ function show_allocation_dialog(frm, payment_row_idx, customer) {
|
|||
|
||||
let template = (item.item_tax_template || '').toLowerCase();
|
||||
|
||||
// ИСПРАВЛЕНО: используем .includes() вместо 'in'
|
||||
if (template.includes('ədv 0%') || template.includes('ədv-dən azadolma')) {
|
||||
vat_free_items.push(item_data);
|
||||
} else if (template.includes('ədv 18%') || template.includes('ədv daxil 18%')) {
|
||||
|
|
@ -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
|
||||
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'));
|
||||
return;
|
||||
}
|
||||
|
|
@ -554,19 +665,22 @@ function show_allocation_dialog(frm, payment_row_idx, customer) {
|
|||
vat_free_available: vat_free_amount - vat_free_allocated,
|
||||
vat_allocated_available: vat_allocated_amount - vat_allocated_allocated,
|
||||
vat_free_items: vat_free_items,
|
||||
vat_items: vat_items
|
||||
vat_items: vat_items,
|
||||
vat_free_items_previous: vat_free_items_previous,
|
||||
vat_items_previous: vat_items_previous
|
||||
};
|
||||
|
||||
// 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) {
|
||||
let allocation_data = {};
|
||||
function create_allocation_dialog(frm, payment_row_idx, data) {
|
||||
let allocation_data_current = {};
|
||||
let allocation_data_previous = {};
|
||||
|
||||
let dialog = new frappe.ui.Dialog({
|
||||
title: __('Allocate Amount'),
|
||||
size: 'large',
|
||||
size: 'extra-large',
|
||||
fields: [
|
||||
{
|
||||
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
|
||||
},
|
||||
{
|
||||
fieldname: 'items_section',
|
||||
fieldtype: 'Section Break',
|
||||
label: __('Sales Invoice Items - Edit Allocations')
|
||||
fieldname: 'tabs_section',
|
||||
fieldtype: 'Section Break'
|
||||
},
|
||||
{
|
||||
fieldname: 'allocation_html',
|
||||
fieldname: 'tabs_html',
|
||||
fieldtype: 'HTML'
|
||||
}
|
||||
],
|
||||
primary_action_label: __('Save Allocation'),
|
||||
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: 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);
|
||||
|
||||
// Build allocation HTML
|
||||
let html = build_allocation_html(vat_free_items, vat_items, allocation_data);
|
||||
dialog.fields_dict.allocation_html.$wrapper.html(html);
|
||||
// Build tabs HTML with Bootstrap
|
||||
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');
|
||||
|
||||
// Setup input handlers
|
||||
setup_allocation_inputs(dialog, allocation_data, data.vat_free_available, data.vat_allocated_available);
|
||||
let tabs_html = `
|
||||
<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();
|
||||
}
|
||||
|
||||
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;">';
|
||||
|
||||
// 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.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" 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>';
|
||||
});
|
||||
|
||||
|
|
@ -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.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" 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>';
|
||||
});
|
||||
|
||||
|
|
@ -709,13 +944,13 @@ function build_allocation_html(vat_free_items, vat_items, allocation_data) {
|
|||
// Summary section
|
||||
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="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 to Allocate:</strong> <span id="total_vat_allocated" class="text-warning">' + 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_' + tab_type + '" class="text-warning">' + 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" 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>';
|
||||
|
||||
|
|
@ -724,15 +959,16 @@ function build_allocation_html(vat_free_items, vat_items, allocation_data) {
|
|||
return html;
|
||||
}
|
||||
|
||||
function setup_allocation_inputs(dialog, allocation_data, max_vat_free, max_vat_allocated) {
|
||||
let $wrapper = dialog.fields_dict.allocation_html.$wrapper;
|
||||
function setup_allocation_inputs(dialog, allocation_data, max_vat_free, max_vat_allocated, tab_type) {
|
||||
// Ищем wrapper по ID вкладки в общем wrapper диалога
|
||||
let $wrapper = dialog.$wrapper.find('#tab-' + tab_type);
|
||||
|
||||
// Initial calculation
|
||||
setTimeout(function() {
|
||||
$wrapper.find('.allocation-input').first().trigger('input');
|
||||
$wrapper.find('.allocation-input-' + tab_type).first().trigger('input');
|
||||
}, 100);
|
||||
|
||||
$wrapper.find('.allocation-input').on('input', function() {
|
||||
$wrapper.find('.allocation-input-' + tab_type).on('input', function() {
|
||||
let $input = $(this);
|
||||
let name = $input.data('name');
|
||||
let type = $input.data('type');
|
||||
|
|
@ -755,11 +991,11 @@ function setup_allocation_inputs(dialog, allocation_data, max_vat_free, max_vat_
|
|||
type: type
|
||||
};
|
||||
|
||||
// Calculate totals
|
||||
// Calculate totals for this tab only
|
||||
let total_vat_free = 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_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;
|
||||
}
|
||||
|
||||
|
||||
// Update summary
|
||||
$wrapper.find('#total_vat_free').text($(frappe.format(total_vat_free, {fieldtype: 'Currency'})).text());
|
||||
$wrapper.find('#total_vat_allocated').text($(frappe.format(total_vat_allocated, {fieldtype: 'Currency'})).text());
|
||||
$wrapper.find('#total_allocated').text($(frappe.format(total_vat_free + total_vat_allocated, {fieldtype: 'Currency'})).text());
|
||||
// Update summary for this tab
|
||||
$wrapper.find('#total_vat_free_' + tab_type).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_allocated_' + tab_type).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) {
|
||||
// Call server method to calculate auto allocation
|
||||
function auto_allocate(frm, payment_row_idx, data, vat_free_items, vat_items, allocation_data, dialog, tab_type, available_amounts, other_allocation_data) {
|
||||
// Use calculated available amounts
|
||||
frappe.call({
|
||||
method: 'taxes_az.taxes_az.doctype.vat_allocation.vat_allocation.calculate_auto_allocation',
|
||||
args: {
|
||||
vat_free_available: data.vat_free_available,
|
||||
vat_allocated_available: data.vat_allocated_available,
|
||||
vat_free_available: available_amounts.vat_free_available,
|
||||
vat_allocated_available: available_amounts.vat_allocated_available,
|
||||
vat_free_items: JSON.stringify(vat_free_items),
|
||||
vat_items: JSON.stringify(vat_items)
|
||||
},
|
||||
callback: function(r) {
|
||||
if (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
|
||||
$wrapper.find('.allocation-input').val(0);
|
||||
// Reset all inputs for this tab
|
||||
$wrapper.find('.allocation-input-' + tab_type).val(0);
|
||||
|
||||
// Update 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
|
||||
allocations.forEach(function(alloc) {
|
||||
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
|
||||
$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({
|
||||
message: __('Auto allocation completed'),
|
||||
|
|
@ -834,12 +1078,23 @@ function auto_allocate(frm, payment_row_idx, data, vat_free_items, vat_items, al
|
|||
}
|
||||
|
||||
// Сохранение allocation - работает с несохраненным документом
|
||||
function save_allocation_data(frm, payment_row_idx, allocation_data, dialog) {
|
||||
// Prepare allocations array
|
||||
function save_allocation_data(frm, payment_row_idx, allocation_data_current, allocation_data_previous, dialog) {
|
||||
// Prepare allocations array - combine both tabs
|
||||
let allocations = [];
|
||||
|
||||
for (let name in allocation_data) {
|
||||
let data = allocation_data[name];
|
||||
// Add allocations from current month tab
|
||||
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({
|
||||
name: name,
|
||||
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('payment_entry_table');
|
||||
frm.refresh_field('sales_invoice_table');
|
||||
frm.refresh_field('sales_invoice_previous_table');
|
||||
|
||||
// Помечаем документ как измененный
|
||||
frm.dirty();
|
||||
|
|
@ -899,7 +1155,7 @@ function save_allocation_data(frm, payment_row_idx, allocation_data, dialog) {
|
|||
dialog.hide();
|
||||
}
|
||||
|
||||
// Функция для пересчета totals без сохранения в БД
|
||||
// Функция для пересчета totals без сохранения в БД - UPDATED
|
||||
function recalculate_totals_manually(frm) {
|
||||
// Reset all allocated amounts
|
||||
for (let pe_row of frm.doc.payment_entry_table || []) {
|
||||
|
|
@ -912,6 +1168,10 @@ function recalculate_totals_manually(frm) {
|
|||
si_row.allocated_amount = 0;
|
||||
}
|
||||
|
||||
for (let si_row of frm.doc.sales_invoice_previous_table || []) {
|
||||
si_row.allocated_amount = 0;
|
||||
}
|
||||
|
||||
// Calculate from links
|
||||
for (let link of frm.doc.allocation_links || []) {
|
||||
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 || []) {
|
||||
if (si_row.name === link.sales_invoice_row) {
|
||||
si_row.allocated_amount = (si_row.allocated_amount || 0) + allocated;
|
||||
found = true;
|
||||
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
|
||||
|
|
@ -953,6 +1224,12 @@ function recalculate_totals_manually(frm) {
|
|||
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(frm);
|
||||
}
|
||||
|
|
@ -966,7 +1243,7 @@ function calculate_tax_template_totals(frm) {
|
|||
frm.doc.total_edv_18 = 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 || []) {
|
||||
let allocated = si_row.allocated_amount || 0;
|
||||
if (allocated <= 0) continue;
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@
|
|||
"payment_entry_table",
|
||||
"sales_invoice_section",
|
||||
"sales_invoice_table",
|
||||
"section_break_fhfs",
|
||||
"sales_invoice_previous_table",
|
||||
"allocation_links_section",
|
||||
"allocation_links",
|
||||
"total_edv_18",
|
||||
|
|
@ -146,12 +148,22 @@
|
|||
"print_hide": 1,
|
||||
"read_only": 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,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2025-10-06 16:52:07.347647",
|
||||
"modified": "2025-10-07 14:10:17.385408",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Taxes Az",
|
||||
"name": "VAT allocation",
|
||||
|
|
|
|||
|
|
@ -46,6 +46,9 @@ class VATallocation(Document):
|
|||
for si_row in self.sales_invoice_table:
|
||||
si_row.allocated_amount = 0
|
||||
|
||||
for si_row in self.sales_invoice_previous_table:
|
||||
si_row.allocated_amount = 0
|
||||
|
||||
# Calculate from links
|
||||
for link in self.allocation_links:
|
||||
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)
|
||||
break
|
||||
|
||||
# Update sales invoice row
|
||||
# Update sales invoice row - check both tables
|
||||
found = False
|
||||
for si_row in self.sales_invoice_table:
|
||||
if si_row.name == link.sales_invoice_row:
|
||||
current_allocated = Decimal(str(si_row.allocated_amount or 0))
|
||||
si_row.allocated_amount = float(current_allocated + allocated)
|
||||
found = True
|
||||
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
|
||||
for pe_row in self.payment_entry_table:
|
||||
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))
|
||||
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):
|
||||
"""
|
||||
Validate that all allocations are mathematically correct
|
||||
|
|
@ -106,7 +123,7 @@ class VATallocation(Document):
|
|||
if vat_allocated_spent > vat_allocated_total + ROUNDING_TOLERANCE:
|
||||
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:
|
||||
amount = Decimal(str(si_row.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:
|
||||
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()
|
||||
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
|
||||
|
||||
@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()
|
||||
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_items = []
|
||||
|
||||
vat_free_items_previous = []
|
||||
vat_items_previous = []
|
||||
|
||||
# Current month items
|
||||
for item in doc.sales_invoice_table:
|
||||
if item.customer == customer:
|
||||
# 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:
|
||||
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 {
|
||||
'payment_row': payment_row,
|
||||
'vat_free_available': vat_free_available,
|
||||
'vat_allocated_available': vat_allocated_available,
|
||||
'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):
|
||||
|
|
@ -513,8 +684,9 @@ def save_allocation(vat_allocation_name, payment_entry_row_name, allocations):
|
|||
"Total VAT Allocated ({0}) exceeds total amount ({1})"
|
||||
).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.update({item.name: item for item in doc.sales_invoice_previous_table})
|
||||
|
||||
for allocation in allocations:
|
||||
si_row_name = allocation['name']
|
||||
|
|
@ -716,6 +888,9 @@ def recalculate_totals_from_links(self):
|
|||
for si_row in self.sales_invoice_table:
|
||||
si_row.allocated_amount = 0
|
||||
|
||||
for si_row in self.sales_invoice_previous_table:
|
||||
si_row.allocated_amount = 0
|
||||
|
||||
# Calculate from links
|
||||
for link in self.allocation_links:
|
||||
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)
|
||||
break
|
||||
|
||||
# Update sales invoice row
|
||||
# Update sales invoice row - check both tables
|
||||
found = False
|
||||
for si_row in self.sales_invoice_table:
|
||||
if si_row.name == link.sales_invoice_row:
|
||||
current_allocated = Decimal(str(si_row.allocated_amount or 0))
|
||||
si_row.allocated_amount = float(current_allocated + allocated)
|
||||
found = True
|
||||
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
|
||||
for pe_row in self.payment_entry_table:
|
||||
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))
|
||||
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
|
||||
self.calculate_tax_template_totals()
|
||||
|
||||
|
|
@ -768,7 +957,7 @@ def calculate_tax_template_totals(self):
|
|||
self.total_edv_18 = 0
|
||||
self.total_allocated_sum = 0
|
||||
|
||||
# Calculate totals
|
||||
# Calculate totals - only from current month table
|
||||
for si_row in self.sales_invoice_table:
|
||||
allocated = Decimal(str(si_row.allocated_amount or 0))
|
||||
if allocated <= 0:
|
||||
|
|
@ -788,3 +977,187 @@ def calculate_tax_template_totals(self):
|
|||
|
||||
# Add to total sum
|
||||
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
|
||||
})
|
||||
|
|
@ -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": []
|
||||
}
|
||||
|
|
@ -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
|
||||
Loading…
Reference in New Issue