mirror of
https://github.com/frappe/erpnext.git
synced 2026-06-01 03:09:09 +00:00
Merge pull request #44293 from ruthra-kumar/refactor_transaction_js
refactor: move `item_code` reactivity to server-side
This commit is contained in:
@@ -207,3 +207,23 @@ class AccountsTestMixin:
|
|||||||
]
|
]
|
||||||
for doctype in doctype_list:
|
for doctype in doctype_list:
|
||||||
qb.from_(qb.DocType(doctype)).delete().where(qb.DocType(doctype).company == self.company).run()
|
qb.from_(qb.DocType(doctype)).delete().where(qb.DocType(doctype).company == self.company).run()
|
||||||
|
|
||||||
|
def create_price_list(self):
|
||||||
|
pl_name = "Mixin Price List"
|
||||||
|
if not frappe.db.exists("Price List", pl_name):
|
||||||
|
self.price_list = (
|
||||||
|
frappe.get_doc(
|
||||||
|
{
|
||||||
|
"doctype": "Price List",
|
||||||
|
"currency": "INR",
|
||||||
|
"enabled": True,
|
||||||
|
"selling": True,
|
||||||
|
"buying": True,
|
||||||
|
"price_list_name": pl_name,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.insert()
|
||||||
|
.name
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
self.price_list = frappe.get_doc("Price List", pl_name).name
|
||||||
|
|||||||
69
erpnext/controllers/tests/test_reactivity.py
Normal file
69
erpnext/controllers/tests/test_reactivity.py
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
import frappe
|
||||||
|
from frappe import qb
|
||||||
|
from frappe.tests import IntegrationTestCase
|
||||||
|
from frappe.utils import getdate, today
|
||||||
|
|
||||||
|
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import disable_dimension
|
||||||
|
from erpnext.accounts.test.accounts_mixin import AccountsTestMixin
|
||||||
|
|
||||||
|
|
||||||
|
class TestReactivity(AccountsTestMixin, IntegrationTestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.create_company()
|
||||||
|
self.create_customer()
|
||||||
|
self.create_item()
|
||||||
|
self.create_usd_receivable_account()
|
||||||
|
self.create_price_list()
|
||||||
|
self.clear_old_entries()
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
frappe.db.rollback()
|
||||||
|
|
||||||
|
def disable_dimensions(self):
|
||||||
|
res = frappe.db.get_all("Accounting Dimension", filters={"disabled": False})
|
||||||
|
for x in res:
|
||||||
|
dim = frappe.get_doc("Accounting Dimension", x.name)
|
||||||
|
dim.disabled = True
|
||||||
|
dim.save()
|
||||||
|
|
||||||
|
def test_01_basic_item_details(self):
|
||||||
|
self.disable_dimensions()
|
||||||
|
|
||||||
|
# set Item Price
|
||||||
|
frappe.get_doc(
|
||||||
|
{
|
||||||
|
"doctype": "Item Price",
|
||||||
|
"item_code": self.item,
|
||||||
|
"price_list": self.price_list,
|
||||||
|
"price_list_rate": 90,
|
||||||
|
"selling": True,
|
||||||
|
"rate": 90,
|
||||||
|
"valid_from": today(),
|
||||||
|
}
|
||||||
|
).insert()
|
||||||
|
|
||||||
|
si = frappe.get_doc(
|
||||||
|
{
|
||||||
|
"doctype": "Sales Invoice",
|
||||||
|
"company": self.company,
|
||||||
|
"customer": self.customer,
|
||||||
|
"debit_to": self.debit_to,
|
||||||
|
"posting_date": today(),
|
||||||
|
"cost_center": self.cost_center,
|
||||||
|
"conversion_rate": 1,
|
||||||
|
"selling_price_list": self.price_list,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
itm = si.append("items")
|
||||||
|
itm.item_code = self.item
|
||||||
|
si.process_item_selection(si.items[0].name)
|
||||||
|
self.assertEqual(itm.rate, 90)
|
||||||
|
|
||||||
|
df = qb.DocType("DocField")
|
||||||
|
_res = (
|
||||||
|
qb.from_(df).select(df.fieldname).where(df.parent.eq("Sales Invoice Item") & df.reqd.eq(1)).run()
|
||||||
|
)
|
||||||
|
for field in _res:
|
||||||
|
with self.subTest(field=field):
|
||||||
|
self.assertIsNotNone(itm.get(field[0]))
|
||||||
|
si.save().submit()
|
||||||
@@ -498,7 +498,29 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
|||||||
|
|
||||||
item_code(doc, cdt, cdn) {
|
item_code(doc, cdt, cdn) {
|
||||||
var me = this;
|
var me = this;
|
||||||
|
// Experimental: This will be removed once stability is achieved.
|
||||||
|
if (frappe.boot.sysdefaults.use_server_side_reactivity) {
|
||||||
|
var item = frappe.get_doc(cdt, cdn);
|
||||||
|
frappe.call({
|
||||||
|
doc: doc,
|
||||||
|
method: "process_item_selection",
|
||||||
|
args: {
|
||||||
|
item: item.name
|
||||||
|
},
|
||||||
|
callback: function(r) {
|
||||||
|
if(!r.exc) {
|
||||||
|
me.frm.refresh_fields();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
me.process_item_selection(doc, cdt, cdn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
process_item_selection(doc, cdt, cdn) {
|
||||||
var item = frappe.get_doc(cdt, cdn);
|
var item = frappe.get_doc(cdt, cdn);
|
||||||
|
var me = this;
|
||||||
var update_stock = 0, show_batch_dialog = 0;
|
var update_stock = 0, show_batch_dialog = 0;
|
||||||
|
|
||||||
item.weight_per_unit = 0;
|
item.weight_per_unit = 0;
|
||||||
@@ -510,7 +532,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
|||||||
show_batch_dialog = update_stock;
|
show_batch_dialog = update_stock;
|
||||||
|
|
||||||
} else if((this.frm.doc.doctype === 'Purchase Receipt') ||
|
} else if((this.frm.doc.doctype === 'Purchase Receipt') ||
|
||||||
this.frm.doc.doctype === 'Delivery Note') {
|
this.frm.doc.doctype === 'Delivery Note') {
|
||||||
show_batch_dialog = 1;
|
show_batch_dialog = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -583,10 +605,10 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
|||||||
frappe.run_serially([
|
frappe.run_serially([
|
||||||
() => {
|
() => {
|
||||||
if (item.docstatus === 0
|
if (item.docstatus === 0
|
||||||
&& frappe.meta.has_field(item.doctype, "use_serial_batch_fields")
|
&& frappe.meta.has_field(item.doctype, "use_serial_batch_fields")
|
||||||
&& !item.use_serial_batch_fields
|
&& !item.use_serial_batch_fields
|
||||||
&& cint(frappe.user_defaults?.use_serial_batch_fields) === 1
|
&& cint(frappe.user_defaults?.use_serial_batch_fields) === 1
|
||||||
) {
|
) {
|
||||||
item["use_serial_batch_fields"] = 1;
|
item["use_serial_batch_fields"] = 1;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -601,7 +623,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
|||||||
// for internal customer instead of pricing rule directly apply valuation rate on item
|
// for internal customer instead of pricing rule directly apply valuation rate on item
|
||||||
if ((me.frm.doc.is_internal_customer || me.frm.doc.is_internal_supplier) && me.frm.doc.represents_company === me.frm.doc.company) {
|
if ((me.frm.doc.is_internal_customer || me.frm.doc.is_internal_supplier) && me.frm.doc.represents_company === me.frm.doc.company) {
|
||||||
me.get_incoming_rate(item, me.frm.posting_date, me.frm.posting_time,
|
me.get_incoming_rate(item, me.frm.posting_date, me.frm.posting_time,
|
||||||
me.frm.doc.doctype, me.frm.doc.company);
|
me.frm.doc.doctype, me.frm.doc.company);
|
||||||
} else {
|
} else {
|
||||||
me.frm.script_manager.trigger("price_list_rate", cdt, cdn);
|
me.frm.script_manager.trigger("price_list_rate", cdt, cdn);
|
||||||
}
|
}
|
||||||
@@ -615,24 +637,24 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
|||||||
() => {
|
() => {
|
||||||
if (show_batch_dialog && !frappe.flags.trigger_from_barcode_scanner)
|
if (show_batch_dialog && !frappe.flags.trigger_from_barcode_scanner)
|
||||||
return frappe.db.get_value("Item", item.item_code, ["has_batch_no", "has_serial_no"])
|
return frappe.db.get_value("Item", item.item_code, ["has_batch_no", "has_serial_no"])
|
||||||
.then((r) => {
|
.then((r) => {
|
||||||
if (r.message &&
|
if (r.message &&
|
||||||
(r.message.has_batch_no || r.message.has_serial_no)) {
|
(r.message.has_batch_no || r.message.has_serial_no)) {
|
||||||
frappe.flags.hide_serial_batch_dialog = false;
|
frappe.flags.hide_serial_batch_dialog = false;
|
||||||
} else {
|
} else {
|
||||||
show_batch_dialog = false;
|
show_batch_dialog = false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
() => {
|
() => {
|
||||||
// check if batch serial selector is disabled or not
|
// check if batch serial selector is disabled or not
|
||||||
if (show_batch_dialog && !frappe.flags.hide_serial_batch_dialog)
|
if (show_batch_dialog && !frappe.flags.hide_serial_batch_dialog)
|
||||||
return frappe.db.get_single_value('Stock Settings', 'disable_serial_no_and_batch_selector')
|
return frappe.db.get_single_value('Stock Settings', 'disable_serial_no_and_batch_selector')
|
||||||
.then((value) => {
|
.then((value) => {
|
||||||
if (value) {
|
if (value) {
|
||||||
frappe.flags.hide_serial_batch_dialog = true;
|
frappe.flags.hide_serial_batch_dialog = true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
() => {
|
() => {
|
||||||
if(show_batch_dialog && !frappe.flags.hide_serial_batch_dialog && !frappe.flags.dialog_set) {
|
if(show_batch_dialog && !frappe.flags.hide_serial_batch_dialog && !frappe.flags.dialog_set) {
|
||||||
@@ -676,6 +698,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
price_list_rate(doc, cdt, cdn) {
|
price_list_rate(doc, cdt, cdn) {
|
||||||
var item = frappe.get_doc(cdt, cdn);
|
var item = frappe.get_doc(cdt, cdn);
|
||||||
frappe.model.round_floats_in(item, ["price_list_rate", "discount_percentage"]);
|
frappe.model.round_floats_in(item, ["price_list_rate", "discount_percentage"]);
|
||||||
|
|||||||
@@ -33,7 +33,9 @@
|
|||||||
"dont_reserve_sales_order_qty_on_sales_return",
|
"dont_reserve_sales_order_qty_on_sales_return",
|
||||||
"hide_tax_id",
|
"hide_tax_id",
|
||||||
"enable_discount_accounting",
|
"enable_discount_accounting",
|
||||||
"enable_cutoff_date_on_bulk_delivery_note_creation"
|
"enable_cutoff_date_on_bulk_delivery_note_creation",
|
||||||
|
"experimental_section",
|
||||||
|
"use_server_side_reactivity"
|
||||||
],
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
@@ -207,6 +209,17 @@
|
|||||||
"fieldname": "enable_cutoff_date_on_bulk_delivery_note_creation",
|
"fieldname": "enable_cutoff_date_on_bulk_delivery_note_creation",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Enable Cut-Off Date on Bulk Delivery Note Creation"
|
"label": "Enable Cut-Off Date on Bulk Delivery Note Creation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "experimental_section",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Experimental"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "1",
|
||||||
|
"fieldname": "use_server_side_reactivity",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Use Server Side Reactivity"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"icon": "fa fa-cog",
|
"icon": "fa fa-cog",
|
||||||
@@ -214,7 +227,7 @@
|
|||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"issingle": 1,
|
"issingle": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2024-03-27 13:10:38.633352",
|
"modified": "2024-12-06 11:41:54.722337",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Selling",
|
"module": "Selling",
|
||||||
"name": "Selling Settings",
|
"name": "Selling Settings",
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ class SellingSettings(Document):
|
|||||||
selling_price_list: DF.Link | None
|
selling_price_list: DF.Link | None
|
||||||
so_required: DF.Literal["No", "Yes"]
|
so_required: DF.Literal["No", "Yes"]
|
||||||
territory: DF.Link | None
|
territory: DF.Link | None
|
||||||
|
use_server_side_reactivity: DF.Check
|
||||||
validate_selling_price: DF.Check
|
validate_selling_price: DF.Check
|
||||||
# end: auto-generated types
|
# end: auto-generated types
|
||||||
|
|
||||||
@@ -69,15 +70,15 @@ class SellingSettings(Document):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def toggle_hide_tax_id(self):
|
def toggle_hide_tax_id(self):
|
||||||
self.hide_tax_id = cint(self.hide_tax_id)
|
_hide_tax_id = cint(self.hide_tax_id)
|
||||||
|
|
||||||
# Make property setters to hide tax_id fields
|
# Make property setters to hide tax_id fields
|
||||||
for doctype in ("Sales Order", "Sales Invoice", "Delivery Note"):
|
for doctype in ("Sales Order", "Sales Invoice", "Delivery Note"):
|
||||||
make_property_setter(
|
make_property_setter(
|
||||||
doctype, "tax_id", "hidden", self.hide_tax_id, "Check", validate_fields_for_doctype=False
|
doctype, "tax_id", "hidden", _hide_tax_id, "Check", validate_fields_for_doctype=False
|
||||||
)
|
)
|
||||||
make_property_setter(
|
make_property_setter(
|
||||||
doctype, "tax_id", "print_hide", self.hide_tax_id, "Check", validate_fields_for_doctype=False
|
doctype, "tax_id", "print_hide", _hide_tax_id, "Check", validate_fields_for_doctype=False
|
||||||
)
|
)
|
||||||
|
|
||||||
def toggle_editable_rate_for_bundle_items(self):
|
def toggle_editable_rate_for_bundle_items(self):
|
||||||
|
|||||||
@@ -16,6 +16,9 @@ def boot_session(bootinfo):
|
|||||||
|
|
||||||
bootinfo.sysdefaults.territory = frappe.db.get_single_value("Selling Settings", "territory")
|
bootinfo.sysdefaults.territory = frappe.db.get_single_value("Selling Settings", "territory")
|
||||||
bootinfo.sysdefaults.customer_group = frappe.db.get_single_value("Selling Settings", "customer_group")
|
bootinfo.sysdefaults.customer_group = frappe.db.get_single_value("Selling Settings", "customer_group")
|
||||||
|
bootinfo.sysdefaults.use_server_side_reactivity = frappe.db.get_single_value(
|
||||||
|
"Selling Settings", "use_server_side_reactivity"
|
||||||
|
)
|
||||||
bootinfo.sysdefaults.allow_stale = cint(
|
bootinfo.sysdefaults.allow_stale = cint(
|
||||||
frappe.db.get_single_value("Accounts Settings", "allow_stale")
|
frappe.db.get_single_value("Accounts Settings", "allow_stale")
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -7,7 +7,10 @@ import frappe.share
|
|||||||
from frappe import _
|
from frappe import _
|
||||||
from frappe.utils import cint, flt, get_time, now_datetime
|
from frappe.utils import cint, flt, get_time, now_datetime
|
||||||
|
|
||||||
|
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_dimensions
|
||||||
from erpnext.controllers.status_updater import StatusUpdater
|
from erpnext.controllers.status_updater import StatusUpdater
|
||||||
|
from erpnext.stock.get_item_details import get_item_details
|
||||||
|
from erpnext.stock.utils import get_incoming_rate
|
||||||
|
|
||||||
|
|
||||||
class UOMMustBeIntegerError(frappe.ValidationError):
|
class UOMMustBeIntegerError(frappe.ValidationError):
|
||||||
@@ -231,6 +234,259 @@ class TransactionBase(StatusUpdater):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def fetch_item_details(self, item: dict) -> dict:
|
||||||
|
return get_item_details(
|
||||||
|
frappe._dict(
|
||||||
|
{
|
||||||
|
"item_code": item.get("item_code"),
|
||||||
|
"barcode": item.get("barcode"),
|
||||||
|
"serial_no": item.get("serial_no"),
|
||||||
|
"batch_no": item.get("batch_no"),
|
||||||
|
"set_warehouse": self.get("set_warehouse"),
|
||||||
|
"warehouse": item.get("warehouse"),
|
||||||
|
"customer": self.get("customer") or self.get("party_name"),
|
||||||
|
"quotation_to": self.get("quotation_to"),
|
||||||
|
"supplier": self.get("supplier"),
|
||||||
|
"currency": self.get("currency"),
|
||||||
|
"is_internal_supplier": self.get("is_internal_supplier"),
|
||||||
|
"is_internal_customer": self.get("is_internal_customer"),
|
||||||
|
"update_stock": self.update_stock
|
||||||
|
if self.doctype in ["Purchase Invoice", "Sales Invoice"]
|
||||||
|
else False,
|
||||||
|
"conversion_rate": self.get("conversion_rate"),
|
||||||
|
"price_list": self.get("selling_price_list") or self.get("buying_price_list"),
|
||||||
|
"price_list_currency": self.get("price_list_currency"),
|
||||||
|
"plc_conversion_rate": self.get("plc_conversion_rate"),
|
||||||
|
"company": self.get("company"),
|
||||||
|
"order_type": self.get("order_type"),
|
||||||
|
"is_pos": cint(self.get("is_pos")),
|
||||||
|
"is_return": cint(self.get("is_return)")),
|
||||||
|
"is_subcontracted": self.get("is_subcontracted"),
|
||||||
|
"ignore_pricing_rule": self.get("ignore_pricing_rule"),
|
||||||
|
"doctype": self.get("doctype"),
|
||||||
|
"name": self.get("name"),
|
||||||
|
"project": item.get("project") or self.get("project"),
|
||||||
|
"qty": item.get("qty") or 1,
|
||||||
|
"net_rate": item.get("rate"),
|
||||||
|
"base_net_rate": item.get("base_net_rate"),
|
||||||
|
"stock_qty": item.get("stock_qty"),
|
||||||
|
"conversion_factor": item.get("conversion_factor"),
|
||||||
|
"weight_per_unit": item.get("weight_per_unit"),
|
||||||
|
"uom": item.get("uom"),
|
||||||
|
"weight_uom": item.get("weight_uom"),
|
||||||
|
"manufacturer": item.get("manufacturer"),
|
||||||
|
"stock_uom": item.get("stock_uom"),
|
||||||
|
"pos_profile": self.get("pos_profile") if cint(self.get("is_pos")) else "",
|
||||||
|
"cost_center": item.get("cost_center"),
|
||||||
|
"tax_category": self.get("tax_category"),
|
||||||
|
"item_tax_template": item.get("item_tax_template"),
|
||||||
|
"child_doctype": item.get("doctype"),
|
||||||
|
"child_docname": item.get("name"),
|
||||||
|
"is_old_subcontracting_flow": self.get("is_old_subcontracting_flow"),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def process_item_selection(self, item):
|
||||||
|
# Server side 'item' doc. Update this to reflect in UI
|
||||||
|
item_obj = self.get("items", {"name": item})[0]
|
||||||
|
|
||||||
|
# 'item_details' has latest item related values
|
||||||
|
item_details = self.fetch_item_details(item_obj)
|
||||||
|
|
||||||
|
self.set_fetched_values(item_obj, item_details)
|
||||||
|
self.set_item_rate_and_discounts(item_obj, item_details)
|
||||||
|
self.add_taxes_from_item_template(item_obj, item_details)
|
||||||
|
self.add_free_item(item_obj, item_details)
|
||||||
|
self.handle_internal_parties(item_obj, item_details)
|
||||||
|
self.conversion_factor(item_obj, item_details)
|
||||||
|
self.calculate_taxes_and_totals()
|
||||||
|
|
||||||
|
def set_fetched_values(self, item_obj: object, item_details: dict) -> None:
|
||||||
|
for k, v in item_details.items():
|
||||||
|
if hasattr(item_obj, k):
|
||||||
|
setattr(item_obj, k, v)
|
||||||
|
|
||||||
|
def handle_internal_parties(self, item_obj: object, item_details: dict) -> None:
|
||||||
|
if (
|
||||||
|
self.get("is_internal_customer") or self.get("is_internal_supplier")
|
||||||
|
) and self.represents_company == self.company:
|
||||||
|
args = frappe._dict(
|
||||||
|
{
|
||||||
|
"item_code": item_obj.item_code,
|
||||||
|
"warehouse": item_obj.from_warehouse
|
||||||
|
if self.doctype in ["Purchase Receipt", "Purchase Invoice"]
|
||||||
|
else item_obj.warehouse,
|
||||||
|
"posting_date": self.posting_date,
|
||||||
|
"posting_time": self.posting_time,
|
||||||
|
"qty": item_obj.qty * item_obj.conversion_factor,
|
||||||
|
"serial_no": item_obj.serial_no,
|
||||||
|
"batch_no": item_obj.batch_no,
|
||||||
|
"voucher_type": self.doctype,
|
||||||
|
"company": self.company,
|
||||||
|
"allow_zero_valuation_rate": item_obj.allow_zero_valuation_rate,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
rate = get_incoming_rate(args=args)
|
||||||
|
item_obj.rate = rate * item_obj.conversion_factor
|
||||||
|
else:
|
||||||
|
self.set_rate_based_on_price_list(item_obj, item_details)
|
||||||
|
|
||||||
|
def add_taxes_from_item_template(self, item_obj: object, item_details: dict) -> None:
|
||||||
|
if item_details.item_tax_rate and frappe.db.get_single_value(
|
||||||
|
"Accounts Settings", "add_taxes_from_item_tax_template"
|
||||||
|
):
|
||||||
|
item_tax_template = frappe.json.loads(item_details.item_tax_rate)
|
||||||
|
for tax_head, _rate in item_tax_template.items():
|
||||||
|
found = [x for x in self.taxes if x.account_head == tax_head]
|
||||||
|
if not found:
|
||||||
|
self.append("taxes", {"charge_type": "On Net Total", "account_head": tax_head, "rate": 0})
|
||||||
|
|
||||||
|
def set_rate_based_on_price_list(self, item_obj: object, item_details: dict) -> None:
|
||||||
|
if item_obj.price_list_rate and item_obj.discount_percentage:
|
||||||
|
item_obj.rate = flt(
|
||||||
|
item_obj.price_list_rate * (1 - item_obj.discount_percentage / 100.0),
|
||||||
|
item_obj.precision("rate"),
|
||||||
|
)
|
||||||
|
|
||||||
|
def copy_from_first_row(self, row, fields):
|
||||||
|
if self.items and row:
|
||||||
|
fields.extend([x.get("fieldname") for x in get_dimensions(True)[0]])
|
||||||
|
first_row = self.items[0]
|
||||||
|
[setattr(row, k, first_row.get(k)) for k in fields if hasattr(first_row, k)]
|
||||||
|
|
||||||
|
def add_free_item(self, item_obj: object, item_details: dict) -> None:
|
||||||
|
free_items = item_details.get("free_item_data")
|
||||||
|
if free_items and len(free_items):
|
||||||
|
existing_free_items = [x for x in self.items if x.is_free_item]
|
||||||
|
for free_item in free_items:
|
||||||
|
_matches = [
|
||||||
|
x
|
||||||
|
for x in existing_free_items
|
||||||
|
if x.item_code == free_item.get("item_code")
|
||||||
|
and x.pricing_rules == free_item.get("pricing_rules")
|
||||||
|
]
|
||||||
|
if _matches:
|
||||||
|
row_to_modify = _matches[0]
|
||||||
|
else:
|
||||||
|
row_to_modify = self.append("items")
|
||||||
|
|
||||||
|
for k, _v in free_item.items():
|
||||||
|
setattr(row_to_modify, k, free_item.get(k))
|
||||||
|
|
||||||
|
self.copy_from_first_row(row_to_modify, ["expense_account", "income_account"])
|
||||||
|
|
||||||
|
def conversion_factor(self, item_obj: object, item_details: dict) -> None:
|
||||||
|
if frappe.get_meta(item_obj.doctype).has_field("stock_qty"):
|
||||||
|
item_obj.stock_qty = flt(
|
||||||
|
item_obj.qty * item_obj.conversion_factor, item_obj.precision("stock_qty")
|
||||||
|
)
|
||||||
|
|
||||||
|
if self.doctype != "Material Request":
|
||||||
|
item_obj.total_weight = flt(item_obj.stock_qty * item_obj.weight_per_unit)
|
||||||
|
self.calculate_net_weight()
|
||||||
|
|
||||||
|
# TODO: for handling customization not to fetch price list rate
|
||||||
|
if frappe.flags.dont_fetch_price_list_rate:
|
||||||
|
return
|
||||||
|
|
||||||
|
if not frappe.flags.dont_fetch_price_list_rate and frappe.get_meta(self.doctype).has_field(
|
||||||
|
"price_list_currency"
|
||||||
|
):
|
||||||
|
self._apply_price_list(item_obj, True)
|
||||||
|
self.calculate_stock_uom_rate(item_obj)
|
||||||
|
|
||||||
|
def calculate_stock_uom_rate(self, item_obj: object) -> None:
|
||||||
|
if item_obj.rate:
|
||||||
|
item_obj.stock_uom_rate = flt(item_obj.rate) / flt(item_obj.conversion_factor)
|
||||||
|
|
||||||
|
def set_item_rate_and_discounts(self, item_obj: object, item_details: dict) -> None:
|
||||||
|
effective_item_rate = item_details.price_list_rate
|
||||||
|
item_rate = item_details.rate
|
||||||
|
|
||||||
|
# Field order precedance
|
||||||
|
# blanket_order_rate -> margin_type -> discount_percentage -> discount_amount
|
||||||
|
if item_obj.parenttype in ["Sales Order", "Quotation"] and item_obj.blanket_order_rate:
|
||||||
|
effective_item_rate = item_obj.blanket_order_rate
|
||||||
|
|
||||||
|
if item_obj.margin_type == "Percentage":
|
||||||
|
item_obj.rate_with_margin = flt(effective_item_rate) + flt(effective_item_rate) * (
|
||||||
|
flt(item_obj.margin_rate_or_amount) / 100
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
item_obj.rate_with_margin = flt(effective_item_rate) + flt(item_obj.margin_rate_or_amount)
|
||||||
|
|
||||||
|
item_obj.base_rate_with_margin = flt(item_obj.rate_with_margin) * flt(self.conversion_rate)
|
||||||
|
item_rate = flt(item_obj.rate_with_margin, item_obj.precision("rate"))
|
||||||
|
|
||||||
|
if item_obj.discount_percentage and not item_obj.discount_amount:
|
||||||
|
item_obj.discount_amount = (
|
||||||
|
flt(item_obj.rate_with_margin) * flt(item_obj.discount_percentage) / 100
|
||||||
|
)
|
||||||
|
|
||||||
|
if item_obj.discount_amount and item_obj.discount_amount > 0:
|
||||||
|
item_rate = flt(
|
||||||
|
(item_obj.rate_with_margin) - (item_obj.discount_amount), item_obj.precision("rate")
|
||||||
|
)
|
||||||
|
item_obj.discount_percentage = (
|
||||||
|
100 * flt(item_obj.discount_amount) / flt(item_obj.rate_with_margin)
|
||||||
|
)
|
||||||
|
|
||||||
|
item_obj.rate = item_rate
|
||||||
|
|
||||||
|
def calculate_net_weight(self):
|
||||||
|
self.total_net_weight = sum([x.get("total_weight") or 0 for x in self.items])
|
||||||
|
self.apply_shipping_rule()
|
||||||
|
|
||||||
|
def _apply_price_list(self, item_obj: object, reset_plc_conversion: bool) -> None:
|
||||||
|
if self.doctype == "Material Request":
|
||||||
|
return
|
||||||
|
|
||||||
|
if not reset_plc_conversion:
|
||||||
|
self.plc_conversion_rate = ""
|
||||||
|
|
||||||
|
if not self.items or not (item_obj.get("selling_price_list") or item_obj.get("buying_price_list")):
|
||||||
|
return
|
||||||
|
|
||||||
|
if self.get("in_apply_price_list"):
|
||||||
|
return
|
||||||
|
|
||||||
|
self.in_apply_price_list = True
|
||||||
|
|
||||||
|
from erpnext.stock.get_item_details import apply_price_list
|
||||||
|
|
||||||
|
args = {
|
||||||
|
"items": [x.as_dict() for x in self.items],
|
||||||
|
"customer": self.customer or self.party_name,
|
||||||
|
"quotation_to": self.quotation_to,
|
||||||
|
"customer_group": self.customer_group,
|
||||||
|
"territory": self.territory,
|
||||||
|
"supplier": self.supplier,
|
||||||
|
"supplier_group": self.supplier_group,
|
||||||
|
"currency": self.currency,
|
||||||
|
"conversion_rate": self.conversion_rate,
|
||||||
|
"price_list": self.selling_price_list or self.buying_price_list,
|
||||||
|
"price_list_currency": self.price_list_currency,
|
||||||
|
"plc_conversion_rate": self.plc_conversion_rate,
|
||||||
|
"company": self.company,
|
||||||
|
"transaction_date": self.transaction_date or self.posting_date,
|
||||||
|
"campaign": self.campaign,
|
||||||
|
"sales_partner": self.sales_partner,
|
||||||
|
"ignore_pricing_rule": self.ignore_pricing_rule,
|
||||||
|
"doctype": self.doctype,
|
||||||
|
"name": self.name,
|
||||||
|
"is_return": self.is_return,
|
||||||
|
"update_stock": self.update_stock if self.doctype in ["Sales Invoice", "Purchase Invoice"] else 0,
|
||||||
|
"conversion_factor": self.conversion_factor,
|
||||||
|
"pos_profile": self.pos_profile if self.doctype == "Sales Invoice" else "",
|
||||||
|
"coupon_code": self.coupon_code,
|
||||||
|
"is_internal_supplier": self.is_internal_supplier,
|
||||||
|
"is_internal_customer": self.is_internal_customer,
|
||||||
|
}
|
||||||
|
# TODO: test method call impact on document
|
||||||
|
apply_price_list(cts=args, as_doc=True, doc=self)
|
||||||
|
|
||||||
|
|
||||||
def delete_events(ref_type, ref_name):
|
def delete_events(ref_type, ref_name):
|
||||||
events = (
|
events = (
|
||||||
|
|||||||
Reference in New Issue
Block a user