opening entryies has been added to vat allocation
This commit is contained in:
parent
57a5b0ac32
commit
d5a9a7b14b
|
|
@ -10,6 +10,11 @@ frappe.ui.form.on('VAT allocation', {
|
|||
frm.add_custom_button(__('Fill Sales Invoice Items'), function() {
|
||||
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
|
||||
|
|
@ -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
|
||||
function setup_journal_entry_query(frm) {
|
||||
frm.fields_dict.payment_entry_table.grid.get_field('vat_journal_entry').get_query = function(doc, cdt, cdn) {
|
||||
|
|
@ -349,6 +456,7 @@ frappe.ui.form.on('VAT allocation payment entry', {
|
|||
frm.refresh_field('payment_entry_table');
|
||||
frm.refresh_field('sales_invoice_table');
|
||||
frm.refresh_field('sales_invoice_previous_table');
|
||||
frm.refresh_field('opening_entry_table');
|
||||
},
|
||||
|
||||
// При изменении Payment Entry - обновить суммы
|
||||
|
|
@ -376,6 +484,7 @@ frappe.ui.form.on('VAT allocation payment entry', {
|
|||
frm.refresh_field('payment_entry_table');
|
||||
frm.refresh_field('sales_invoice_table');
|
||||
frm.refresh_field('sales_invoice_previous_table');
|
||||
frm.refresh_field('opening_entry_table');
|
||||
|
||||
return;
|
||||
}
|
||||
|
|
@ -437,6 +546,7 @@ frappe.ui.form.on('VAT allocation payment entry', {
|
|||
frm.refresh_field('payment_entry_table');
|
||||
frm.refresh_field('sales_invoice_table');
|
||||
frm.refresh_field('sales_invoice_previous_table');
|
||||
frm.refresh_field('opening_entry_table');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
@ -462,6 +572,7 @@ frappe.ui.form.on('VAT allocation payment entry', {
|
|||
frm.refresh_field('payment_entry_table');
|
||||
frm.refresh_field('sales_invoice_table');
|
||||
frm.refresh_field('sales_invoice_previous_table');
|
||||
frm.refresh_field('opening_entry_table');
|
||||
},
|
||||
|
||||
vat_journal_entry: function(frm, cdt, cdn) {
|
||||
|
|
@ -647,9 +758,35 @@ 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
|
||||
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'));
|
||||
return;
|
||||
}
|
||||
|
|
@ -667,7 +804,8 @@ function show_allocation_dialog(frm, payment_row_idx, customer) {
|
|||
vat_free_items: vat_free_items,
|
||||
vat_items: vat_items,
|
||||
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
|
||||
|
|
@ -677,6 +815,7 @@ function show_allocation_dialog(frm, payment_row_idx, customer) {
|
|||
function create_allocation_dialog(frm, payment_row_idx, data) {
|
||||
let allocation_data_current = {};
|
||||
let allocation_data_previous = {};
|
||||
let allocation_data_opening = {};
|
||||
|
||||
let dialog = new frappe.ui.Dialog({
|
||||
title: __('Allocate Amount'),
|
||||
|
|
@ -725,7 +864,7 @@ function create_allocation_dialog(frm, payment_row_idx, data) {
|
|||
],
|
||||
primary_action_label: __('Save Allocation'),
|
||||
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: function() {
|
||||
|
|
@ -736,13 +875,18 @@ function create_allocation_dialog(frm, payment_row_idx, data) {
|
|||
let available_amounts = calculate_available_amounts(
|
||||
data,
|
||||
allocation_data_current,
|
||||
allocation_data_previous
|
||||
allocation_data_previous,
|
||||
allocation_data_opening
|
||||
);
|
||||
|
||||
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 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 {
|
||||
auto_allocate(frm, payment_row_idx, data, data.vat_free_items_previous, data.vat_items_previous,
|
||||
allocation_data_previous, dialog, 'previous', available_amounts,
|
||||
|
|
@ -771,6 +915,7 @@ function create_allocation_dialog(frm, payment_row_idx, data) {
|
|||
// 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');
|
||||
let html_opening = build_allocation_html(data.opening_entry_items, [], allocation_data_opening, 'opening');
|
||||
|
||||
let tabs_html = `
|
||||
<style>
|
||||
|
|
@ -809,6 +954,11 @@ function create_allocation_dialog(frm, payment_row_idx, data) {
|
|||
${__('Previous Months')}
|
||||
</a>
|
||||
</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>
|
||||
<div class="tab-content">
|
||||
<div class="tab-pane fade show active" id="tab-current" role="tabpanel">
|
||||
|
|
@ -817,6 +967,9 @@ function create_allocation_dialog(frm, payment_row_idx, data) {
|
|||
<div class="tab-pane fade" id="tab-previous" role="tabpanel">
|
||||
${html_previous}
|
||||
</div>
|
||||
<div class="tab-pane fade" id="tab-opening" role="tabpanel">
|
||||
${html_opening}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
|
|
@ -839,18 +992,21 @@ function create_allocation_dialog(frm, payment_row_idx, data) {
|
|||
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_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();
|
||||
}
|
||||
|
||||
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_allocated_current = 0;
|
||||
let total_vat_free_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
|
||||
for (let name in allocation_data_current) {
|
||||
|
|
@ -872,9 +1028,19 @@ function calculate_available_amounts(data, allocation_data_current, allocation_d
|
|||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
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;
|
||||
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 - total_vat_allocated_opening;
|
||||
|
||||
return {
|
||||
vat_free_available: Math.max(0, vat_free_available),
|
||||
|
|
@ -885,6 +1051,48 @@ function calculate_available_amounts(data, allocation_data_current, allocation_d
|
|||
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;">';
|
||||
|
||||
// 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
|
||||
if (vat_free_items.length > 0) {
|
||||
html += '<h5 style="margin-top: 10px; color: #2490ef;">VAT Free Items (ƏDV 0%, ƏDV-dən azadolma)</h5>';
|
||||
|
|
@ -1062,7 +1270,8 @@ function auto_allocate(frm, payment_row_idx, data, vat_free_items, vat_items, al
|
|||
// 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
|
||||
tab_type === 'previous' ? allocation_data : other_allocation_data,
|
||||
tab_type === 'opening' ? allocation_data : {}
|
||||
);
|
||||
|
||||
dialog.set_value('vat_free_available', new_available.vat_free_available);
|
||||
|
|
@ -1078,8 +1287,8 @@ 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_current, allocation_data_previous, dialog) {
|
||||
// Prepare allocations array - combine both tabs
|
||||
function save_allocation_data(frm, payment_row_idx, allocation_data_current, allocation_data_previous, allocation_data_opening, dialog) {
|
||||
// Prepare allocations array - combine all tabs
|
||||
let allocations = [];
|
||||
|
||||
// Add allocations from current month tab
|
||||
|
|
@ -1102,6 +1311,16 @@ function save_allocation_data(frm, payment_row_idx, allocation_data_current, all
|
|||
});
|
||||
}
|
||||
|
||||
// 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
|
||||
let payment_row = null;
|
||||
for (let row of frm.doc.payment_entry_table || []) {
|
||||
|
|
@ -1148,6 +1367,7 @@ function save_allocation_data(frm, payment_row_idx, allocation_data_current, all
|
|||
frm.refresh_field('payment_entry_table');
|
||||
frm.refresh_field('sales_invoice_table');
|
||||
frm.refresh_field('sales_invoice_previous_table');
|
||||
frm.refresh_field('opening_entry_table');
|
||||
|
||||
// Помечаем документ как измененный
|
||||
frm.dirty();
|
||||
|
|
@ -1172,6 +1392,10 @@ function recalculate_totals_manually(frm) {
|
|||
si_row.allocated_amount = 0;
|
||||
}
|
||||
|
||||
for (let oe_row of frm.doc.opening_entry_table || []) {
|
||||
oe_row.allocated_amount = 0;
|
||||
}
|
||||
|
||||
// Calculate from links
|
||||
for (let link of frm.doc.allocation_links || []) {
|
||||
let allocated = link.allocated_amount || 0;
|
||||
|
|
@ -1191,7 +1415,7 @@ function recalculate_totals_manually(frm) {
|
|||
}
|
||||
}
|
||||
|
||||
// Update sales invoice row - check both tables
|
||||
// Update sales invoice row - check all three tables
|
||||
let found = false;
|
||||
for (let si_row of frm.doc.sales_invoice_table || []) {
|
||||
if (si_row.name === link.sales_invoice_row) {
|
||||
|
|
@ -1205,6 +1429,16 @@ function recalculate_totals_manually(frm) {
|
|||
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;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
@ -1230,6 +1464,12 @@ function recalculate_totals_manually(frm) {
|
|||
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(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
|
||||
frm.refresh_field('total_edv_daxil_18');
|
||||
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_18_unallocated');
|
||||
frm.refresh_field('total_unallocated_sum');
|
||||
|
||||
// Refresh opening entry table
|
||||
frm.refresh_field('opening_entry_table');
|
||||
}
|
||||
|
||||
function format_currency(value) {
|
||||
|
|
|
|||
|
|
@ -14,6 +14,8 @@
|
|||
"sales_invoice_table",
|
||||
"section_break_fhfs",
|
||||
"sales_invoice_previous_table",
|
||||
"opening_entry_section",
|
||||
"opening_entry_table",
|
||||
"allocation_links_section",
|
||||
"allocation_links",
|
||||
"total_section_break",
|
||||
|
|
@ -80,6 +82,16 @@
|
|||
"label": "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",
|
||||
"fieldtype": "Section Break",
|
||||
|
|
@ -235,7 +247,7 @@
|
|||
"index_web_pages_for_search": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2026-02-06 15:29:21.644114",
|
||||
"modified": "2026-02-23 22:13:47.185401",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Taxes Az",
|
||||
"name": "VAT allocation",
|
||||
|
|
|
|||
|
|
@ -80,6 +80,9 @@ class VATallocation(Document):
|
|||
for si_row in self.sales_invoice_previous_table:
|
||||
si_row.allocated_amount = 0
|
||||
|
||||
for oe_row in self.opening_entry_table:
|
||||
oe_row.allocated_amount = 0
|
||||
|
||||
# Calculate from links
|
||||
for link in self.allocation_links:
|
||||
allocated = Decimal(str(link.allocated_amount or 0))
|
||||
|
|
@ -114,6 +117,14 @@ class VATallocation(Document):
|
|||
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 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
|
||||
|
|
@ -132,6 +143,11 @@ class VATallocation(Document):
|
|||
allocated = Decimal(str(si_row.allocated_amount or 0))
|
||||
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
|
||||
self.calculate_tax_template_totals()
|
||||
|
||||
|
|
@ -188,6 +204,15 @@ class VATallocation(Document):
|
|||
if unallocated > 0:
|
||||
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):
|
||||
"""
|
||||
Validate that all allocations are mathematically correct
|
||||
|
|
@ -239,6 +264,20 @@ class VATallocation(Document):
|
|||
if abs(total_check - amount) > ROUNDING_TOLERANCE:
|
||||
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()
|
||||
def validate_all_allocations(vat_allocation_name):
|
||||
"""
|
||||
|
|
@ -705,6 +744,22 @@ 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_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 {
|
||||
'payment_row': payment_row,
|
||||
'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_items': vat_items,
|
||||
'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):
|
||||
|
|
@ -791,9 +847,10 @@ 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 - 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.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:
|
||||
si_row_name = allocation['name']
|
||||
|
|
@ -982,6 +1039,63 @@ def calculate_auto_allocation(vat_free_available, vat_allocated_available, vat_f
|
|||
|
||||
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):
|
||||
"""
|
||||
Recalculate all allocated/unallocated amounts from allocation links
|
||||
|
|
@ -998,6 +1112,9 @@ def recalculate_totals_from_links(self):
|
|||
for si_row in self.sales_invoice_previous_table:
|
||||
si_row.allocated_amount = 0
|
||||
|
||||
for oe_row in self.opening_entry_table:
|
||||
oe_row.allocated_amount = 0
|
||||
|
||||
# Calculate from links
|
||||
for link in self.allocation_links:
|
||||
allocated = Decimal(str(link.allocated_amount or 0))
|
||||
|
|
@ -1018,7 +1135,7 @@ def recalculate_totals_from_links(self):
|
|||
pe_row.vat_allocated_allocated = float(current_vat_allocated + allocated)
|
||||
break
|
||||
|
||||
# Update sales invoice row - check both tables
|
||||
# Update sales invoice row - check all three tables
|
||||
found = False
|
||||
for si_row in self.sales_invoice_table:
|
||||
if si_row.name == link.sales_invoice_row:
|
||||
|
|
@ -1032,6 +1149,14 @@ def recalculate_totals_from_links(self):
|
|||
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 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
|
||||
|
|
@ -1050,6 +1175,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 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
|
||||
self.calculate_tax_template_totals()
|
||||
|
||||
|
|
@ -1106,6 +1236,15 @@ def calculate_tax_template_totals(self):
|
|||
if unallocated > 0:
|
||||
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()
|
||||
def get_payment_entries_previous_months(year, month, customers=None, min_amount=None, max_amount=None):
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -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": []
|
||||
}
|
||||
|
|
@ -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
|
||||
Loading…
Reference in New Issue