-
Notifications
You must be signed in to change notification settings - Fork 9.3k
feat: add multi-currency support to Blanket Order #49346
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from 3 commits
e6255e2
1d16449
e82baa3
d4ef686
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -26,6 +26,8 @@ class BlanketOrder(Document): | |||||||||||||
amended_from: DF.Link | None | ||||||||||||||
blanket_order_type: DF.Literal["", "Selling", "Purchasing"] | ||||||||||||||
company: DF.Link | ||||||||||||||
conversion_rate: DF.Float | ||||||||||||||
currency: DF.Link | ||||||||||||||
customer: DF.Link | None | ||||||||||||||
customer_name: DF.Data | None | ||||||||||||||
from_date: DF.Date | ||||||||||||||
|
@@ -45,6 +47,7 @@ def validate(self): | |||||||||||||
self.validate_duplicate_items() | ||||||||||||||
self.validate_item_qty() | ||||||||||||||
self.set_party_item_code() | ||||||||||||||
self.calculate_base_rate() | ||||||||||||||
|
||||||||||||||
def validate_dates(self): | ||||||||||||||
if getdate(self.from_date) > getdate(self.to_date): | ||||||||||||||
|
@@ -123,6 +126,10 @@ def validate_item_qty(self): | |||||||||||||
if d.qty < 0: | ||||||||||||||
frappe.throw(_("Row {0}: Quantity cannot be negative.").format(d.idx)) | ||||||||||||||
|
||||||||||||||
def calculate_base_rate(self): | ||||||||||||||
for d in self.items: | ||||||||||||||
d.base_rate = flt(d.rate * self.conversion_rate, d.precision("base_rate")) | ||||||||||||||
|
||||||||||||||
Comment on lines
+129
to
+132
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Guard against None/empty values in base rate calculation If - for d in self.items:
- d.base_rate = flt(d.rate * self.conversion_rate, d.precision("base_rate"))
+ for d in self.items:
+ d.base_rate = flt(flt(d.rate) * flt(self.conversion_rate), d.precision("base_rate")) 📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents
|
||||||||||||||
|
||||||||||||||
@frappe.whitelist() | ||||||||||||||
def make_order(source_name): | ||||||||||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -113,12 +113,36 @@ def test_party_item_code(self): | |
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") | ||
|
||
def test_multicurrency_blanket_order(self): | ||
from erpnext.buying.doctype.supplier.test_supplier import create_supplier | ||
|
||
supplier = create_supplier(supplier_name="_Test BO USD Supplier", default_currency="USD") | ||
bo = make_blanket_order( | ||
blanket_order_type="Purchasing", | ||
supplier=supplier.name, | ||
currency="USD", | ||
conversion_rate=86, | ||
quantity=10, | ||
rate=5, | ||
) | ||
self.assertEqual(bo.items[0].base_rate, 430) | ||
|
||
frappe.flags.args.doctype = "Purchase Order" | ||
po = make_order(bo.name) | ||
|
||
self.assertEqual(po.currency, "USD") | ||
self.assertEqual(po.conversion_rate, 86) | ||
self.assertEqual(po.items[0].rate, 5) | ||
self.assertEqual(po.items[0].base_rate, 430) | ||
|
||
Comment on lines
+116
to
+137
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Stabilize FX expectations in test to avoid flakiness The assertions assume USD→company FX is 86 on the test date. Seed a Currency Exchange row to make the test deterministic. def test_multicurrency_blanket_order(self):
from erpnext.buying.doctype.supplier.test_supplier import create_supplier
+ from frappe.utils import today
+ company_currency = get_company_currency("_Test Company")
+ # Ensure deterministic FX for today's date
+ if not frappe.db.exists(
+ "Currency Exchange",
+ {"from_currency": "USD", "to_currency": company_currency, "date": today()},
+ ):
+ frappe.get_doc({
+ "doctype": "Currency Exchange",
+ "from_currency": "USD",
+ "to_currency": company_currency,
+ "exchange_rate": 86,
+ "date": today(),
+ }).insert()
supplier = create_supplier(supplier_name="_Test BO USD Supplier", default_currency="USD")
🤖 Prompt for AI Agents
|
||
|
||
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" | ||
bo.currency = args.currency or get_company_currency(bo.company) | ||
bo.conversion_rate = args.conversion_rate or 1.0 | ||
|
||
if args.blanket_order_type == "Selling": | ||
bo.customer = args.customer or "_Test Customer" | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -11,6 +11,7 @@ | |
"column_break_3", | ||
"qty", | ||
"rate", | ||
"base_rate", | ||
"ordered_qty", | ||
Comment on lines
+14
to
15
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 Verification agent ❓ Verification inconclusiveNew base_rate is read_only + reqd — likely to break saves/migrations. A newly required, read-only field can block saves on existing records unless a server patch backfills and server-side validate always sets it. Recommend making it non-required or ship a patch ensuring population for all existing rows and on validate. Apply if opting non-required: {
"fieldname": "base_rate",
"fieldtype": "Currency",
"label": "Base Rate (Company Currency)",
"options": "Company:company:default_currency",
"print_hide": 1,
"read_only": 1,
- "reqd": 1
+ "reqd": 0
} If keeping required, please add a data patch to compute base_rate = rate * parent.conversion_rate for all existing Blanket Orders and ensure server-side validate sets it when missing. Also applies to: 77-83 Ensure new base_rate field won’t block existing records
|
||
"section_break_7", | ||
"terms_and_conditions" | ||
|
@@ -46,6 +47,7 @@ | |
"fieldtype": "Currency", | ||
"in_list_view": 1, | ||
"label": "Rate", | ||
"options": "currency", | ||
"reqd": 1 | ||
}, | ||
{ | ||
|
@@ -70,19 +72,29 @@ | |
"fieldtype": "Data", | ||
"label": "Party Item Code", | ||
"read_only": 1 | ||
}, | ||
{ | ||
"fieldname": "base_rate", | ||
"fieldtype": "Currency", | ||
"label": "Base Rate (Company Currency)", | ||
"options": "Company:company:default_currency", | ||
"print_hide": 1, | ||
"read_only": 1, | ||
"reqd": 1 | ||
} | ||
], | ||
"istable": 1, | ||
"links": [], | ||
"modified": "2024-03-27 13:06:40.083042", | ||
"modified": "2025-08-27 17:21:32.870482", | ||
"modified_by": "Administrator", | ||
"module": "Manufacturing", | ||
"name": "Blanket Order Item", | ||
"owner": "Administrator", | ||
"permissions": [], | ||
"quick_entry": 1, | ||
"row_format": "Dynamic", | ||
"sort_field": "creation", | ||
"sort_order": "DESC", | ||
"states": [], | ||
"track_changes": 1 | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Required new fields need migration/backfill and a server-side default path
currency
andconversion_rate
will block edits/amendments of existing Blanket Orders unless you backfill. Provide a patch to setcurrency = company.default_currency
andconversion_rate = 1.0
for existing rows.currency
.Apply this diff to auto-fetch the company currency:
{ "fieldname": "currency", "fieldtype": "Link", "label": "Currency", "options": "Currency", + "fetch_from": "company.default_currency", "print_hide": 1, "reqd": 1 }
📝 Committable suggestion