added 4 columns for vat in sales invoice

This commit is contained in:
Ali 2025-08-21 15:57:34 +04:00
parent 2c8d0ffd1b
commit c515dcf83f
5 changed files with 576 additions and 3 deletions

View File

@ -0,0 +1,147 @@
# Файл: jey_erp/custom/sales_invoice_vat.py
import frappe
import json
from frappe.utils import flt
def set_vat_fields_permissions(doc, method=None):
"""
Set VAT fields permissions based on Item Tax Template
Called on Sales Invoice validate/before_save
"""
if doc.doctype != "Sales Invoice":
return
for item in doc.get("items", []):
set_item_vat_permissions(item)
def set_item_vat_permissions(item):
"""
Set VAT field permissions for individual item based on tax template
"""
if not item.get("item_code"):
return
# Get item tax template
item_tax_template = item.get("item_tax_template")
# If no tax template in item row, get from Item master
if not item_tax_template:
try:
item_doc = frappe.get_doc("Item", item.item_code)
item_tax_template = item_doc.get("item_tax_template")
except Exception:
pass
# Default state - all fields disabled
vat_permissions = {
'vat_18_percent_with_amount': False,
'vat_0_percent_with_amount': False,
'vat_exempt_amount': False,
'not_subject_to_vat': False
}
if item_tax_template:
try:
tax_template = frappe.get_doc("Item Tax Template", item_tax_template)
# Look for tax line with account code 521.3
vat_tax_found = False
vat_rate = None
for template_tax in tax_template.get("taxes", []):
# Get account document to check account code
try:
account_doc = frappe.get_doc("Account", template_tax.tax_type)
account_number = account_doc.get("account_number") or ""
# Check if this is VAT account (521.3)
if account_number == "521.3":
vat_tax_found = True
vat_rate = flt(template_tax.tax_rate)
break
except Exception:
continue
if vat_tax_found:
if vat_rate == 18.0:
# Rule 1: Only 18% field enabled
vat_permissions['vat_18_percent_with_amount'] = True
elif vat_rate == 0.0:
# Rule 2: Only 0% field enabled
vat_permissions['vat_0_percent_with_amount'] = True
else:
# Rule 3: No VAT line found - enable only exempt field
vat_permissions['vat_exempt_amount'] = True
# not_subject_to_vat remains False (disabled)
except Exception:
# Rule 3: Error loading template - enable only exempt field
vat_permissions['vat_exempt_amount'] = True
# not_subject_to_vat remains False (disabled)
else:
# Rule 3: No tax template - enable only exempt field
vat_permissions['vat_exempt_amount'] = True
# not_subject_to_vat remains False (disabled)
# Apply permissions to item
apply_vat_permissions_to_item(item, vat_permissions)
def apply_vat_permissions_to_item(item, permissions):
"""
Apply VAT field permissions to item and clear disabled fields
"""
# Clear values for disabled fields
for field, enabled in permissions.items():
if not enabled and hasattr(item, field):
setattr(item, field, 0.0)
# Calculate amounts for enabled fields
item_amount = flt(item.get("amount", 0))
for field, enabled in permissions.items():
if enabled and hasattr(item, field):
# Only auto-fill if field is empty
current_value = getattr(item, field, 0)
if not current_value or current_value == 0:
if field == 'vat_18_percent_with_amount':
# For 18% VAT: Amount + 18%
vat_amount = item_amount * 1.18
setattr(item, field, vat_amount)
elif field == 'vat_0_percent_with_amount':
# For 0% VAT: just the amount
setattr(item, field, item_amount)
elif field == 'vat_exempt_amount':
# For VAT exempt: auto-fill with item amount
setattr(item, field, item_amount)
# not_subject_to_vat is never auto-filled (always disabled)
# Store permissions in item for client-side use
item.vat_permissions = json.dumps(permissions)
@frappe.whitelist()
def get_vat_permissions_for_item(item_code, item_tax_template=None):
"""
API method to get VAT permissions for an item
Called from client side when item changes
"""
# Create temporary item object
temp_item = frappe._dict({
'item_code': item_code,
'item_tax_template': item_tax_template
})
# Calculate permissions
set_item_vat_permissions(temp_item)
# Return permissions
if hasattr(temp_item, 'vat_permissions'):
return json.loads(temp_item.vat_permissions)
else:
# Default fallback
return {
'vat_18_percent_with_amount': False,
'vat_0_percent_with_amount': False,
'vat_exempt_amount': True,
'not_subject_to_vat': False # Always disabled
}

View File

@ -0,0 +1,59 @@
import frappe
from frappe.custom.doctype.property_setter.property_setter import make_property_setter
def show_item_tax_template_in_sales_invoice():
"""
Создает Property Setters для показа item_tax_template в Sales Invoice Item
"""
property_setters = [
{
'doctype': 'Sales Invoice Item',
'fieldname': 'item_tax_template',
'property': 'hidden',
'value': 0
},
{
'doctype': 'Sales Invoice Item',
'fieldname': 'item_tax_template',
'property': 'in_list_view',
'value': 1
},
{
'doctype': 'Sales Invoice Item',
'fieldname': 'item_tax_template',
'property': 'columns',
'value': 2
},
{
'doctype': 'Sales Invoice Item',
'fieldname': 'item_tax_template',
'property': 'insert_after',
'value': 'item_name'
}
]
for ps in property_setters:
try:
make_property_setter(
doctype=ps['doctype'],
fieldname=ps['fieldname'],
property=ps['property'],
value=ps['value'],
property_type=get_property_type(ps['property'])
)
print(f"✓ Set {ps['fieldname']}.{ps['property']} = {ps['value']}")
except Exception as e:
print(f"✗ Error: {e}")
frappe.clear_cache()
def get_property_type(property_name):
"""Возвращает тип свойства"""
types = {
'hidden': 'Check',
'in_list_view': 'Check',
'columns': 'Int',
'insert_after': 'Data'
}
return types.get(property_name, 'Data')

View File

@ -54,6 +54,44 @@ def create_custom_fields():
hidden=1 hidden=1
) )
], ],
"Sales Order Item": [
dict(
fieldname='vat_18_percent_with_amount',
label='VAT 18% with amount',
fieldtype='Currency',
insert_after='amount',
in_list_view=1,
columns=2,
precision=2
),
dict(
fieldname='vat_0_percent_with_amount',
label='VAT 0% with amount',
fieldtype='Currency',
insert_after='vat_18_percent_with_amount',
in_list_view=1,
columns=2,
precision=2
),
dict(
fieldname='vat_exempt_amount',
label='VAT free amount',
fieldtype='Currency',
insert_after='vat_0_percent_with_amount',
in_list_view=1,
columns=2,
precision=2
),
dict(
fieldname='not_subject_to_vat',
label='Not subject to VAT',
fieldtype='Currency',
insert_after='vat_exempt_amount',
in_list_view=1,
columns=2,
precision=2
)
],
"Sales Invoice": [ "Sales Invoice": [
dict( dict(
fieldname='is_taxes_doc', fieldname='is_taxes_doc',
@ -91,6 +129,44 @@ def create_custom_fields():
insert_after='comments_column_break' insert_after='comments_column_break'
) )
], ],
"Sales Invoice Item": [
dict(
fieldname='vat_18_percent_with_amount',
label='VAT 18% with amount',
fieldtype='Currency',
insert_after='amount',
in_list_view=1,
columns=2,
precision=2
),
dict(
fieldname='vat_0_percent_with_amount',
label='VAT 0% with amount',
fieldtype='Currency',
insert_after='vat_18_percent_with_amount',
in_list_view=1,
columns=2,
precision=2
),
dict(
fieldname='vat_exempt_amount',
label='VAT free amount',
fieldtype='Currency',
insert_after='vat_0_percent_with_amount',
in_list_view=1,
columns=2,
precision=2
),
dict(
fieldname='not_subject_to_vat',
label='Not subject to VAT',
fieldtype='Currency',
insert_after='vat_exempt_amount',
in_list_view=1,
columns=2,
precision=2
)
],
"Sales Taxes and Charges": [ "Sales Taxes and Charges": [
dict( dict(
fieldname='tax_free_amount', fieldname='tax_free_amount',

View File

@ -10,7 +10,8 @@ boot_session = "jey_erp.extend_party_types.add_company_to_party_account_types"
app_include_js = [ app_include_js = [
"/assets/jey_erp/js/account_tree_custom.js", "/assets/jey_erp/js/account_tree_custom.js",
"/assets/jey_erp/js/sales_invoice.js" "/assets/jey_erp/js/sales_invoice.js",
"/assets/jey_erp/js/sales_invoice_vat.js"
] ]
doctype_js = { doctype_js = {
@ -28,8 +29,15 @@ doc_events = {
"on_update": "jey_erp.custom_employee_onboarding.on_update" "on_update": "jey_erp.custom_employee_onboarding.on_update"
}, },
"Sales Invoice": { "Sales Invoice": {
"validate": "jey_erp.custom.sales_invoice.calculate_tax_free_amounts", "validate": [
"on_update": "jey_erp.custom.sales_invoice.calculate_tax_free_amounts", "jey_erp.custom.sales_invoice.calculate_tax_free_amounts",
"jey_erp.custom.sales_invoice_vat.set_vat_fields_permissions"
],
"on_update": [
"jey_erp.custom.sales_invoice.calculate_tax_free_amounts",
"jey_erp.custom.sales_invoice_vat.set_vat_fields_permissions"
],
"before_save": "jey_erp.custom.sales_invoice_vat.set_vat_fields_permissions"
} }
} }
@ -39,6 +47,8 @@ def after_migrate_combined():
from jey_erp.custom_fields import create_custom_fields from jey_erp.custom_fields import create_custom_fields
create_custom_fields() create_custom_fields()
from jey_erp.custom.show_item_tax_template_in_sales_invoice import show_item_tax_template_in_sales_invoice
show_item_tax_template_in_sales_invoice()
fixtures = [ fixtures = [

View File

@ -0,0 +1,281 @@
// Файл: jey_erp/public/js/sales_invoice_vat.js
// Sales Invoice VAT fields logic
frappe.ui.form.on('Sales Invoice', {
onload: function(frm) {
setup_vat_fields_logic(frm);
},
refresh: function(frm) {
if (frm.doc.docstatus === 0) {
wait_for_grid_ready(frm, function() {
update_all_vat_permissions(frm);
});
}
}
});
frappe.ui.form.on('Sales Invoice Item', {
items_add: function(frm, cdt, cdn) {
wait_for_grid_ready(frm, function() {
update_item_vat_permissions(frm, cdt, cdn);
});
},
item_code: function(frm, cdt, cdn) {
update_item_vat_permissions(frm, cdt, cdn);
},
item_tax_template: function(frm, cdt, cdn) {
update_item_vat_permissions(frm, cdt, cdn);
},
vat_18_percent_with_amount: function(frm, cdt, cdn) {
clear_other_vat_fields(frm, cdt, cdn, 'vat_18_percent_with_amount');
},
vat_0_percent_with_amount: function(frm, cdt, cdn) {
clear_other_vat_fields(frm, cdt, cdn, 'vat_0_percent_with_amount');
},
vat_exempt_amount: function(frm, cdt, cdn) {
clear_other_vat_fields(frm, cdt, cdn, 'vat_exempt_amount');
},
not_subject_to_vat: function(frm, cdt, cdn) {
clear_other_vat_fields(frm, cdt, cdn, 'not_subject_to_vat');
}
});
function setup_vat_fields_logic(frm) {
frm.vat_field_names = [
'vat_18_percent_with_amount',
'vat_0_percent_with_amount',
'vat_exempt_amount',
'not_subject_to_vat'
];
frm.vat_permissions_cache = {};
}
function wait_for_grid_ready(frm, callback, max_attempts = 20, attempt = 0) {
if (attempt >= max_attempts) {
callback();
return;
}
let grid = frm.fields_dict.items.grid;
if (grid && grid.grid_rows && grid.grid_rows.length >= frm.doc.items.length) {
let all_ready = true;
for (let i = 0; i < frm.doc.items.length; i++) {
if (!grid.grid_rows[i] || !grid.grid_rows[i].docfields) {
all_ready = false;
break;
}
}
if (all_ready) {
callback();
return;
}
}
setTimeout(() => {
wait_for_grid_ready(frm, callback, max_attempts, attempt + 1);
}, 100);
}
function update_all_vat_permissions(frm) {
if (frm.doc.items) {
frm.doc.items.forEach(function(item, idx) {
if (item.item_code) {
update_item_vat_permissions(frm, frm.fields_dict.items.grid.doctype, item.name);
}
});
}
}
function update_item_vat_permissions(frm, cdt, cdn) {
let item = locals[cdt][cdn];
if (!item.item_code) {
return;
}
let cache_key = item.item_code + '|' + (item.item_tax_template || '');
if (frm.vat_permissions_cache && frm.vat_permissions_cache[cache_key]) {
apply_vat_permissions(frm, cdt, cdn, frm.vat_permissions_cache[cache_key]);
return;
}
let row_index = frm.doc.items.findIndex(i => i.name === cdn);
if (row_index >= 0) {
try {
frm.fields_dict.items.grid.grid_rows[row_index].toggle_editable_row(false);
} catch (e) {
// Ignore error
}
}
frappe.call({
method: 'jey_erp.custom.sales_invoice_vat.get_vat_permissions_for_item',
args: {
item_code: item.item_code,
item_tax_template: item.item_tax_template
},
callback: function(r) {
if (r.message) {
if (!frm.vat_permissions_cache) {
frm.vat_permissions_cache = {};
}
frm.vat_permissions_cache[cache_key] = r.message;
apply_vat_permissions(frm, cdt, cdn, r.message);
}
},
always: function() {
if (row_index >= 0) {
try {
frm.fields_dict.items.grid.grid_rows[row_index].toggle_editable_row(true);
} catch (e) {
// Ignore error
}
}
}
});
}
function apply_vat_permissions(frm, cdt, cdn, permissions) {
let item = locals[cdt][cdn];
let row_index = frm.doc.items.findIndex(doc_item => doc_item.name === cdn);
if (row_index === -1) {
return;
}
wait_for_row_ready(frm, row_index, function() {
frm.vat_field_names.forEach(function(fieldname) {
try {
apply_field_permission(frm, cdt, cdn, row_index, fieldname, permissions[fieldname], item);
} catch (e) {
// Ignore error
}
});
frm.fields_dict.items.grid.refresh();
});
}
function wait_for_row_ready(frm, row_index, callback, max_attempts = 10, attempt = 0) {
if (attempt >= max_attempts) {
callback();
return;
}
let grid = frm.fields_dict.items.grid;
if (grid && grid.grid_rows && grid.grid_rows[row_index] && grid.grid_rows[row_index].docfields) {
callback();
return;
}
setTimeout(() => {
wait_for_row_ready(frm, row_index, callback, max_attempts, attempt + 1);
}, 50);
}
function apply_field_permission(frm, cdt, cdn, row_index, fieldname, is_enabled, item) {
let grid_row = frm.fields_dict.items.grid.grid_rows[row_index];
if (grid_row && grid_row.docfields) {
let docfield = grid_row.docfields.find(df => df.fieldname === fieldname);
if (docfield) {
docfield.read_only = is_enabled ? 0 : 1;
}
}
try {
let meta = frappe.get_meta("Sales Invoice Item");
let field_meta = meta.fields.find(f => f.fieldname === fieldname);
if (field_meta) {
field_meta.read_only = is_enabled ? 0 : 1;
}
} catch (meta_error) {
// Ignore error
}
if (is_enabled) {
auto_fill_vat_field(cdt, cdn, fieldname, item);
} else {
if (item[fieldname] && item[fieldname] != 0) {
frappe.model.set_value(cdt, cdn, fieldname, 0.0);
}
}
}
function auto_fill_vat_field(cdt, cdn, fieldname, item) {
if (!item[fieldname] || item[fieldname] == 0) {
let item_amount = flt(item.amount || 0);
if (item_amount > 0) {
let new_value = 0;
if (fieldname === 'vat_18_percent_with_amount') {
new_value = item_amount * 1.18;
} else if (fieldname === 'vat_0_percent_with_amount') {
new_value = item_amount;
} else if (fieldname === 'vat_exempt_amount') {
new_value = item_amount;
}
if (new_value > 0) {
frappe.model.set_value(cdt, cdn, fieldname, new_value);
}
}
}
}
function clear_other_vat_fields(frm, cdt, cdn, current_field) {
let item = locals[cdt][cdn];
if (item[current_field] && item[current_field] > 0) {
frm.vat_field_names.forEach(function(fieldname) {
if (fieldname !== current_field) {
frappe.model.set_value(cdt, cdn, fieldname, 0.0);
}
});
}
}
$(document).ready(function() {
function setup_erpnext_override() {
if (erpnext && erpnext.selling && erpnext.selling.SellingController) {
if (!erpnext.selling.SellingController.prototype._original_item_code) {
let original_item_code = erpnext.selling.SellingController.prototype.item_code;
erpnext.selling.SellingController.prototype._original_item_code = original_item_code;
erpnext.selling.SellingController.prototype.item_code = function(doc, cdt, cdn) {
let result = this._original_item_code.call(this, doc, cdt, cdn);
if (this.frm.doc.doctype === 'Sales Invoice') {
let check_item_loaded = () => {
let item = locals[cdt][cdn];
if (item && item.item_code && (item.rate || item.price_list_rate)) {
update_item_vat_permissions(this.frm, cdt, cdn);
} else {
setTimeout(check_item_loaded, 100);
}
};
setTimeout(check_item_loaded, 200);
}
return result;
};
}
} else {
setTimeout(setup_erpnext_override, 500);
}
}
setup_erpnext_override();
});