mirror of
https://github.com/frappe/erpnext.git
synced 2026-09-01 15:47:06 +00:00
feat(manufacturing): add multi-currency support to Blanket Orders (#58472)
This commit is contained in:
@@ -557,6 +557,7 @@
|
||||
"fieldname": "blanket_order_rate",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Blanket Order Rate",
|
||||
"options": "currency",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
@@ -944,7 +945,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2026-08-07 17:31:31.732720",
|
||||
"modified": "2026-08-27 10:55:37.000000",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Buying",
|
||||
"name": "Purchase Order Item",
|
||||
|
||||
@@ -801,7 +801,7 @@ def get_blanket_orders(doctype: str, txt: str, searchfield: str, start: int, pag
|
||||
bo = frappe.qb.DocType("Blanket Order")
|
||||
bo_item = frappe.qb.DocType("Blanket Order Item")
|
||||
|
||||
blanket_orders = (
|
||||
query = (
|
||||
frappe.qb.from_(bo)
|
||||
.from_(bo_item)
|
||||
.select(bo.name)
|
||||
@@ -814,10 +814,12 @@ def get_blanket_orders(doctype: str, txt: str, searchfield: str, start: int, pag
|
||||
& (bo.company == filters.get("company"))
|
||||
& (bo.docstatus == 1)
|
||||
)
|
||||
.run()
|
||||
)
|
||||
|
||||
return blanket_orders
|
||||
if currency := filters.get("currency"):
|
||||
query = query.where(bo.currency == currency)
|
||||
|
||||
return query.run()
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
|
||||
@@ -4,6 +4,10 @@
|
||||
frappe.ui.form.on("Blanket Order", {
|
||||
onload: function (frm) {
|
||||
frm.trigger("set_tc_name_filter");
|
||||
if (frm.is_new()) {
|
||||
let has_pricing = frm.doc.currency || frm.doc.selling_price_list || frm.doc.buying_price_list;
|
||||
blanket_order_pricing.apply(frm, null, { reset_party_values: !has_pricing });
|
||||
}
|
||||
},
|
||||
|
||||
setup: function (frm) {
|
||||
@@ -15,10 +19,13 @@ frappe.ui.form.on("Blanket Order", {
|
||||
|
||||
frm.add_fetch("customer", "customer_name", "customer_name");
|
||||
frm.add_fetch("supplier", "supplier_name", "supplier_name");
|
||||
frm.set_query("selling_price_list", () => ({ filters: { selling: 1 } }));
|
||||
frm.set_query("buying_price_list", () => ({ filters: { buying: 1 } }));
|
||||
},
|
||||
|
||||
refresh: function (frm) {
|
||||
erpnext.hide_company(frm);
|
||||
blanket_order_pricing.update_labels(frm);
|
||||
if (frm.doc.customer && frm.doc.docstatus === 1 && frm.doc.to_date > frappe.datetime.get_today()) {
|
||||
frm.add_custom_button(
|
||||
__("Sales Order"),
|
||||
@@ -101,5 +108,141 @@ frappe.ui.form.on("Blanket Order", {
|
||||
|
||||
blanket_order_type: function (frm) {
|
||||
frm.trigger("set_tc_name_filter");
|
||||
return reset_party_pricing(frm);
|
||||
},
|
||||
|
||||
company: reset_party_pricing,
|
||||
|
||||
customer: reset_party_pricing,
|
||||
|
||||
supplier: reset_party_pricing,
|
||||
|
||||
currency: function (frm) {
|
||||
return blanket_order_pricing.apply(frm, null, { reset_conversion_rate: true });
|
||||
},
|
||||
|
||||
from_date: function (frm) {
|
||||
return blanket_order_pricing.apply(frm, null, {
|
||||
reset_conversion_rate: true,
|
||||
reset_plc_conversion_rate: true,
|
||||
});
|
||||
},
|
||||
|
||||
conversion_rate: async function (frm) {
|
||||
await blanket_order_pricing.update_base_rates(frm);
|
||||
return blanket_order_pricing.apply(frm);
|
||||
},
|
||||
|
||||
selling_price_list: reset_price_list_exchange_rate,
|
||||
|
||||
buying_price_list: reset_price_list_exchange_rate,
|
||||
|
||||
plc_conversion_rate: function (frm) {
|
||||
return blanket_order_pricing.apply(frm);
|
||||
},
|
||||
});
|
||||
|
||||
frappe.ui.form.on("Blanket Order Item", {
|
||||
item_code: apply_item_pricing,
|
||||
|
||||
qty: apply_item_pricing,
|
||||
|
||||
rate: function (frm, cdt, cdn) {
|
||||
return set_base_rate(frm, frappe.get_doc(cdt, cdn));
|
||||
},
|
||||
});
|
||||
|
||||
const blanket_order_pricing = {
|
||||
update_base_rates(frm) {
|
||||
return Promise.all((frm.doc.items || []).map((item) => set_base_rate(frm, item)));
|
||||
},
|
||||
|
||||
update_labels(frm) {
|
||||
let company_currency = this.get_company_currency(frm);
|
||||
let show_base_rate = Boolean(
|
||||
frm.doc.currency && company_currency && frm.doc.currency !== company_currency
|
||||
);
|
||||
|
||||
frm.set_currency_labels(["price_list_rate", "rate"], frm.doc.currency || company_currency, "items");
|
||||
frm.set_currency_labels(["base_price_list_rate", "base_rate"], company_currency, "items");
|
||||
frm.fields_dict.items.grid.set_column_disp("base_price_list_rate", show_base_rate);
|
||||
frm.fields_dict.items.grid.set_column_disp("base_rate", show_base_rate);
|
||||
frm.toggle_display("conversion_rate", show_base_rate);
|
||||
frm.toggle_display(
|
||||
"plc_conversion_rate",
|
||||
Boolean(frm.doc.price_list_currency && frm.doc.price_list_currency !== company_currency)
|
||||
);
|
||||
frm.set_df_property(
|
||||
"conversion_rate",
|
||||
"description",
|
||||
show_base_rate ? `1 ${frm.doc.currency} = [?] ${company_currency}` : ""
|
||||
);
|
||||
frm.refresh_fields();
|
||||
},
|
||||
|
||||
get_company_currency(frm) {
|
||||
return frm.doc.company ? erpnext.get_currency(frm.doc.company) : null;
|
||||
},
|
||||
|
||||
async apply(frm, item_name = null, options = {}) {
|
||||
if (!frm.doc.company || !frm.doc.blanket_order_type) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (frm.__applying_blanket_order_price_list) {
|
||||
frm.__pending_blanket_order_price_list = { item_name, options };
|
||||
return;
|
||||
}
|
||||
|
||||
frm.__applying_blanket_order_price_list = true;
|
||||
let pending;
|
||||
try {
|
||||
let response = await frappe.call({
|
||||
method: "erpnext.manufacturing.doctype.blanket_order.blanket_order.apply_price_list",
|
||||
args: {
|
||||
doc: frm.doc,
|
||||
item_name,
|
||||
reset_party_values: options.reset_party_values,
|
||||
reset_conversion_rate: options.reset_conversion_rate,
|
||||
reset_plc_conversion_rate: options.reset_plc_conversion_rate,
|
||||
},
|
||||
});
|
||||
if (response.message) {
|
||||
await frm.set_value(response.message.parent);
|
||||
for (const values of response.message.children) {
|
||||
let { name, ...fields } = values;
|
||||
let item = (frm.doc.items || []).find((row) => row.name === name);
|
||||
if (item) {
|
||||
await frappe.model.set_value(item.doctype, item.name, fields);
|
||||
}
|
||||
}
|
||||
this.update_labels(frm);
|
||||
}
|
||||
} finally {
|
||||
frm.__applying_blanket_order_price_list = false;
|
||||
pending = frm.__pending_blanket_order_price_list;
|
||||
frm.__pending_blanket_order_price_list = null;
|
||||
}
|
||||
if (pending) {
|
||||
return this.apply(frm, pending.item_name, pending.options);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
function reset_party_pricing(frm) {
|
||||
return blanket_order_pricing.apply(frm, null, { reset_party_values: true });
|
||||
}
|
||||
|
||||
function reset_price_list_exchange_rate(frm) {
|
||||
return blanket_order_pricing.apply(frm, null, { reset_plc_conversion_rate: true });
|
||||
}
|
||||
|
||||
function apply_item_pricing(frm, cdt, cdn) {
|
||||
return blanket_order_pricing.apply(frm, cdn);
|
||||
}
|
||||
|
||||
function set_base_rate(frm, item) {
|
||||
frappe.model.round_floats_in(item, ["rate"]);
|
||||
let base_rate = flt(flt(item.rate) * flt(frm.doc.conversion_rate), precision("base_rate", item));
|
||||
return frappe.model.set_value(item.doctype, item.name, "base_rate", base_rate);
|
||||
}
|
||||
|
||||
@@ -18,6 +18,14 @@
|
||||
"from_date",
|
||||
"to_date",
|
||||
"company",
|
||||
"currency_and_price_list",
|
||||
"currency",
|
||||
"conversion_rate",
|
||||
"column_break_price_list",
|
||||
"selling_price_list",
|
||||
"buying_price_list",
|
||||
"price_list_currency",
|
||||
"plc_conversion_rate",
|
||||
"section_break_12",
|
||||
"items",
|
||||
"amended_from",
|
||||
@@ -96,6 +104,66 @@
|
||||
"reqd": 1,
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"collapsible_depends_on": "eval:doc.currency && doc.currency != erpnext.get_currency(doc.company)",
|
||||
"fieldname": "currency_and_price_list",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Currency and Price List"
|
||||
},
|
||||
{
|
||||
"fieldname": "currency",
|
||||
"fieldtype": "Link",
|
||||
"label": "Currency",
|
||||
"options": "Currency",
|
||||
"print_hide": 1,
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_price_list",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"description": "Rate at which document currency is converted to company currency",
|
||||
"fieldname": "conversion_rate",
|
||||
"fieldtype": "Float",
|
||||
"label": "Exchange Rate",
|
||||
"precision": "9",
|
||||
"print_hide": 1,
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.blanket_order_type == \"Selling\"",
|
||||
"fieldname": "selling_price_list",
|
||||
"fieldtype": "Link",
|
||||
"label": "Price List",
|
||||
"options": "Price List",
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.blanket_order_type == \"Purchasing\"",
|
||||
"fieldname": "buying_price_list",
|
||||
"fieldtype": "Link",
|
||||
"label": "Price List",
|
||||
"options": "Price List",
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "price_list_currency",
|
||||
"fieldtype": "Link",
|
||||
"label": "Price List Currency",
|
||||
"options": "Currency",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"description": "Rate at which Price List Currency is converted to Company Currency",
|
||||
"fieldname": "plc_conversion_rate",
|
||||
"fieldtype": "Float",
|
||||
"label": "Price List Exchange Rate",
|
||||
"precision": "9",
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_12",
|
||||
"fieldtype": "Section Break"
|
||||
@@ -147,7 +215,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2026-08-21 23:11:40.122402",
|
||||
"modified": "2026-08-27 10:55:37.000000",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Manufacturing",
|
||||
"name": "Blanket Order",
|
||||
|
||||
@@ -9,6 +9,9 @@ from frappe.model.mapper import get_mapped_doc
|
||||
from frappe.query_builder.functions import Sum
|
||||
from frappe.utils import flt, getdate
|
||||
|
||||
from erpnext import get_company_currency
|
||||
from erpnext.accounts.services.taxes import validate_conversion_rate
|
||||
from erpnext.manufacturing.doctype.blanket_order import blanket_order_pricing
|
||||
from erpnext.stock.doctype.item.item import get_item_defaults
|
||||
|
||||
|
||||
@@ -25,7 +28,10 @@ class BlanketOrder(Document):
|
||||
|
||||
amended_from: DF.Link | None
|
||||
blanket_order_type: DF.Literal["", "Selling", "Purchasing"]
|
||||
buying_price_list: DF.Link | None
|
||||
company: DF.Link
|
||||
conversion_rate: DF.Float
|
||||
currency: DF.Link
|
||||
customer: DF.Link | None
|
||||
customer_name: DF.Data | None
|
||||
from_date: DF.Date
|
||||
@@ -33,6 +39,9 @@ class BlanketOrder(Document):
|
||||
naming_series: DF.Literal["MFG-BLR-.YYYY.-"]
|
||||
order_date: DF.Date | None
|
||||
order_no: DF.Data | None
|
||||
plc_conversion_rate: DF.Float
|
||||
price_list_currency: DF.Link | None
|
||||
selling_price_list: DF.Link | None
|
||||
supplier: DF.Link | None
|
||||
supplier_name: DF.Data | None
|
||||
tc_name: DF.Link | None
|
||||
@@ -40,11 +49,42 @@ class BlanketOrder(Document):
|
||||
to_date: DF.Date
|
||||
# end: auto-generated types
|
||||
|
||||
def before_validate(self):
|
||||
self.set_currency()
|
||||
self.set_conversion_rate()
|
||||
blanket_order_pricing.set_price_list(self)
|
||||
|
||||
def validate(self):
|
||||
self.validate_dates()
|
||||
self.validate_duplicate_items()
|
||||
self.validate_item_qty()
|
||||
self.set_party_item_code()
|
||||
self.set_base_rates()
|
||||
|
||||
def set_currency(self):
|
||||
if self.currency:
|
||||
return
|
||||
|
||||
config = blanket_order_pricing.get_order_type_config(self.blanket_order_type)
|
||||
party_type = config["party_type"]
|
||||
party = self.get(config["party_field"])
|
||||
party_currency = frappe.get_cached_value(party_type, party, "default_currency") if party else None
|
||||
self.currency = party_currency or get_company_currency(self.company)
|
||||
|
||||
def set_conversion_rate(self):
|
||||
company_currency = get_company_currency(self.company)
|
||||
if self.currency == company_currency:
|
||||
self.conversion_rate = 1.0
|
||||
elif not self.conversion_rate:
|
||||
self.conversion_rate = blanket_order_pricing.get_exchange_rate_to_company(self, self.currency)
|
||||
|
||||
validate_conversion_rate(
|
||||
self.currency,
|
||||
self.conversion_rate,
|
||||
self.meta.get_translated_label("conversion_rate"),
|
||||
self.company,
|
||||
)
|
||||
self.conversion_rate = flt(self.conversion_rate, self.precision("conversion_rate"))
|
||||
|
||||
def validate_dates(self):
|
||||
if getdate(self.from_date) > getdate(self.to_date):
|
||||
@@ -123,6 +163,26 @@ class BlanketOrder(Document):
|
||||
if flt(d.qty) <= 0:
|
||||
frappe.throw(_("Row {0}: Quantity must be greater than zero.").format(d.idx))
|
||||
|
||||
def set_base_rates(self):
|
||||
blanket_order_pricing.set_base_rates(self)
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def apply_price_list(
|
||||
doc: str | dict,
|
||||
item_name: str | None = None,
|
||||
reset_party_values: bool = False,
|
||||
reset_plc_conversion_rate: bool = False,
|
||||
reset_conversion_rate: bool = False,
|
||||
):
|
||||
return blanket_order_pricing.apply_price_list(
|
||||
doc=doc,
|
||||
item_name=item_name,
|
||||
reset_party_values=reset_party_values,
|
||||
reset_plc_conversion_rate=reset_plc_conversion_rate,
|
||||
reset_conversion_rate=reset_conversion_rate,
|
||||
)
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_order(source_name: str):
|
||||
@@ -136,14 +196,12 @@ def make_order(source_name: str):
|
||||
def update_item(source, target, source_parent):
|
||||
target_qty = source.get("qty") - source.get("ordered_qty")
|
||||
target.qty = target_qty if flt(target_qty) >= 0 else 0
|
||||
target.rate = source.get("rate")
|
||||
item = get_item_defaults(target.item_code, source_parent.company)
|
||||
if item:
|
||||
target.item_name = item.get("item_name")
|
||||
target.description = item.get("description")
|
||||
target.uom = item.get("stock_uom")
|
||||
target.against_blanket_order = 1
|
||||
target.blanket_order = source_name
|
||||
|
||||
target_doc = get_mapped_doc(
|
||||
"Blanket Order",
|
||||
@@ -156,7 +214,10 @@ def make_order(source_name: str):
|
||||
},
|
||||
"Blanket Order Item": {
|
||||
"doctype": doctype + " Item",
|
||||
"field_map": {"rate": "blanket_order_rate", "parent": "blanket_order"},
|
||||
"field_map": {
|
||||
"rate": "blanket_order_rate",
|
||||
"parent": "blanket_order",
|
||||
},
|
||||
"postprocess": update_item,
|
||||
"condition": lambda item: not (flt(item.qty)) or (flt(item.qty) - flt(item.ordered_qty)) > 0,
|
||||
},
|
||||
|
||||
@@ -0,0 +1,228 @@
|
||||
# Copyright (c) 2026, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.utils import cint, flt
|
||||
|
||||
from erpnext import get_company_currency
|
||||
from erpnext.accounts.party import get_default_price_list as get_party_default_price_list
|
||||
from erpnext.accounts.services.taxes import validate_conversion_rate
|
||||
from erpnext.setup.utils import get_exchange_rate
|
||||
from erpnext.stock.get_item_details import get_price_list_rate_for
|
||||
|
||||
_ORDER_TYPE_CONFIG = {
|
||||
"Selling": {
|
||||
"exchange_rate_type": "for_selling",
|
||||
"opposite_price_list_field": "buying_price_list",
|
||||
"party_field": "customer",
|
||||
"party_type": "Customer",
|
||||
"price_list_field": "selling_price_list",
|
||||
"price_list_type": "Selling",
|
||||
"settings_doctype": "Selling Settings",
|
||||
},
|
||||
"Purchasing": {
|
||||
"exchange_rate_type": "for_buying",
|
||||
"opposite_price_list_field": "selling_price_list",
|
||||
"party_field": "supplier",
|
||||
"party_type": "Supplier",
|
||||
"price_list_field": "buying_price_list",
|
||||
"price_list_type": "Buying",
|
||||
"settings_doctype": "Buying Settings",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def get_order_type_config(blanket_order_type):
|
||||
return _ORDER_TYPE_CONFIG[blanket_order_type]
|
||||
|
||||
|
||||
def get_exchange_rate_to_company(doc, currency):
|
||||
config = get_order_type_config(doc.blanket_order_type)
|
||||
return get_exchange_rate(
|
||||
currency,
|
||||
get_company_currency(doc.company),
|
||||
doc.from_date,
|
||||
config["exchange_rate_type"],
|
||||
)
|
||||
|
||||
|
||||
def set_price_list(doc, set_default=False, force_exchange_rate=False):
|
||||
config = get_order_type_config(doc.blanket_order_type)
|
||||
fieldname = config["price_list_field"]
|
||||
doc.set(config["opposite_price_list_field"], None)
|
||||
|
||||
if not doc.get(fieldname) and (doc.is_new() or set_default):
|
||||
doc.set(fieldname, get_default_price_list(doc))
|
||||
|
||||
price_list = doc.get(fieldname)
|
||||
if not price_list:
|
||||
clear_price_list(doc)
|
||||
return
|
||||
|
||||
price_list_type = config["price_list_type"].lower()
|
||||
price_list_details = frappe.get_cached_value(
|
||||
"Price List", price_list, ["currency", price_list_type, "enabled"], as_dict=True
|
||||
)
|
||||
if not price_list_details or not price_list_details.enabled:
|
||||
frappe.throw(_("Price List {0} is disabled or does not exist").format(frappe.bold(price_list)))
|
||||
if not price_list_details.get(price_list_type):
|
||||
frappe.throw(
|
||||
_("Price List {0} is not enabled for {1}").format(
|
||||
frappe.bold(price_list), frappe.bold(doc.blanket_order_type)
|
||||
)
|
||||
)
|
||||
|
||||
price_list_currency_changed = doc.price_list_currency != price_list_details.currency
|
||||
doc.price_list_currency = price_list_details.currency
|
||||
company_currency = get_company_currency(doc.company)
|
||||
if doc.price_list_currency == company_currency:
|
||||
doc.plc_conversion_rate = 1.0
|
||||
elif price_list_currency_changed or not doc.plc_conversion_rate or force_exchange_rate:
|
||||
doc.plc_conversion_rate = get_exchange_rate_to_company(doc, doc.price_list_currency)
|
||||
|
||||
validate_conversion_rate(
|
||||
doc.price_list_currency,
|
||||
doc.plc_conversion_rate,
|
||||
doc.meta.get_translated_label("plc_conversion_rate"),
|
||||
doc.company,
|
||||
)
|
||||
doc.plc_conversion_rate = flt(doc.plc_conversion_rate, doc.precision("plc_conversion_rate"))
|
||||
|
||||
|
||||
def clear_price_list(doc):
|
||||
doc.price_list_currency = None
|
||||
doc.plc_conversion_rate = 0
|
||||
for item in doc.items:
|
||||
item.price_list_rate = 0
|
||||
item.base_price_list_rate = 0
|
||||
|
||||
|
||||
def get_default_price_list(doc):
|
||||
config = get_order_type_config(doc.blanket_order_type)
|
||||
party_type = config["party_type"]
|
||||
party = doc.get(config["party_field"])
|
||||
if party:
|
||||
party_price_list = get_party_default_price_list(frappe.get_cached_doc(party_type, party))
|
||||
if party_price_list:
|
||||
return party_price_list
|
||||
|
||||
return frappe.db.get_single_value(config["settings_doctype"], config["price_list_field"])
|
||||
|
||||
|
||||
def get_price_list_rates(doc, item_name=None):
|
||||
price_list = doc.get(get_order_type_config(doc.blanket_order_type)["price_list_field"])
|
||||
items = [item for item in doc.items if item.item_code and (not item_name or item.name == item_name)]
|
||||
if not items:
|
||||
return []
|
||||
if not price_list:
|
||||
return [{"name": item.name, "price_list_rate": 0, "base_price_list_rate": 0} for item in items]
|
||||
|
||||
stock_uoms = dict(
|
||||
frappe.get_all(
|
||||
"Item",
|
||||
filters={"name": ("in", [item.item_code for item in items])},
|
||||
fields=["name", "stock_uom"],
|
||||
as_list=True,
|
||||
)
|
||||
)
|
||||
|
||||
ctx = frappe._dict(
|
||||
{
|
||||
"price_list": price_list,
|
||||
"customer": doc.customer,
|
||||
"supplier": doc.supplier,
|
||||
"transaction_date": doc.from_date,
|
||||
}
|
||||
)
|
||||
rates = []
|
||||
for item in items:
|
||||
stock_uom = stock_uoms.get(item.item_code)
|
||||
ctx.update(
|
||||
{
|
||||
"qty": flt(item.qty) or 1,
|
||||
"uom": stock_uom,
|
||||
"stock_uom": stock_uom,
|
||||
"conversion_factor": 1,
|
||||
}
|
||||
)
|
||||
price_list_rate = get_price_list_rate_for(ctx, item.item_code)
|
||||
rate_details = {"name": item.name, "price_list_rate": 0, "base_price_list_rate": 0}
|
||||
if price_list_rate is not None:
|
||||
rate = flt(price_list_rate) * flt(doc.plc_conversion_rate) / flt(doc.conversion_rate)
|
||||
price_list_rate, base_price_list_rate = get_rate_and_base_amount(
|
||||
doc, item, "price_list_rate", rate
|
||||
)
|
||||
rate, base_rate = get_rate_and_base_amount(doc, item, "rate", rate)
|
||||
rate_details.update(
|
||||
{
|
||||
"price_list_rate": price_list_rate,
|
||||
"base_price_list_rate": base_price_list_rate,
|
||||
"rate": rate,
|
||||
"base_rate": base_rate,
|
||||
}
|
||||
)
|
||||
rates.append(rate_details)
|
||||
|
||||
return rates
|
||||
|
||||
|
||||
def set_base_rates(doc):
|
||||
for item in doc.items:
|
||||
for fieldname in ("price_list_rate", "rate"):
|
||||
rate, base_rate = get_rate_and_base_amount(doc, item, fieldname, item.get(fieldname))
|
||||
item.set(fieldname, rate)
|
||||
item.set(f"base_{fieldname}", base_rate)
|
||||
|
||||
|
||||
def get_rate_and_base_amount(doc, item, fieldname, rate):
|
||||
rate = flt(rate, item.precision(fieldname))
|
||||
base_fieldname = f"base_{fieldname}"
|
||||
base_rate = flt(rate * flt(doc.conversion_rate), item.precision(base_fieldname))
|
||||
return rate, base_rate
|
||||
|
||||
|
||||
def apply_price_list(
|
||||
doc,
|
||||
item_name=None,
|
||||
reset_party_values=False,
|
||||
reset_plc_conversion_rate=False,
|
||||
reset_conversion_rate=False,
|
||||
):
|
||||
doc = frappe.get_doc(frappe.parse_json(doc))
|
||||
reset_party_values = cint(reset_party_values)
|
||||
reset_plc_conversion_rate = cint(reset_plc_conversion_rate)
|
||||
reset_conversion_rate = cint(reset_conversion_rate)
|
||||
if reset_party_values:
|
||||
doc.currency = None
|
||||
doc.conversion_rate = 0
|
||||
doc.selling_price_list = None
|
||||
doc.buying_price_list = None
|
||||
doc.price_list_currency = None
|
||||
doc.plc_conversion_rate = 0
|
||||
else:
|
||||
if reset_conversion_rate:
|
||||
doc.conversion_rate = 0
|
||||
if reset_plc_conversion_rate:
|
||||
doc.plc_conversion_rate = 0
|
||||
|
||||
doc.set_currency()
|
||||
doc.set_conversion_rate()
|
||||
set_price_list(
|
||||
doc,
|
||||
set_default=reset_party_values,
|
||||
force_exchange_rate=reset_party_values or reset_plc_conversion_rate,
|
||||
)
|
||||
|
||||
return {
|
||||
"parent": {
|
||||
"currency": doc.currency,
|
||||
"conversion_rate": doc.conversion_rate,
|
||||
"selling_price_list": doc.selling_price_list,
|
||||
"buying_price_list": doc.buying_price_list,
|
||||
"price_list_currency": doc.price_list_currency,
|
||||
"plc_conversion_rate": doc.plc_conversion_rate,
|
||||
},
|
||||
"children": get_price_list_rates(doc, item_name),
|
||||
}
|
||||
@@ -1,13 +1,18 @@
|
||||
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
from unittest.mock import patch
|
||||
|
||||
import frappe
|
||||
from frappe.utils import add_months, today
|
||||
from frappe.utils import add_months, flt, today
|
||||
|
||||
from erpnext import get_company_currency
|
||||
from erpnext.controllers.queries import get_blanket_orders
|
||||
from erpnext.stock.doctype.item.test_item import make_item
|
||||
from erpnext.stock.get_item_details import get_blanket_order_details
|
||||
from erpnext.tests.utils import ERPNextTestSuite
|
||||
|
||||
from .blanket_order import make_order
|
||||
from . import blanket_order_pricing
|
||||
from .blanket_order import apply_price_list, make_order
|
||||
|
||||
|
||||
class TestBlanketOrder(ERPNextTestSuite):
|
||||
@@ -184,21 +189,241 @@ class TestBlanketOrder(ERPNextTestSuite):
|
||||
with self.assertRaises(frappe.ValidationError):
|
||||
bo.insert()
|
||||
|
||||
def test_multicurrency_blanket_order(self):
|
||||
company_currency = get_company_currency("_Test Company")
|
||||
transaction_currency = "USD" if company_currency != "USD" else "EUR"
|
||||
conversion_rate = 80
|
||||
rate = 5
|
||||
|
||||
for blanket_order_type, target_doctypes in (
|
||||
("Selling", ("Sales Order", "Quotation")),
|
||||
("Purchasing", ("Purchase Order",)),
|
||||
):
|
||||
blanket_order = make_blanket_order(
|
||||
blanket_order_type=blanket_order_type,
|
||||
currency=transaction_currency,
|
||||
conversion_rate=conversion_rate,
|
||||
rate=rate,
|
||||
)
|
||||
|
||||
self.assertEqual(blanket_order.currency, transaction_currency)
|
||||
self.assertEqual(blanket_order.conversion_rate, conversion_rate)
|
||||
self.assertEqual(blanket_order.items[0].base_rate, rate * conversion_rate)
|
||||
|
||||
for target_doctype in target_doctypes:
|
||||
with self.subTest(target_doctype=target_doctype):
|
||||
frappe.flags.args.doctype = target_doctype
|
||||
target = make_order(blanket_order.name)
|
||||
|
||||
self.assertEqual(target.currency, transaction_currency)
|
||||
self.assertEqual(target.conversion_rate, conversion_rate)
|
||||
self.assertEqual(target.items[0].rate, rate)
|
||||
self.assertEqual(target.items[0].base_rate, rate * conversion_rate)
|
||||
self.assertEqual(target.items[0].blanket_order_rate, rate)
|
||||
self.assertEqual(target.items[0].blanket_order, blanket_order.name)
|
||||
|
||||
def test_price_list_rates_and_mapping(self):
|
||||
company = "_Test Company"
|
||||
company_currency = get_company_currency(company)
|
||||
transaction_currency = "USD" if company_currency != "USD" else "EUR"
|
||||
conversion_rate = 80
|
||||
price_list_rate = 800
|
||||
|
||||
for blanket_order_type, price_list_field, target_doctypes in (
|
||||
("Selling", "selling_price_list", ("Sales Order", "Quotation")),
|
||||
("Purchasing", "buying_price_list", ("Purchase Order",)),
|
||||
):
|
||||
blanket_order, price_list = make_priced_blanket_order(
|
||||
blanket_order_type=blanket_order_type,
|
||||
company=company,
|
||||
currency=transaction_currency,
|
||||
conversion_rate=conversion_rate,
|
||||
price_list_rate=price_list_rate,
|
||||
qty=1000,
|
||||
)
|
||||
blanket_order.insert()
|
||||
blanket_order.submit()
|
||||
|
||||
expected_rate = price_list_rate / conversion_rate
|
||||
self.assertEqual(blanket_order.price_list_currency, company_currency)
|
||||
self.assertEqual(blanket_order.plc_conversion_rate, 1)
|
||||
self.assertEqual(blanket_order.items[0].price_list_rate, expected_rate)
|
||||
self.assertEqual(blanket_order.items[0].base_price_list_rate, price_list_rate)
|
||||
self.assertEqual(blanket_order.items[0].rate, expected_rate)
|
||||
self.assertEqual(blanket_order.items[0].base_rate, price_list_rate)
|
||||
|
||||
for target_doctype in target_doctypes:
|
||||
with self.subTest(target_doctype=target_doctype):
|
||||
frappe.flags.args.doctype = target_doctype
|
||||
target = make_order(blanket_order.name)
|
||||
|
||||
self.assertEqual(target.get(price_list_field), price_list)
|
||||
self.assertEqual(target.price_list_currency, company_currency)
|
||||
self.assertEqual(target.plc_conversion_rate, 1)
|
||||
self.assertEqual(target.items[0].price_list_rate, expected_rate)
|
||||
self.assertEqual(target.items[0].base_price_list_rate, price_list_rate)
|
||||
self.assertEqual(target.items[0].rate, expected_rate)
|
||||
self.assertEqual(target.items[0].blanket_order, blanket_order.name)
|
||||
|
||||
def test_applying_price_list_ignores_empty_item_rows(self):
|
||||
blanket_order = frappe.new_doc("Blanket Order")
|
||||
blanket_order.blanket_order_type = "Selling"
|
||||
blanket_order.company = "_Test Company"
|
||||
blanket_order.customer = "_Test Customer"
|
||||
blanket_order.from_date = today()
|
||||
blanket_order.append("items", {})
|
||||
|
||||
pricing = apply_price_list(blanket_order.as_dict())
|
||||
|
||||
self.assertEqual(pricing["children"], [])
|
||||
|
||||
def test_price_list_rate_is_fetched_on_item_selection(self):
|
||||
company = "_Test Company"
|
||||
company_currency = get_company_currency(company)
|
||||
price_list_rate = 800
|
||||
blanket_order, _price_list = make_priced_blanket_order(
|
||||
company=company,
|
||||
currency=company_currency,
|
||||
conversion_rate=1,
|
||||
price_list_rate=price_list_rate,
|
||||
qty=0,
|
||||
)
|
||||
item = blanket_order.items[0]
|
||||
|
||||
self.assertEqual(item.price_list_rate, price_list_rate)
|
||||
self.assertEqual(item.rate, price_list_rate)
|
||||
|
||||
def test_price_list_rates_fetch_item_uoms_once(self):
|
||||
blanket_order = new_blanket_order("Selling")
|
||||
blanket_order.selling_price_list = "_Test Price List"
|
||||
for item_code in ("ITEM-1", "ITEM-2"):
|
||||
blanket_order.append("items", {"item_code": item_code, "qty": 1})
|
||||
|
||||
with (
|
||||
patch.object(
|
||||
blanket_order_pricing.frappe,
|
||||
"get_all",
|
||||
return_value=[["ITEM-1", "Nos"], ["ITEM-2", "Nos"]],
|
||||
) as get_all,
|
||||
patch.object(blanket_order_pricing, "get_price_list_rate_for", return_value=None),
|
||||
):
|
||||
rates = blanket_order_pricing.get_price_list_rates(blanket_order)
|
||||
|
||||
self.assertEqual(len(rates), 2)
|
||||
get_all.assert_called_once_with(
|
||||
"Item",
|
||||
filters={"name": ("in", ["ITEM-1", "ITEM-2"])},
|
||||
fields=["name", "stock_uom"],
|
||||
as_list=True,
|
||||
)
|
||||
|
||||
def test_price_list_conversion_uses_currency_precision(self):
|
||||
company = "_Test Company"
|
||||
company_currency = get_company_currency(company)
|
||||
transaction_currency = "USD" if company_currency != "USD" else "EUR"
|
||||
conversion_rate = 95.47
|
||||
price_list_rate = 100
|
||||
blanket_order, _price_list = make_priced_blanket_order(
|
||||
company=company,
|
||||
currency=transaction_currency,
|
||||
conversion_rate=conversion_rate,
|
||||
price_list_rate=price_list_rate,
|
||||
)
|
||||
item = blanket_order.items[0]
|
||||
expected_rate = flt(price_list_rate / conversion_rate, item.precision("rate"))
|
||||
expected_base_rate = flt(expected_rate * conversion_rate, item.precision("base_rate"))
|
||||
|
||||
self.assertFalse(frappe.get_meta("Blanket Order Item").get_field("rate").precision)
|
||||
self.assertEqual(item.price_list_rate, expected_rate)
|
||||
self.assertEqual(item.base_price_list_rate, expected_base_rate)
|
||||
self.assertEqual(item.rate, expected_rate)
|
||||
self.assertEqual(item.base_rate, expected_base_rate)
|
||||
|
||||
blanket_order.insert()
|
||||
blanket_order.submit()
|
||||
|
||||
frappe.flags.args.doctype = "Sales Order"
|
||||
sales_order = make_order(blanket_order.name)
|
||||
sales_order.delivery_date = today()
|
||||
sales_order.insert()
|
||||
|
||||
self.assertEqual(sales_order.items[0].price_list_rate, item.price_list_rate)
|
||||
self.assertEqual(sales_order.items[0].base_price_list_rate, item.base_price_list_rate)
|
||||
self.assertEqual(sales_order.items[0].rate, item.rate)
|
||||
self.assertEqual(sales_order.items[0].base_rate, item.base_rate)
|
||||
|
||||
def test_applying_price_list_can_reset_conversion_rate(self):
|
||||
company_currency = get_company_currency("_Test Company")
|
||||
transaction_currency = "USD" if company_currency != "USD" else "EUR"
|
||||
blanket_order, _price_list = make_priced_blanket_order(
|
||||
currency=transaction_currency,
|
||||
conversion_rate=80,
|
||||
price_list_rate=100,
|
||||
)
|
||||
|
||||
with patch(
|
||||
"erpnext.manufacturing.doctype.blanket_order.blanket_order_pricing.get_exchange_rate",
|
||||
return_value=95.47,
|
||||
):
|
||||
pricing = apply_price_list(blanket_order.as_dict(), reset_conversion_rate=True)
|
||||
|
||||
self.assertEqual(pricing["parent"]["conversion_rate"], 95.47)
|
||||
expected_rate = flt(
|
||||
100 / pricing["parent"]["conversion_rate"],
|
||||
blanket_order.items[0].precision("rate"),
|
||||
)
|
||||
expected_base_rate = flt(
|
||||
expected_rate * pricing["parent"]["conversion_rate"],
|
||||
blanket_order.items[0].precision("base_rate"),
|
||||
)
|
||||
self.assertEqual(pricing["children"][0]["base_rate"], expected_base_rate)
|
||||
|
||||
def test_blanket_order_lookup_filters_currency(self):
|
||||
company_currency = get_company_currency("_Test Company")
|
||||
transaction_currency = "USD" if company_currency != "USD" else "EUR"
|
||||
blanket_order = make_blanket_order(
|
||||
blanket_order_type="Selling",
|
||||
currency=transaction_currency,
|
||||
conversion_rate=80,
|
||||
)
|
||||
|
||||
filters = {
|
||||
"company": blanket_order.company,
|
||||
"currency": transaction_currency,
|
||||
"blanket_order_type": "Selling",
|
||||
"item": blanket_order.items[0].item_code,
|
||||
}
|
||||
matching_orders = get_blanket_orders("Blanket Order", "", "name", 0, 20, filters)
|
||||
self.assertIn(blanket_order.name, [order[0] for order in matching_orders])
|
||||
|
||||
filters["currency"] = company_currency
|
||||
other_currency_orders = get_blanket_orders("Blanket Order", "", "name", 0, 20, filters)
|
||||
self.assertNotIn(blanket_order.name, [order[0] for order in other_currency_orders])
|
||||
|
||||
details = get_blanket_order_details(
|
||||
{
|
||||
"blanket_order": blanket_order.name,
|
||||
"company": blanket_order.company,
|
||||
"currency": company_currency,
|
||||
"customer": blanket_order.customer,
|
||||
"doctype": "Sales Order",
|
||||
"item_code": blanket_order.items[0].item_code,
|
||||
"transaction_date": today(),
|
||||
}
|
||||
)
|
||||
self.assertFalse(details)
|
||||
|
||||
|
||||
def make_blanket_order(**args):
|
||||
args = frappe._dict(args)
|
||||
bo = frappe.new_doc("Blanket Order")
|
||||
bo.blanket_order_type = args.blanket_order_type
|
||||
bo.company = args.company or "_Test Company"
|
||||
|
||||
if args.blanket_order_type == "Selling":
|
||||
bo.customer = args.customer or "_Test Customer"
|
||||
else:
|
||||
bo.supplier = args.supplier or "_Test Supplier"
|
||||
|
||||
bo.from_date = today()
|
||||
bo.to_date = add_months(bo.from_date, months=12)
|
||||
|
||||
bo = new_blanket_order(
|
||||
blanket_order_type=args.blanket_order_type,
|
||||
company=args.company or "_Test Company",
|
||||
currency=args.currency,
|
||||
conversion_rate=args.conversion_rate or 1,
|
||||
customer=args.customer,
|
||||
supplier=args.supplier,
|
||||
)
|
||||
bo.append(
|
||||
"items",
|
||||
{
|
||||
@@ -211,3 +436,84 @@ def make_blanket_order(**args):
|
||||
bo.insert()
|
||||
bo.submit()
|
||||
return bo
|
||||
|
||||
|
||||
def make_priced_blanket_order(
|
||||
blanket_order_type="Selling",
|
||||
company="_Test Company",
|
||||
currency=None,
|
||||
conversion_rate=1,
|
||||
price_list_rate=800,
|
||||
qty=1,
|
||||
):
|
||||
price_list = make_blanket_order_price_list(get_company_currency(company), price_list_rate)
|
||||
blanket_order = new_blanket_order(
|
||||
blanket_order_type=blanket_order_type,
|
||||
company=company,
|
||||
currency=currency,
|
||||
conversion_rate=conversion_rate,
|
||||
)
|
||||
config = blanket_order_pricing.get_order_type_config(blanket_order_type)
|
||||
blanket_order.set(config["price_list_field"], price_list)
|
||||
item = blanket_order.append("items", {"item_code": "_Test Item", "qty": qty, "rate": 0})
|
||||
pricing = apply_price_list(blanket_order.as_dict())
|
||||
blanket_order.update(pricing["parent"])
|
||||
item.update({key: value for key, value in pricing["children"][0].items() if key != "name"})
|
||||
|
||||
return blanket_order, price_list
|
||||
|
||||
|
||||
def new_blanket_order(
|
||||
blanket_order_type,
|
||||
company="_Test Company",
|
||||
currency=None,
|
||||
conversion_rate=1,
|
||||
customer=None,
|
||||
supplier=None,
|
||||
):
|
||||
blanket_order = frappe.new_doc("Blanket Order")
|
||||
blanket_order.blanket_order_type = blanket_order_type
|
||||
blanket_order.company = company
|
||||
blanket_order.currency = currency or get_company_currency(company)
|
||||
blanket_order.conversion_rate = conversion_rate
|
||||
blanket_order.from_date = today()
|
||||
blanket_order.to_date = add_months(blanket_order.from_date, months=12)
|
||||
|
||||
config = blanket_order_pricing.get_order_type_config(blanket_order_type)
|
||||
party = customer if config["party_field"] == "customer" else supplier
|
||||
blanket_order.set(config["party_field"], party or f"_Test {config['party_type']}")
|
||||
|
||||
return blanket_order
|
||||
|
||||
|
||||
def make_blanket_order_price_list(currency, price_list_rate):
|
||||
price_list = "_Test Blanket Order Price List"
|
||||
if not frappe.db.exists("Price List", price_list):
|
||||
frappe.get_doc(
|
||||
{
|
||||
"doctype": "Price List",
|
||||
"price_list_name": price_list,
|
||||
"currency": currency,
|
||||
"selling": 1,
|
||||
"buying": 1,
|
||||
}
|
||||
).insert()
|
||||
else:
|
||||
frappe.db.set_value("Price List", price_list, {"currency": currency, "selling": 1, "buying": 1})
|
||||
|
||||
item_price = frappe.db.get_value(
|
||||
"Item Price", {"price_list": price_list, "item_code": "_Test Item"}, "name"
|
||||
)
|
||||
if item_price:
|
||||
frappe.db.set_value("Item Price", item_price, "price_list_rate", price_list_rate)
|
||||
else:
|
||||
frappe.get_doc(
|
||||
{
|
||||
"doctype": "Item Price",
|
||||
"price_list": price_list,
|
||||
"item_code": "_Test Item",
|
||||
"price_list_rate": price_list_rate,
|
||||
}
|
||||
).insert()
|
||||
|
||||
return price_list
|
||||
|
||||
@@ -10,7 +10,10 @@
|
||||
"party_item_code",
|
||||
"column_break_3",
|
||||
"qty",
|
||||
"price_list_rate",
|
||||
"base_price_list_rate",
|
||||
"rate",
|
||||
"base_rate",
|
||||
"ordered_qty",
|
||||
"section_break_7",
|
||||
"terms_and_conditions"
|
||||
@@ -41,11 +44,37 @@
|
||||
"in_list_view": 1,
|
||||
"label": "Quantity"
|
||||
},
|
||||
{
|
||||
"fieldname": "price_list_rate",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Price List Rate",
|
||||
"options": "currency",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "base_price_list_rate",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Price List Rate (Company Currency)",
|
||||
"options": "Company:company:default_currency",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "rate",
|
||||
"fieldtype": "Currency",
|
||||
"in_list_view": 1,
|
||||
"label": "Rate",
|
||||
"options": "currency",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "base_rate",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Rate (Company Currency)",
|
||||
"options": "Company:company:default_currency",
|
||||
"print_hide": 1,
|
||||
"read_only": 1,
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
@@ -74,7 +103,7 @@
|
||||
],
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2024-03-27 13:06:40.083042",
|
||||
"modified": "2026-08-27 10:55:37.000000",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Manufacturing",
|
||||
"name": "Blanket Order Item",
|
||||
@@ -85,4 +114,4 @@
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"track_changes": 1
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,8 @@ class BlanketOrderItem(Document):
|
||||
if TYPE_CHECKING:
|
||||
from frappe.types import DF
|
||||
|
||||
base_price_list_rate: DF.Currency
|
||||
base_rate: DF.Currency
|
||||
item_code: DF.Link
|
||||
item_name: DF.Data | None
|
||||
ordered_qty: DF.Float
|
||||
@@ -21,6 +23,7 @@ class BlanketOrderItem(Document):
|
||||
parentfield: DF.Data
|
||||
parenttype: DF.Data
|
||||
party_item_code: DF.Data | None
|
||||
price_list_rate: DF.Currency
|
||||
qty: DF.Float
|
||||
rate: DF.Currency
|
||||
terms_and_conditions: DF.Text | None
|
||||
|
||||
@@ -513,6 +513,7 @@ erpnext.patches.v16_0.set_work_order_requested_and_picked_qty
|
||||
erpnext.patches.v16_0.rename_italy_customer_name_fields
|
||||
erpnext.patches.v16_0.recalculate_purchase_receipt_billing_status
|
||||
erpnext.patches.v16_0.recalculate_mixed_purchase_receipt_billing_status
|
||||
erpnext.patches.v16_0.add_currency_to_blanket_orders
|
||||
erpnext.patches.v16_0.repair_work_order_material_transfer
|
||||
erpnext.patches.v16_0.remove_frappe_crm_custom_fields
|
||||
erpnext.patches.v16_0.add_batch_split_stock_entry_type
|
||||
|
||||
23
erpnext/patches/v16_0/add_currency_to_blanket_orders.py
Normal file
23
erpnext/patches/v16_0/add_currency_to_blanket_orders.py
Normal file
@@ -0,0 +1,23 @@
|
||||
import frappe
|
||||
|
||||
|
||||
def execute():
|
||||
company_currencies = dict(frappe.get_all("Company", fields=["name", "default_currency"], as_list=True))
|
||||
blanket_order_updates = {
|
||||
order.name: {
|
||||
"currency": company_currencies.get(order.company),
|
||||
"conversion_rate": 1.0,
|
||||
}
|
||||
for order in frappe.get_all("Blanket Order", fields=["name", "company", "currency"])
|
||||
if not order.currency
|
||||
}
|
||||
if blanket_order_updates:
|
||||
frappe.db.bulk_update("Blanket Order", blanket_order_updates, update_modified=False)
|
||||
|
||||
item_updates = {
|
||||
item.name: {"base_rate": item.rate}
|
||||
for item in frappe.get_all("Blanket Order Item", fields=["name", "rate", "base_rate"])
|
||||
if not item.base_rate
|
||||
}
|
||||
if item_updates:
|
||||
frappe.db.bulk_update("Blanket Order Item", item_updates, update_modified=False)
|
||||
@@ -334,6 +334,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
||||
query: "erpnext.controllers.queries.get_blanket_orders",
|
||||
filters: {
|
||||
company: doc.company,
|
||||
currency: doc.currency,
|
||||
blanket_order_type: doc.doctype === "Sales Order" ? "Selling" : "Purchasing",
|
||||
item: item.item_code,
|
||||
},
|
||||
@@ -3267,10 +3268,12 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
||||
method: "erpnext.stock.get_item_details.get_blanket_order_details",
|
||||
args: {
|
||||
ctx: {
|
||||
doctype: doc.doctype,
|
||||
item_code: item.item_code,
|
||||
customer: doc.customer,
|
||||
supplier: doc.supplier,
|
||||
company: doc.company,
|
||||
currency: doc.currency,
|
||||
transaction_date: doc.transaction_date,
|
||||
blanket_order: item.blanket_order,
|
||||
},
|
||||
|
||||
@@ -626,6 +626,7 @@
|
||||
"fieldtype": "Currency",
|
||||
"label": "Blanket Order Rate",
|
||||
"no_copy": 1,
|
||||
"options": "currency",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
@@ -730,7 +731,7 @@
|
||||
"idx": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2026-08-07 17:31:31.732720",
|
||||
"modified": "2026-08-27 10:55:37.000000",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Selling",
|
||||
"name": "Quotation Item",
|
||||
|
||||
@@ -662,6 +662,7 @@
|
||||
"fieldtype": "Currency",
|
||||
"label": "Blanket Order Rate",
|
||||
"no_copy": 1,
|
||||
"options": "currency",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
@@ -1066,7 +1067,7 @@
|
||||
"idx": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2026-08-25 10:00:00.000000",
|
||||
"modified": "2026-08-27 10:55:37.000000",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Selling",
|
||||
"name": "Sales Order Item",
|
||||
|
||||
@@ -1890,6 +1890,8 @@ def get_blanket_order_details(ctx: ItemDetailsCtx):
|
||||
query = query.where(bo.supplier == ctx.supplier)
|
||||
if ctx.blanket_order:
|
||||
query = query.where(bo.name == ctx.blanket_order)
|
||||
if ctx.currency:
|
||||
query = query.where(bo.currency == ctx.currency)
|
||||
if ctx.transaction_date:
|
||||
query = query.where(bo.to_date >= ctx.transaction_date)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user