mirror of
https://github.com/frappe/erpnext.git
synced 2026-09-09 15:29:30 +00:00
This commit is contained in:
@@ -559,6 +559,7 @@
|
|||||||
"fieldname": "blanket_order_rate",
|
"fieldname": "blanket_order_rate",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"label": "Blanket Order Rate",
|
"label": "Blanket Order Rate",
|
||||||
|
"options": "currency",
|
||||||
"print_hide": 1,
|
"print_hide": 1,
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
@@ -954,7 +955,7 @@
|
|||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2026-08-07 17:31:31.732720",
|
"modified": "2026-08-27 10:55:37.000000",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Buying",
|
"module": "Buying",
|
||||||
"name": "Purchase Order Item",
|
"name": "Purchase Order Item",
|
||||||
|
|||||||
@@ -639,7 +639,7 @@ def get_blanket_orders(doctype, txt, searchfield, start, page_len, filters):
|
|||||||
bo = frappe.qb.DocType("Blanket Order")
|
bo = frappe.qb.DocType("Blanket Order")
|
||||||
bo_item = frappe.qb.DocType("Blanket Order Item")
|
bo_item = frappe.qb.DocType("Blanket Order Item")
|
||||||
|
|
||||||
blanket_orders = (
|
query = (
|
||||||
frappe.qb.from_(bo)
|
frappe.qb.from_(bo)
|
||||||
.from_(bo_item)
|
.from_(bo_item)
|
||||||
.select(bo.name)
|
.select(bo.name)
|
||||||
@@ -652,10 +652,12 @@ def get_blanket_orders(doctype, txt, searchfield, start, page_len, filters):
|
|||||||
& (bo.company == filters.get("company"))
|
& (bo.company == filters.get("company"))
|
||||||
& (bo.docstatus == 1)
|
& (bo.docstatus == 1)
|
||||||
)
|
)
|
||||||
.run()
|
|
||||||
)
|
)
|
||||||
|
|
||||||
return blanket_orders
|
if currency := filters.get("currency"):
|
||||||
|
query = query.where(bo.currency == currency)
|
||||||
|
|
||||||
|
return query.run()
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
|
|||||||
@@ -4,6 +4,10 @@
|
|||||||
frappe.ui.form.on("Blanket Order", {
|
frappe.ui.form.on("Blanket Order", {
|
||||||
onload: function (frm) {
|
onload: function (frm) {
|
||||||
frm.trigger("set_tc_name_filter");
|
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) {
|
setup: function (frm) {
|
||||||
@@ -15,10 +19,13 @@ frappe.ui.form.on("Blanket Order", {
|
|||||||
|
|
||||||
frm.add_fetch("customer", "customer_name", "customer_name");
|
frm.add_fetch("customer", "customer_name", "customer_name");
|
||||||
frm.add_fetch("supplier", "supplier_name", "supplier_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) {
|
refresh: function (frm) {
|
||||||
erpnext.hide_company(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()) {
|
if (frm.doc.customer && frm.doc.docstatus === 1 && frm.doc.to_date > frappe.datetime.get_today()) {
|
||||||
frm.add_custom_button(
|
frm.add_custom_button(
|
||||||
__("Sales Order"),
|
__("Sales Order"),
|
||||||
@@ -101,5 +108,141 @@ frappe.ui.form.on("Blanket Order", {
|
|||||||
|
|
||||||
blanket_order_type: function (frm) {
|
blanket_order_type: function (frm) {
|
||||||
frm.trigger("set_tc_name_filter");
|
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",
|
"from_date",
|
||||||
"to_date",
|
"to_date",
|
||||||
"company",
|
"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",
|
"section_break_12",
|
||||||
"items",
|
"items",
|
||||||
"amended_from",
|
"amended_from",
|
||||||
@@ -96,6 +104,66 @@
|
|||||||
"reqd": 1,
|
"reqd": 1,
|
||||||
"search_index": 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",
|
"fieldname": "section_break_12",
|
||||||
"fieldtype": "Section Break"
|
"fieldtype": "Section Break"
|
||||||
@@ -147,7 +215,7 @@
|
|||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2024-12-05 15:44:21.520093",
|
"modified": "2026-08-27 10:55:37.000000",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Manufacturing",
|
"module": "Manufacturing",
|
||||||
"name": "Blanket Order",
|
"name": "Blanket Order",
|
||||||
@@ -175,4 +243,4 @@
|
|||||||
"sort_order": "DESC",
|
"sort_order": "DESC",
|
||||||
"states": [],
|
"states": [],
|
||||||
"track_changes": 1
|
"track_changes": 1
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,9 @@ from frappe.model.mapper import get_mapped_doc
|
|||||||
from frappe.query_builder.functions import Sum
|
from frappe.query_builder.functions import Sum
|
||||||
from frappe.utils import flt, getdate
|
from frappe.utils import flt, getdate
|
||||||
|
|
||||||
|
from erpnext import get_company_currency
|
||||||
|
from erpnext.controllers.accounts_controller import validate_conversion_rate
|
||||||
|
from erpnext.manufacturing.doctype.blanket_order import blanket_order_pricing
|
||||||
from erpnext.stock.doctype.item.item import get_item_defaults
|
from erpnext.stock.doctype.item.item import get_item_defaults
|
||||||
|
|
||||||
|
|
||||||
@@ -25,7 +28,10 @@ class BlanketOrder(Document):
|
|||||||
|
|
||||||
amended_from: DF.Link | None
|
amended_from: DF.Link | None
|
||||||
blanket_order_type: DF.Literal["", "Selling", "Purchasing"]
|
blanket_order_type: DF.Literal["", "Selling", "Purchasing"]
|
||||||
|
buying_price_list: DF.Link | None
|
||||||
company: DF.Link
|
company: DF.Link
|
||||||
|
conversion_rate: DF.Float
|
||||||
|
currency: DF.Link
|
||||||
customer: DF.Link | None
|
customer: DF.Link | None
|
||||||
customer_name: DF.Data | None
|
customer_name: DF.Data | None
|
||||||
from_date: DF.Date
|
from_date: DF.Date
|
||||||
@@ -33,6 +39,9 @@ class BlanketOrder(Document):
|
|||||||
naming_series: DF.Literal["MFG-BLR-.YYYY.-"]
|
naming_series: DF.Literal["MFG-BLR-.YYYY.-"]
|
||||||
order_date: DF.Date | None
|
order_date: DF.Date | None
|
||||||
order_no: DF.Data | 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: DF.Link | None
|
||||||
supplier_name: DF.Data | None
|
supplier_name: DF.Data | None
|
||||||
tc_name: DF.Link | None
|
tc_name: DF.Link | None
|
||||||
@@ -40,11 +49,42 @@ class BlanketOrder(Document):
|
|||||||
to_date: DF.Date
|
to_date: DF.Date
|
||||||
# end: auto-generated types
|
# 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):
|
def validate(self):
|
||||||
self.validate_dates()
|
self.validate_dates()
|
||||||
self.validate_duplicate_items()
|
self.validate_duplicate_items()
|
||||||
self.validate_item_qty()
|
self.validate_item_qty()
|
||||||
self.set_party_item_code()
|
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):
|
def validate_dates(self):
|
||||||
if getdate(self.from_date) > getdate(self.to_date):
|
if getdate(self.from_date) > getdate(self.to_date):
|
||||||
@@ -123,6 +163,26 @@ class BlanketOrder(Document):
|
|||||||
if flt(d.qty) < 0:
|
if flt(d.qty) < 0:
|
||||||
frappe.throw(_("Row {0}: Quantity cannot be negative.").format(d.idx))
|
frappe.throw(_("Row {0}: Quantity cannot be negative.").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()
|
@frappe.whitelist()
|
||||||
def make_order(source_name):
|
def make_order(source_name):
|
||||||
@@ -136,14 +196,12 @@ def make_order(source_name):
|
|||||||
def update_item(source, target, source_parent):
|
def update_item(source, target, source_parent):
|
||||||
target_qty = source.get("qty") - source.get("ordered_qty")
|
target_qty = source.get("qty") - source.get("ordered_qty")
|
||||||
target.qty = target_qty if flt(target_qty) >= 0 else 0
|
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)
|
item = get_item_defaults(target.item_code, source_parent.company)
|
||||||
if item:
|
if item:
|
||||||
target.item_name = item.get("item_name")
|
target.item_name = item.get("item_name")
|
||||||
target.description = item.get("description")
|
target.description = item.get("description")
|
||||||
target.uom = item.get("stock_uom")
|
target.uom = item.get("stock_uom")
|
||||||
target.against_blanket_order = 1
|
target.against_blanket_order = 1
|
||||||
target.blanket_order = source_name
|
|
||||||
|
|
||||||
target_doc = get_mapped_doc(
|
target_doc = get_mapped_doc(
|
||||||
"Blanket Order",
|
"Blanket Order",
|
||||||
@@ -152,7 +210,10 @@ def make_order(source_name):
|
|||||||
"Blanket Order": {"doctype": doctype, "postprocess": update_doc},
|
"Blanket Order": {"doctype": doctype, "postprocess": update_doc},
|
||||||
"Blanket Order Item": {
|
"Blanket Order Item": {
|
||||||
"doctype": doctype + " 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,
|
"postprocess": update_item,
|
||||||
"condition": lambda item: not (flt(item.qty)) or (flt(item.qty) - flt(item.ordered_qty)) > 0,
|
"condition": lambda item: not (flt(item.qty)) or (flt(item.qty) - flt(item.ordered_qty)) > 0,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -0,0 +1,218 @@
|
|||||||
|
# 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.controllers.accounts_controller import validate_conversion_rate
|
||||||
|
from erpnext.setup.utils import get_exchange_rate
|
||||||
|
from erpnext.stock.get_item_details import get_item_prices_for_stock_uom
|
||||||
|
|
||||||
|
_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]
|
||||||
|
|
||||||
|
ctx = frappe._dict(
|
||||||
|
{
|
||||||
|
"price_list": price_list,
|
||||||
|
"customer": doc.customer,
|
||||||
|
"supplier": doc.supplier,
|
||||||
|
"transaction_date": doc.from_date,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
item_prices = get_item_prices_for_stock_uom(ctx, list(dict.fromkeys(item.item_code for item in items)))
|
||||||
|
rates = []
|
||||||
|
for item in items:
|
||||||
|
item_price = item_prices.get(item.item_code)
|
||||||
|
qty = flt(item.qty) or 1
|
||||||
|
packing_unit = flt(item_price.packing_unit) if item_price else 0
|
||||||
|
price_list_rate = (
|
||||||
|
item_price.price_list_rate
|
||||||
|
if item_price and (not packing_unit or qty % packing_unit == 0)
|
||||||
|
else None
|
||||||
|
)
|
||||||
|
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
|
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
# See license.txt
|
# See license.txt
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
import frappe
|
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 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.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 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):
|
class TestBlanketOrder(ERPNextTestSuite):
|
||||||
@@ -139,21 +144,241 @@ class TestBlanketOrder(ERPNextTestSuite):
|
|||||||
bo = make_blanket_order(blanket_order_type="Purchasing", supplier=supplier, item_code=item_code)
|
bo = make_blanket_order(blanket_order_type="Purchasing", supplier=supplier, item_code=item_code)
|
||||||
self.assertEqual(bo.items[0].party_item_code, "SUPP-PART-1")
|
self.assertEqual(bo.items[0].party_item_code, "SUPP-PART-1")
|
||||||
|
|
||||||
|
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_prices_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, "get_item_prices_for_stock_uom", return_value={}
|
||||||
|
) as get_item_prices:
|
||||||
|
rates = blanket_order_pricing.get_price_list_rates(blanket_order)
|
||||||
|
|
||||||
|
self.assertEqual(len(rates), 2)
|
||||||
|
get_item_prices.assert_called_once_with(
|
||||||
|
frappe._dict(
|
||||||
|
{
|
||||||
|
"price_list": "_Test Price List",
|
||||||
|
"customer": "_Test Customer",
|
||||||
|
"supplier": None,
|
||||||
|
"transaction_date": blanket_order.from_date,
|
||||||
|
}
|
||||||
|
),
|
||||||
|
["ITEM-1", "ITEM-2"],
|
||||||
|
)
|
||||||
|
|
||||||
|
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):
|
def make_blanket_order(**args):
|
||||||
args = frappe._dict(args)
|
args = frappe._dict(args)
|
||||||
bo = frappe.new_doc("Blanket Order")
|
bo = new_blanket_order(
|
||||||
bo.blanket_order_type = args.blanket_order_type
|
blanket_order_type=args.blanket_order_type,
|
||||||
bo.company = args.company or "_Test Company"
|
company=args.company or "_Test Company",
|
||||||
|
currency=args.currency,
|
||||||
if args.blanket_order_type == "Selling":
|
conversion_rate=args.conversion_rate or 1,
|
||||||
bo.customer = args.customer or "_Test Customer"
|
customer=args.customer,
|
||||||
else:
|
supplier=args.supplier,
|
||||||
bo.supplier = args.supplier or "_Test Supplier"
|
)
|
||||||
|
|
||||||
bo.from_date = today()
|
|
||||||
bo.to_date = add_months(bo.from_date, months=12)
|
|
||||||
|
|
||||||
bo.append(
|
bo.append(
|
||||||
"items",
|
"items",
|
||||||
{
|
{
|
||||||
@@ -166,3 +391,84 @@ def make_blanket_order(**args):
|
|||||||
bo.insert()
|
bo.insert()
|
||||||
bo.submit()
|
bo.submit()
|
||||||
return bo
|
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",
|
"party_item_code",
|
||||||
"column_break_3",
|
"column_break_3",
|
||||||
"qty",
|
"qty",
|
||||||
|
"price_list_rate",
|
||||||
|
"base_price_list_rate",
|
||||||
"rate",
|
"rate",
|
||||||
|
"base_rate",
|
||||||
"ordered_qty",
|
"ordered_qty",
|
||||||
"section_break_7",
|
"section_break_7",
|
||||||
"terms_and_conditions"
|
"terms_and_conditions"
|
||||||
@@ -41,11 +44,37 @@
|
|||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"label": "Quantity"
|
"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",
|
"fieldname": "rate",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"label": "Rate",
|
"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
|
"reqd": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -74,7 +103,7 @@
|
|||||||
],
|
],
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2024-03-27 13:06:40.083042",
|
"modified": "2026-08-27 10:55:37.000000",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Manufacturing",
|
"module": "Manufacturing",
|
||||||
"name": "Blanket Order Item",
|
"name": "Blanket Order Item",
|
||||||
@@ -85,4 +114,4 @@
|
|||||||
"sort_order": "DESC",
|
"sort_order": "DESC",
|
||||||
"states": [],
|
"states": [],
|
||||||
"track_changes": 1
|
"track_changes": 1
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ class BlanketOrderItem(Document):
|
|||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from frappe.types import DF
|
from frappe.types import DF
|
||||||
|
|
||||||
|
base_price_list_rate: DF.Currency
|
||||||
|
base_rate: DF.Currency
|
||||||
item_code: DF.Link
|
item_code: DF.Link
|
||||||
item_name: DF.Data | None
|
item_name: DF.Data | None
|
||||||
ordered_qty: DF.Float
|
ordered_qty: DF.Float
|
||||||
@@ -21,6 +23,7 @@ class BlanketOrderItem(Document):
|
|||||||
parentfield: DF.Data
|
parentfield: DF.Data
|
||||||
parenttype: DF.Data
|
parenttype: DF.Data
|
||||||
party_item_code: DF.Data | None
|
party_item_code: DF.Data | None
|
||||||
|
price_list_rate: DF.Currency
|
||||||
qty: DF.Float
|
qty: DF.Float
|
||||||
rate: DF.Currency
|
rate: DF.Currency
|
||||||
terms_and_conditions: DF.Text | None
|
terms_and_conditions: DF.Text | None
|
||||||
|
|||||||
@@ -499,6 +499,7 @@ erpnext.patches.v16_0.merge_seeded_item_group_root
|
|||||||
erpnext.patches.v16_0.rename_italy_customer_name_fields
|
erpnext.patches.v16_0.rename_italy_customer_name_fields
|
||||||
erpnext.patches.v16_0.set_stock_uom_in_job_card
|
erpnext.patches.v16_0.set_stock_uom_in_job_card
|
||||||
erpnext.patches.v16_0.recalculate_purchase_receipt_billing_status
|
erpnext.patches.v16_0.recalculate_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.repair_work_order_material_transfer
|
||||||
erpnext.patches.v16_0.remove_frappe_crm_custom_fields
|
erpnext.patches.v16_0.remove_frappe_crm_custom_fields
|
||||||
erpnext.patches.v16_0.rename_secondary_item_type_field
|
erpnext.patches.v16_0.rename_secondary_item_type_field
|
||||||
|
|||||||
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)
|
||||||
@@ -319,6 +319,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
|||||||
query: "erpnext.controllers.queries.get_blanket_orders",
|
query: "erpnext.controllers.queries.get_blanket_orders",
|
||||||
filters: {
|
filters: {
|
||||||
company: doc.company,
|
company: doc.company,
|
||||||
|
currency: doc.currency,
|
||||||
blanket_order_type: doc.doctype === "Sales Order" ? "Selling" : "Purchasing",
|
blanket_order_type: doc.doctype === "Sales Order" ? "Selling" : "Purchasing",
|
||||||
item: item.item_code,
|
item: item.item_code,
|
||||||
},
|
},
|
||||||
@@ -3182,10 +3183,12 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
|||||||
method: "erpnext.stock.get_item_details.get_blanket_order_details",
|
method: "erpnext.stock.get_item_details.get_blanket_order_details",
|
||||||
args: {
|
args: {
|
||||||
ctx: {
|
ctx: {
|
||||||
|
doctype: doc.doctype,
|
||||||
item_code: item.item_code,
|
item_code: item.item_code,
|
||||||
customer: doc.customer,
|
customer: doc.customer,
|
||||||
supplier: doc.supplier,
|
supplier: doc.supplier,
|
||||||
company: doc.company,
|
company: doc.company,
|
||||||
|
currency: doc.currency,
|
||||||
transaction_date: doc.transaction_date,
|
transaction_date: doc.transaction_date,
|
||||||
blanket_order: item.blanket_order,
|
blanket_order: item.blanket_order,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -608,6 +608,7 @@
|
|||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"label": "Blanket Order Rate",
|
"label": "Blanket Order Rate",
|
||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
|
"options": "currency",
|
||||||
"print_hide": 1,
|
"print_hide": 1,
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
@@ -712,7 +713,7 @@
|
|||||||
"idx": 1,
|
"idx": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2026-08-07 17:31:31.732720",
|
"modified": "2026-08-27 10:55:37.000000",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Selling",
|
"module": "Selling",
|
||||||
"name": "Quotation Item",
|
"name": "Quotation Item",
|
||||||
|
|||||||
@@ -632,6 +632,7 @@
|
|||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"label": "Blanket Order Rate",
|
"label": "Blanket Order Rate",
|
||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
|
"options": "currency",
|
||||||
"print_hide": 1,
|
"print_hide": 1,
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
@@ -1036,7 +1037,7 @@
|
|||||||
"idx": 1,
|
"idx": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2026-08-07 17:31:31.732720",
|
"modified": "2026-08-27 10:55:37.000000",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Selling",
|
"module": "Selling",
|
||||||
"name": "Sales Order Item",
|
"name": "Sales Order Item",
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ from frappe.model.meta import get_field_precision
|
|||||||
from frappe.model.utils import get_fetch_values
|
from frappe.model.utils import get_fetch_values
|
||||||
from frappe.query_builder.functions import IfNull, Sum
|
from frappe.query_builder.functions import IfNull, Sum
|
||||||
from frappe.utils import add_days, add_months, cint, cstr, flt, get_link_to_form, getdate, parse_json
|
from frappe.utils import add_days, add_months, cint, cstr, flt, get_link_to_form, getdate, parse_json
|
||||||
|
from pypika import Order
|
||||||
|
from pypika.analytics import RowNumber
|
||||||
|
|
||||||
import erpnext
|
import erpnext
|
||||||
from erpnext import get_company_currency
|
from erpnext import get_company_currency
|
||||||
@@ -1271,21 +1273,60 @@ def get_item_price(
|
|||||||
:param item_code: str, Item Doctype field item_code
|
:param item_code: str, Item Doctype field item_code
|
||||||
"""
|
"""
|
||||||
pctx: ItemPriceCtx = frappe._dict(pctx)
|
pctx: ItemPriceCtx = frappe._dict(pctx)
|
||||||
|
query, ip = _get_item_price_query(pctx, [item_code], ignore_party, force_batch_no)
|
||||||
|
query = query.select(ip.name, ip.price_list_rate, ip.uom)
|
||||||
|
query = _order_item_prices(query, ip, pctx)
|
||||||
|
|
||||||
ip = frappe.qb.DocType("Item Price")
|
return query.limit(1).run(as_dict=True)
|
||||||
query = (
|
|
||||||
frappe.qb.from_(ip)
|
|
||||||
.select(ip.name, ip.price_list_rate, ip.uom)
|
def get_item_prices_for_stock_uom(pctx: ItemPriceCtx | dict, item_codes: list[str]) -> dict[str, ItemDetails]:
|
||||||
.where(
|
"""Return the highest-priority Item Price for each item in its stock UOM."""
|
||||||
(ip.item_code == item_code)
|
if not item_codes:
|
||||||
& (ip.price_list == pctx.price_list)
|
return {}
|
||||||
& (IfNull(ip.uom, "").isin(["", pctx.uom]))
|
|
||||||
|
pctx: ItemPriceCtx = frappe._dict(pctx)
|
||||||
|
query, ip = _get_item_price_query(pctx, item_codes, match_uom=False)
|
||||||
|
item = frappe.qb.DocType("Item")
|
||||||
|
priority = _order_item_prices(RowNumber().over(ip.item_code), ip, pctx)
|
||||||
|
ranked_prices = (
|
||||||
|
query.inner_join(item)
|
||||||
|
.on(item.name == ip.item_code)
|
||||||
|
.select(
|
||||||
|
ip.item_code,
|
||||||
|
ip.name,
|
||||||
|
ip.price_list_rate,
|
||||||
|
ip.packing_unit,
|
||||||
|
priority.as_("priority"),
|
||||||
)
|
)
|
||||||
.orderby(ip.valid_from, order=frappe.qb.desc)
|
.where((IfNull(ip.uom, "") == "") | (ip.uom == item.stock_uom))
|
||||||
.orderby(IfNull(ip.batch_no, ""), order=frappe.qb.desc)
|
).as_("ranked_prices")
|
||||||
.orderby(ip.uom, order=frappe.qb.desc)
|
|
||||||
.limit(1)
|
prices = (
|
||||||
|
frappe.qb.from_(ranked_prices)
|
||||||
|
.select(
|
||||||
|
ranked_prices.item_code,
|
||||||
|
ranked_prices.name,
|
||||||
|
ranked_prices.price_list_rate,
|
||||||
|
ranked_prices.packing_unit,
|
||||||
|
)
|
||||||
|
.where(ranked_prices.priority == 1)
|
||||||
|
.run(as_dict=True)
|
||||||
)
|
)
|
||||||
|
return {price.item_code: price for price in prices}
|
||||||
|
|
||||||
|
|
||||||
|
def _get_item_price_query(
|
||||||
|
pctx: ItemPriceCtx,
|
||||||
|
item_codes: list[str],
|
||||||
|
ignore_party=False,
|
||||||
|
force_batch_no=False,
|
||||||
|
match_uom=True,
|
||||||
|
):
|
||||||
|
ip = frappe.qb.DocType("Item Price")
|
||||||
|
query = frappe.qb.from_(ip).where((ip.item_code.isin(item_codes)) & (ip.price_list == pctx.price_list))
|
||||||
|
if match_uom:
|
||||||
|
query = query.where(IfNull(ip.uom, "").isin(["", pctx.uom]))
|
||||||
|
|
||||||
if force_batch_no:
|
if force_batch_no:
|
||||||
query = query.where(ip.batch_no == pctx.batch_no)
|
query = query.where(ip.batch_no == pctx.batch_no)
|
||||||
@@ -1297,12 +1338,12 @@ def get_item_price(
|
|||||||
query = query.where(
|
query = query.where(
|
||||||
(ip.customer == pctx.customer)
|
(ip.customer == pctx.customer)
|
||||||
| ((IfNull(ip.customer, "") == "") & (IfNull(ip.supplier, "") == ""))
|
| ((IfNull(ip.customer, "") == "") & (IfNull(ip.supplier, "") == ""))
|
||||||
).orderby(IfNull(ip.customer, ""), order=frappe.qb.desc)
|
)
|
||||||
elif pctx.supplier:
|
elif pctx.supplier:
|
||||||
query = query.where(
|
query = query.where(
|
||||||
(ip.supplier == pctx.supplier)
|
(ip.supplier == pctx.supplier)
|
||||||
| ((IfNull(ip.customer, "") == "") & (IfNull(ip.supplier, "") == ""))
|
| ((IfNull(ip.customer, "") == "") & (IfNull(ip.supplier, "") == ""))
|
||||||
).orderby(IfNull(ip.supplier, ""), order=frappe.qb.desc)
|
)
|
||||||
else:
|
else:
|
||||||
query = query.where((IfNull(ip.customer, "") == "") & (IfNull(ip.supplier, "") == ""))
|
query = query.where((IfNull(ip.customer, "") == "") & (IfNull(ip.supplier, "") == ""))
|
||||||
|
|
||||||
@@ -1312,7 +1353,21 @@ def get_item_price(
|
|||||||
& (IfNull(ip.valid_upto, "2500-12-31") >= pctx.transaction_date)
|
& (IfNull(ip.valid_upto, "2500-12-31") >= pctx.transaction_date)
|
||||||
)
|
)
|
||||||
|
|
||||||
return query.run(as_dict=True)
|
return query, ip
|
||||||
|
|
||||||
|
|
||||||
|
def _order_item_prices(query, ip, pctx):
|
||||||
|
query = (
|
||||||
|
query.orderby(ip.valid_from, order=Order.desc)
|
||||||
|
.orderby(IfNull(ip.batch_no, ""), order=Order.desc)
|
||||||
|
.orderby(ip.uom, order=Order.desc)
|
||||||
|
)
|
||||||
|
if pctx.customer:
|
||||||
|
query = query.orderby(IfNull(ip.customer, ""), order=Order.desc)
|
||||||
|
elif pctx.supplier:
|
||||||
|
query = query.orderby(IfNull(ip.supplier, ""), order=Order.desc)
|
||||||
|
|
||||||
|
return query
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
@@ -1859,6 +1914,8 @@ def get_blanket_order_details(ctx: ItemDetailsCtx):
|
|||||||
query = query.where(bo.supplier == ctx.supplier)
|
query = query.where(bo.supplier == ctx.supplier)
|
||||||
if ctx.blanket_order:
|
if ctx.blanket_order:
|
||||||
query = query.where(bo.name == 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:
|
if ctx.transaction_date:
|
||||||
query = query.where(bo.to_date >= ctx.transaction_date)
|
query = query.where(bo.to_date >= ctx.transaction_date)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user