From c0ae1336f43cbaed5a1a226279a70252ae6500a7 Mon Sep 17 00:00:00 2001
From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com>
Date: Wed, 23 Apr 2025 20:26:03 +0200
Subject: [PATCH 01/41] fix(Rename Tool): allow more than 500 rows (backport
#47117) (#47225)
Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com>
fix(Rename Tool): allow more than 500 rows (#47117)
---
.../doctype/rename_tool/rename_tool.js | 67 +++++++++++++++----
.../doctype/rename_tool/rename_tool.py | 9 ++-
2 files changed, 62 insertions(+), 14 deletions(-)
diff --git a/erpnext/utilities/doctype/rename_tool/rename_tool.js b/erpnext/utilities/doctype/rename_tool/rename_tool.js
index 1b8b2be2610..47677a62500 100644
--- a/erpnext/utilities/doctype/rename_tool/rename_tool.js
+++ b/erpnext/utilities/doctype/rename_tool/rename_tool.js
@@ -18,29 +18,70 @@ frappe.ui.form.on("Rename Tool", {
allowed_file_types: [".csv"],
},
};
- if (!frm.doc.file_to_rename) {
- frm.get_field("rename_log").$wrapper.html("");
- }
+
+ frm.trigger("render_overview");
+
frm.page.set_primary_action(__("Rename"), function () {
- frm.get_field("rename_log").$wrapper.html("
Renaming...
");
frappe.call({
method: "erpnext.utilities.doctype.rename_tool.rename_tool.upload",
args: {
select_doctype: frm.doc.select_doctype,
},
- callback: function (r) {
- let html = r.message.join("
");
+ freeze: true,
+ freeze_message: __("Scheduling..."),
+ callback: function () {
+ frappe.msgprint({
+ message: __("Rename jobs for doctype {0} have been enqueued.", [
+ frm.doc.select_doctype,
+ ]),
+ alert: true,
+ indicator: "green",
+ });
+ frm.set_value("select_doctype", "");
+ frm.set_value("file_to_rename", "");
- if (r.exc) {
- r.exc = frappe.utils.parse_json(r.exc);
- if (Array.isArray(r.exc)) {
- html += "
" + r.exc.join("
");
- }
- }
+ frm.trigger("render_overview");
+ },
+ error: function (r) {
+ frappe.msgprint({
+ message: __("Rename jobs for doctype {0} have not been enqueued.", [
+ frm.doc.select_doctype,
+ ]),
+ alert: true,
+ indicator: "red",
+ });
- frm.get_field("rename_log").$wrapper.html(html);
+ frm.trigger("render_overview");
},
});
});
},
+ render_overview: function (frm) {
+ frappe.db
+ .get_list("RQ Job", { filters: { status: ["in", ["started", "queued", "finished", "failed"]] } })
+ .then((jobs) => {
+ let counts = {
+ started: 0,
+ queued: 0,
+ finished: 0,
+ failed: 0,
+ };
+
+ for (const job of jobs) {
+ if (job.job_name !== "frappe.model.rename_doc.bulk_rename") {
+ continue;
+ }
+
+ counts[job.status]++;
+ }
+
+ frm.get_field("rename_log").$wrapper.html(`
+ ${__("Bulk Rename Jobs")}
+ ${__("Queued")}: ${counts.queued}
+ ${__("Started")}: ${counts.started}
+ ${__("Finished")}: ${counts.finished}
+ ${__("Failed")}: ${counts.failed}
+ `);
+ });
+ },
});
diff --git a/erpnext/utilities/doctype/rename_tool/rename_tool.py b/erpnext/utilities/doctype/rename_tool/rename_tool.py
index 19b29f79aa1..230845e55de 100644
--- a/erpnext/utilities/doctype/rename_tool/rename_tool.py
+++ b/erpnext/utilities/doctype/rename_tool/rename_tool.py
@@ -45,4 +45,11 @@ def upload(select_doctype=None, rows=None):
rows = read_csv_content_from_attached_file(frappe.get_doc("Rename Tool", "Rename Tool"))
- return bulk_rename(select_doctype, rows=rows)
+ # bulk rename allows only 500 rows at a time, so we created one job per 500 rows
+ for i in range(0, len(rows), 500):
+ frappe.enqueue(
+ method=bulk_rename,
+ queue="long",
+ doctype=select_doctype,
+ rows=rows[i : i + 500],
+ )
From c140fd0f12bafd857f17d00972031667b5be2aec Mon Sep 17 00:00:00 2001
From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com>
Date: Thu, 24 Apr 2025 11:36:18 +0530
Subject: [PATCH 02/41] fix: make asset quantity and amount editable (backport
#47226) (#47227)
fix: make asset quantity and amount editable (#47226)
(cherry picked from commit 0d53e6ed7ccc5a44b0a4068237637f033632b6c5)
Co-authored-by: Khushi Rawat <142375893+khushi8112@users.noreply.github.com>
---
erpnext/assets/doctype/asset/asset.js | 4 ----
erpnext/assets/doctype/asset/asset.py | 2 --
2 files changed, 6 deletions(-)
diff --git a/erpnext/assets/doctype/asset/asset.js b/erpnext/assets/doctype/asset/asset.js
index b9ea888faf7..3ae91f0593b 100644
--- a/erpnext/assets/doctype/asset/asset.js
+++ b/erpnext/assets/doctype/asset/asset.js
@@ -658,10 +658,6 @@ frappe.ui.form.on("Asset", {
} else {
frm.set_value("purchase_invoice_item", data.purchase_invoice_item);
}
-
- let is_editable = !data.is_multiple_items; // if multiple items, then fields should be read-only
- frm.set_df_property("gross_purchase_amount", "read_only", is_editable);
- frm.set_df_property("asset_quantity", "read_only", is_editable);
}
},
});
diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py
index 6886a79bb0a..e1a5398db85 100644
--- a/erpnext/assets/doctype/asset/asset.py
+++ b/erpnext/assets/doctype/asset/asset.py
@@ -1169,7 +1169,6 @@ def get_values_from_purchase_doc(purchase_doc_name, item_code, doctype):
frappe.throw(_(f"Selected {doctype} does not contain the Item Code {item_code}"))
first_item = matching_items[0]
- is_multiple_items = len(matching_items) > 1
return {
"company": purchase_doc.company,
@@ -1178,7 +1177,6 @@ def get_values_from_purchase_doc(purchase_doc_name, item_code, doctype):
"asset_quantity": first_item.qty,
"cost_center": first_item.cost_center or purchase_doc.get("cost_center"),
"asset_location": first_item.get("asset_location"),
- "is_multiple_items": is_multiple_items,
"purchase_receipt_item": first_item.name if doctype == "Purchase Receipt" else None,
"purchase_invoice_item": first_item.name if doctype == "Purchase Invoice" else None,
}
From 4a29a54804df8355c2ec277bb61cb6bf000923e3 Mon Sep 17 00:00:00 2001
From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com>
Date: Thu, 24 Apr 2025 16:36:53 +0530
Subject: [PATCH 03/41] fix: update additional cost and total asset cost after
asset repair (backport #47233) (#47235)
fix: update additional cost and total asset cost after asset repair (#47233)
* fix: add consumed stock's cost to the asset value after repair
* fix: do not copy additional cost and total asset cost
(cherry picked from commit ed8a8532e169b669d55380a3282b992b6117d7f8)
Co-authored-by: Khushi Rawat <142375893+khushi8112@users.noreply.github.com>
---
erpnext/assets/doctype/asset/asset.json | 4 +++-
erpnext/assets/doctype/asset_repair/asset_repair.py | 12 ++++++++----
2 files changed, 11 insertions(+), 5 deletions(-)
diff --git a/erpnext/assets/doctype/asset/asset.json b/erpnext/assets/doctype/asset/asset.json
index cf3602ef966..83873f670ad 100644
--- a/erpnext/assets/doctype/asset/asset.json
+++ b/erpnext/assets/doctype/asset/asset.json
@@ -512,6 +512,7 @@
"fieldname": "total_asset_cost",
"fieldtype": "Currency",
"label": "Total Asset Cost",
+ "no_copy": 1,
"options": "Company:company:default_currency",
"read_only": 1
},
@@ -520,6 +521,7 @@
"fieldname": "additional_asset_cost",
"fieldtype": "Currency",
"label": "Additional Asset Cost",
+ "no_copy": 1,
"options": "Company:company:default_currency",
"read_only": 1
},
@@ -593,7 +595,7 @@
"link_fieldname": "target_asset"
}
],
- "modified": "2025-04-15 16:33:17.189524",
+ "modified": "2025-04-24 15:31:47.373274",
"modified_by": "Administrator",
"module": "Assets",
"name": "Asset",
diff --git a/erpnext/assets/doctype/asset_repair/asset_repair.py b/erpnext/assets/doctype/asset_repair/asset_repair.py
index 4e73148828d..3938ae06b50 100644
--- a/erpnext/assets/doctype/asset_repair/asset_repair.py
+++ b/erpnext/assets/doctype/asset_repair/asset_repair.py
@@ -98,9 +98,11 @@ class AssetRepair(AccountsController):
self.increase_asset_value()
+ total_repair_cost = self.get_total_value_of_stock_consumed()
if self.capitalize_repair_cost:
- self.asset_doc.total_asset_cost += self.repair_cost
- self.asset_doc.additional_asset_cost += self.repair_cost
+ total_repair_cost += self.repair_cost
+ self.asset_doc.total_asset_cost += total_repair_cost
+ self.asset_doc.additional_asset_cost += total_repair_cost
if self.get("stock_consumption"):
self.check_for_stock_items_and_warehouse()
@@ -139,9 +141,11 @@ class AssetRepair(AccountsController):
self.decrease_asset_value()
+ total_repair_cost = self.get_total_value_of_stock_consumed()
if self.capitalize_repair_cost:
- self.asset_doc.total_asset_cost -= self.repair_cost
- self.asset_doc.additional_asset_cost -= self.repair_cost
+ total_repair_cost += self.repair_cost
+ self.asset_doc.total_asset_cost -= total_repair_cost
+ self.asset_doc.additional_asset_cost -= total_repair_cost
if self.get("capitalize_repair_cost"):
self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry")
From 0caba9f70dbefc7aa2114dcd69959a6cf1ea7cb5 Mon Sep 17 00:00:00 2001
From: Lakshit Jain <108322669+ljain112@users.noreply.github.com>
Date: Thu, 24 Apr 2025 20:33:30 +0530
Subject: [PATCH 04/41] fix: do not check for permission if values are not
changed in employee doctype (#47238)
---
erpnext/setup/doctype/employee/employee.py | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/erpnext/setup/doctype/employee/employee.py b/erpnext/setup/doctype/employee/employee.py
index 41bc41d5d34..eb5284019da 100755
--- a/erpnext/setup/doctype/employee/employee.py
+++ b/erpnext/setup/doctype/employee/employee.py
@@ -85,9 +85,10 @@ class Employee(NestedSet):
self.reset_employee_emails_cache()
def update_user_permissions(self):
- if not has_permission("User Permission", ptype="write") or (
- not self.has_value_changed("user_id") and not self.has_value_changed("create_user_permission")
- ):
+ if not self.has_value_changed("user_id") and not self.has_value_changed("create_user_permission"):
+ return
+
+ if not has_permission("User Permission", ptype="write", raise_exception=False):
return
employee_user_permission_exists = frappe.db.exists(
From 10d843e49061e21ae9460c981fe5938545be1b5c Mon Sep 17 00:00:00 2001
From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com>
Date: Thu, 24 Apr 2025 23:23:08 +0530
Subject: [PATCH 05/41] fix: cancel pos closing entry failure for return pos
invoices (backport #47248) (#47249)
fix: cancel pos closing entry failure for return pos invoices (#47248)
(cherry picked from commit c8ee5d9a4e1e29d595e8bfe02096d930937b963a)
Co-authored-by: Diptanil Saha
---
.../doctype/pos_invoice_merge_log/pos_invoice_merge_log.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py b/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py
index a8eba648545..96b3be79664 100644
--- a/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py
+++ b/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py
@@ -336,7 +336,7 @@ class POSInvoiceMergeLog(Document):
for doc in invoice_docs:
doc.load_from_db()
inv = sales_invoice
- if doc.is_return:
+ if doc.is_return and credit_notes:
for key, value in credit_notes.items():
if doc.name in value:
inv = key
From 4bcea5556337e9dc5ccaa4d8fc55bb7e54fe1d81 Mon Sep 17 00:00:00 2001
From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com>
Date: Fri, 25 Apr 2025 11:33:04 +0530
Subject: [PATCH 06/41] fix: prohibit consolidated sales invoice return
(backport #47251) (#47252)
fix: prohibit consolidated sales invoice return (#47251)
(cherry picked from commit 483c4a327169b58464c22038a308c35637cb0ed5)
Co-authored-by: Diptanil Saha
---
erpnext/controllers/sales_and_purchase_return.py | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/erpnext/controllers/sales_and_purchase_return.py b/erpnext/controllers/sales_and_purchase_return.py
index 74c5e34ecfa..d5aa7823751 100644
--- a/erpnext/controllers/sales_and_purchase_return.py
+++ b/erpnext/controllers/sales_and_purchase_return.py
@@ -347,6 +347,16 @@ def make_return_doc(doctype: str, source_name: str, target_doc=None, return_agai
"Company", company, "default_warehouse_for_sales_return"
)
+ if doctype == "Sales Invoice":
+ inv_is_consolidated, inv_is_pos = frappe.db.get_value(
+ "Sales Invoice", source_name, ["is_consolidated", "is_pos"]
+ )
+ if inv_is_consolidated and inv_is_pos:
+ frappe.throw(
+ _("Cannot create return for consolidated invoice {0}.").format(source_name),
+ title=_("Cannot Create Return"),
+ )
+
def set_missing_values(source, target):
doc = frappe.get_doc(target)
doc.is_return = 1
From 727c32d7898d2d5a48780d474d6a6ecaaa456221 Mon Sep 17 00:00:00 2001
From: marination <25857446+marination@users.noreply.github.com>
Date: Thu, 24 Apr 2025 09:50:43 +0200
Subject: [PATCH 07/41] fix: Re-insert missing "Serial No Warranty Expiry"
Report
(cherry picked from commit deefac0abfe295b59e55203982bb0ebf3fb3c83e)
---
.../serial_no_warranty_expiry.json | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/erpnext/stock/report/serial_no_warranty_expiry/serial_no_warranty_expiry.json b/erpnext/stock/report/serial_no_warranty_expiry/serial_no_warranty_expiry.json
index 75e2fac98fd..2f6acad6557 100644
--- a/erpnext/stock/report/serial_no_warranty_expiry/serial_no_warranty_expiry.json
+++ b/erpnext/stock/report/serial_no_warranty_expiry/serial_no_warranty_expiry.json
@@ -10,14 +10,14 @@
"is_standard": "Yes",
"json": "{\"add_total_row\": 0, \"sort_by\": \"Serial No.modified\", \"sort_order\": \"desc\", \"sort_by_next\": null, \"filters\": [[\"Serial No\", \"warehouse\", \"=\", \"\"]], \"sort_order_next\": \"desc\", \"columns\": [[\"name\", \"Serial No\"], [\"item_code\", \"Serial No\"], [\"amc_expiry_date\", \"Serial No\"], [\"maintenance_status\", \"Serial No\"],[\"item_name\", \"Serial No\"], [\"description\", \"Serial No\"], [\"item_group\", \"Serial No\"], [\"brand\", \"Serial No\"]]}",
"letterhead": null,
- "modified": "2024-09-26 13:07:23.451182",
+ "modified": "2025-04-24 13:07:23.451182",
"modified_by": "Administrator",
"module": "Stock",
- "name": "Serial No Service Contract Expiry",
+ "name": "Serial No Warranty Expiry",
"owner": "Administrator",
"prepared_report": 0,
"ref_doctype": "Serial No",
- "report_name": "Serial No Service Contract Expiry",
+ "report_name": "Serial No Warranty Expiry",
"report_type": "Report Builder",
"roles": [
{
From f8da1599bbb4ade1f628f64c9617d058e58f0205 Mon Sep 17 00:00:00 2001
From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com>
Date: Fri, 25 Apr 2025 17:59:15 +0530
Subject: [PATCH 08/41] fix: consolidating pos invoices on the basis of
accounting dimensions (backport #46961) (#47265)
fix: consolidating pos invoices on the basis of accounting dimensions (#46961)
* fix: consolidating pos invoices on the basis of accounting dimensions
* fix: project field
(cherry picked from commit c85edc3346dde165925aa32931bc448f4b89d697)
Co-authored-by: Diptanil Saha
---
.../pos_invoice_merge_log.py | 74 +++++++++++++++----
.../test_pos_invoice_merge_log.py | 55 ++++++++++++++
2 files changed, 113 insertions(+), 16 deletions(-)
diff --git a/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py b/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py
index 96b3be79664..8216a9e7259 100644
--- a/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py
+++ b/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py
@@ -2,6 +2,7 @@
# For license information, please see license.txt
+import hashlib
import json
import frappe
@@ -302,10 +303,17 @@ class POSInvoiceMergeLog(Document):
accounting_dimensions = get_checks_for_pl_and_bs_accounts()
accounting_dimensions_fields = [d.fieldname for d in accounting_dimensions]
dimension_values = frappe.db.get_value(
- "POS Profile", {"name": invoice.pos_profile}, accounting_dimensions_fields, as_dict=1
+ "POS Profile",
+ {"name": invoice.pos_profile},
+ [*accounting_dimensions_fields, "cost_center", "project"],
+ as_dict=1,
)
for dimension in accounting_dimensions:
- dimension_value = dimension_values.get(dimension.fieldname)
+ dimension_value = (
+ data[0].get(dimension.fieldname)
+ if data[0].get(dimension.fieldname)
+ else dimension_values.get(dimension.fieldname)
+ )
if not dimension_value and (dimension.mandatory_for_pl or dimension.mandatory_for_bs):
frappe.throw(
@@ -317,6 +325,14 @@ class POSInvoiceMergeLog(Document):
invoice.set(dimension.fieldname, dimension_value)
+ invoice.set(
+ "cost_center",
+ data[0].get("cost_center") if data[0].get("cost_center") else dimension_values.get("cost_center"),
+ )
+ invoice.set(
+ "project", data[0].get("project") if data[0].get("project") else dimension_values.get("project")
+ )
+
if self.merge_invoices_based_on == "Customer Group":
invoice.flags.ignore_pos_profile = True
invoice.pos_profile = ""
@@ -446,9 +462,34 @@ def get_invoice_customer_map(pos_invoices):
pos_invoice_customer_map.setdefault(customer, [])
pos_invoice_customer_map[customer].append(invoice)
+ for customer, invoices in pos_invoice_customer_map.items():
+ pos_invoice_customer_map[customer] = split_invoices_by_accounting_dimension(invoices)
+
return pos_invoice_customer_map
+def split_invoices_by_accounting_dimension(pos_invoices):
+ # pos_invoices = {
+ # {'dim_field1': 'dim_field1_value1', 'dim_field2': 'dim_field2_value1'}: [],
+ # {'dim_field1': 'dim_field1_value2', 'dim_field2': 'dim_field2_value1'}: []
+ # }
+ pos_invoice_accounting_dimensions_map = {}
+ for invoice in pos_invoices:
+ dimension_fields = [d.fieldname for d in get_checks_for_pl_and_bs_accounts()]
+ accounting_dimensions = frappe.db.get_value(
+ "POS Invoice", invoice.pos_invoice, [*dimension_fields, "cost_center", "project"], as_dict=1
+ )
+
+ accounting_dimensions_dic_hash = hashlib.sha256(
+ json.dumps(accounting_dimensions).encode()
+ ).hexdigest()
+
+ pos_invoice_accounting_dimensions_map.setdefault(accounting_dimensions_dic_hash, [])
+ pos_invoice_accounting_dimensions_map[accounting_dimensions_dic_hash].append(invoice)
+
+ return pos_invoice_accounting_dimensions_map
+
+
def consolidate_pos_invoices(pos_invoices=None, closing_entry=None):
invoices = pos_invoices or (closing_entry and closing_entry.get("pos_transactions"))
if frappe.flags.in_test and not invoices:
@@ -532,20 +573,21 @@ def split_invoices(invoices):
def create_merge_logs(invoice_by_customer, closing_entry=None):
try:
- for customer, invoices in invoice_by_customer.items():
- for _invoices in split_invoices(invoices):
- merge_log = frappe.new_doc("POS Invoice Merge Log")
- merge_log.posting_date = (
- getdate(closing_entry.get("posting_date")) if closing_entry else nowdate()
- )
- merge_log.posting_time = (
- get_time(closing_entry.get("posting_time")) if closing_entry else nowtime()
- )
- merge_log.customer = customer
- merge_log.pos_closing_entry = closing_entry.get("name") if closing_entry else None
- merge_log.set("pos_invoices", _invoices)
- merge_log.save(ignore_permissions=True)
- merge_log.submit()
+ for customer, invoices_acc_dim in invoice_by_customer.items():
+ for invoices in invoices_acc_dim.values():
+ for _invoices in split_invoices(invoices):
+ merge_log = frappe.new_doc("POS Invoice Merge Log")
+ merge_log.posting_date = (
+ getdate(closing_entry.get("posting_date")) if closing_entry else nowdate()
+ )
+ merge_log.posting_time = (
+ get_time(closing_entry.get("posting_time")) if closing_entry else nowtime()
+ )
+ merge_log.customer = customer
+ merge_log.pos_closing_entry = closing_entry.get("name") if closing_entry else None
+ merge_log.set("pos_invoices", _invoices)
+ merge_log.save(ignore_permissions=True)
+ merge_log.submit()
if closing_entry:
closing_entry.set_status(update=True, status="Submitted")
closing_entry.db_set("error_message", "")
diff --git a/erpnext/accounts/doctype/pos_invoice_merge_log/test_pos_invoice_merge_log.py b/erpnext/accounts/doctype/pos_invoice_merge_log/test_pos_invoice_merge_log.py
index e0d37436be5..be7206fc9bd 100644
--- a/erpnext/accounts/doctype/pos_invoice_merge_log/test_pos_invoice_merge_log.py
+++ b/erpnext/accounts/doctype/pos_invoice_merge_log/test_pos_invoice_merge_log.py
@@ -455,3 +455,58 @@ class TestPOSInvoiceMergeLog(unittest.TestCase):
frappe.set_user("Administrator")
frappe.db.sql("delete from `tabPOS Profile`")
frappe.db.sql("delete from `tabPOS Invoice`")
+
+ def test_separate_consolidated_invoice_for_different_accounting_dimensions(self):
+ """
+ Creating 3 POS Invoices where first POS Invoice has different Cost Center than the other two.
+ Consolidate the Invoices.
+ Check whether the first POS Invoice is consolidated with a separate Sales Invoice than the other two.
+ Check whether the second and third POS Invoice are consolidated with the same Sales Invoice.
+ """
+ from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center
+
+ frappe.db.sql("delete from `tabPOS Invoice`")
+
+ create_cost_center(cost_center_name="_Test POS Cost Center 1", is_group=0)
+ create_cost_center(cost_center_name="_Test POS Cost Center 2", is_group=0)
+
+ try:
+ test_user, pos_profile = init_user_and_profile()
+
+ pos_inv = create_pos_invoice(rate=300, do_not_submit=1)
+ pos_inv.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 300})
+ pos_inv.cost_center = "_Test POS Cost Center 1 - _TC"
+ pos_inv.save()
+ pos_inv.submit()
+
+ pos_inv2 = create_pos_invoice(rate=3200, do_not_submit=1)
+ pos_inv2.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 3200})
+ pos_inv.cost_center = "_Test POS Cost Center 2 - _TC"
+ pos_inv2.save()
+ pos_inv2.submit()
+
+ pos_inv3 = create_pos_invoice(rate=2300, do_not_submit=1)
+ pos_inv3.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 2300})
+ pos_inv.cost_center = "_Test POS Cost Center 2 - _TC"
+ pos_inv3.save()
+ pos_inv3.submit()
+
+ consolidate_pos_invoices()
+
+ pos_inv.load_from_db()
+ self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv.consolidated_invoice))
+
+ pos_inv2.load_from_db()
+ self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv2.consolidated_invoice))
+
+ self.assertFalse(pos_inv.consolidated_invoice == pos_inv3.consolidated_invoice)
+
+ pos_inv3.load_from_db()
+ self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv3.consolidated_invoice))
+
+ self.assertTrue(pos_inv2.consolidated_invoice == pos_inv3.consolidated_invoice)
+
+ finally:
+ frappe.set_user("Administrator")
+ frappe.db.sql("delete from `tabPOS Profile`")
+ frappe.db.sql("delete from `tabPOS Invoice`")
From b2294ed6e3380f9f43530f7fb11674cc20a45793 Mon Sep 17 00:00:00 2001
From: Rohit Waghchaure
Date: Sat, 26 Apr 2025 16:31:37 +0530
Subject: [PATCH 09/41] fix: allow to change valuation method from FIFO to
Moving Average
(cherry picked from commit b454ed4b8f7f4c549a6590ad93b4842bc3a5bda7)
---
erpnext/stock/doctype/item/item.py | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py
index 003e0d4d3a0..c07ea6cdac1 100644
--- a/erpnext/stock/doctype/item/item.py
+++ b/erpnext/stock/doctype/item/item.py
@@ -970,6 +970,11 @@ class Item(Document):
changed_fields = [
field for field in restricted_fields if cstr(self.get(field)) != cstr(values.get(field))
]
+
+ # Allow to change valuation method from FIFO to Moving Average not vice versa
+ if self.valuation_method == "Moving Average" and "valuation_method" in changed_fields:
+ changed_fields.remove("valuation_method")
+
if not changed_fields:
return
From 925cc40eface504ff2885343d2376de13c3af05a Mon Sep 17 00:00:00 2001
From: Rohit Waghchaure
Date: Fri, 25 Apr 2025 14:45:01 +0530
Subject: [PATCH 10/41] fix: enable use serial / batch fields on batch
selection
(cherry picked from commit a4471865a9485cd9aab33abca8f86354741c474f)
---
erpnext/public/js/controllers/transaction.js | 18 ++++++++++--------
.../stock/doctype/stock_entry/stock_entry.js | 16 ++++++++++++++++
.../stock_reconciliation.js | 17 +++++++++++++++--
3 files changed, 41 insertions(+), 10 deletions(-)
diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js
index 59dd337d3af..1fcdd459a3f 100644
--- a/erpnext/public/js/controllers/transaction.js
+++ b/erpnext/public/js/controllers/transaction.js
@@ -792,6 +792,10 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
return;
}
+ if (item.serial_no) {
+ item.use_serial_batch_fields = 1
+ }
+
if (item && item.serial_no) {
if (!item.item_code) {
this.frm.trigger("item_code", cdt, cdn);
@@ -1355,13 +1359,6 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
}
}
- batch_no(doc, cdt, cdn) {
- let item = frappe.get_doc(cdt, cdn);
- if (!this.is_a_mapped_document(item)) {
- this.apply_price_list(item, true);
- }
- }
-
toggle_conversion_factor(item) {
// toggle read only property for conversion factor field if the uom and stock uom are same
if(this.frm.get_field('items').grid.fields_map.conversion_factor) {
@@ -1587,7 +1584,12 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
batch_no(frm, cdt, cdn) {
let row = locals[cdt][cdn];
- if (row.use_serial_batch_fields && row.batch_no) {
+
+ if (row.batch_no) {
+ row.use_serial_batch_fields = 1
+ }
+
+ if (row.batch_no) {
var params = this._get_args(row);
params.batch_no = row.batch_no;
params.uom = row.uom;
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js
index 0c619b22a33..223789fc8a3 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.js
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.js
@@ -950,6 +950,15 @@ frappe.ui.form.on("Stock Entry Detail", {
},
batch_no(frm, cdt, cdn) {
+ let row = locals[cdt][cdn];
+
+ if (row.batch_no) {
+ frappe.model.set_value(cdt, cdn, {
+ use_serial_batch_fields: 1,
+ serial_and_batch_bundle: "",
+ });
+ }
+
validate_sample_quantity(frm, cdt, cdn);
},
@@ -1074,6 +1083,13 @@ erpnext.stock.StockEntry = class StockEntry extends erpnext.stock.StockControlle
serial_no(doc, cdt, cdn) {
var item = frappe.get_doc(cdt, cdn);
+ if (item.serial_no) {
+ frappe.model.set_value(cdt, cdn, {
+ use_serial_batch_fields: 1,
+ serial_and_batch_bundle: "",
+ });
+ }
+
if (item?.serial_no) {
// Replace all occurences of comma with line feed
item.serial_no = item.serial_no.replace(/,/g, "\n");
diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js
index 9307eee46f1..44dd2952409 100644
--- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js
+++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js
@@ -289,8 +289,16 @@ frappe.ui.form.on("Stock Reconciliation Item", {
frm.events.set_valuation_rate_and_qty(frm, cdt, cdn);
},
- batch_no: function (frm, cdt, cdn) {
- frm.events.set_valuation_rate_and_qty(frm, cdt, cdn);
+ batch_no(frm, cdt, cdn) {
+ let row = locals[cdt][cdn];
+ if (row.batch_no) {
+ frappe.model.set_value(cdt, cdn, {
+ use_serial_batch_fields: 1,
+ serial_and_batch_bundle: "",
+ });
+
+ frm.events.set_valuation_rate_and_qty(frm, cdt, cdn);
+ }
},
qty: function (frm, cdt, cdn) {
@@ -310,6 +318,11 @@ frappe.ui.form.on("Stock Reconciliation Item", {
var child = locals[cdt][cdn];
if (child.serial_no) {
+ frappe.model.set_value(cdt, cdn, {
+ use_serial_batch_fields: 1,
+ serial_and_batch_bundle: "",
+ });
+
const serial_nos = child.serial_no.trim().split("\n");
frappe.model.set_value(cdt, cdn, "qty", serial_nos.length);
}
From 3e733f6ba1029b4b693d4a3bbfd3ca07d65c8128 Mon Sep 17 00:00:00 2001
From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com>
Date: Sun, 27 Apr 2025 15:24:59 +0200
Subject: [PATCH 11/41] fix(PE): Set account types in get_payment_entry
(backport #47246) (#47266)
Co-authored-by: Corentin Forler <10946971+cogk@users.noreply.github.com>
fix(PE): Set account types in get_payment_entry (#47246)
---
erpnext/accounts/doctype/payment_entry/payment_entry.py | 2 ++
1 file changed, 2 insertions(+)
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py
index 00c9a337499..1f9747a77cb 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.py
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py
@@ -2941,6 +2941,8 @@ def get_payment_entry(
party_account_currency if payment_type == "Receive" else bank.account_currency
)
pe.paid_to_account_currency = party_account_currency if payment_type == "Pay" else bank.account_currency
+ pe.paid_from_account_type = frappe.db.get_value("Account", pe.paid_from, "account_type")
+ pe.paid_to_account_type = frappe.db.get_value("Account", pe.paid_to, "account_type")
pe.paid_amount = paid_amount
pe.received_amount = received_amount
pe.letter_head = doc.get("letter_head")
From 5f101e763565f4146a44103ba94f56f670d69111 Mon Sep 17 00:00:00 2001
From: Karm Soni
Date: Wed, 9 Apr 2025 19:19:47 +0530
Subject: [PATCH 12/41] feat: add dispatch address fields to purchase doctypes
(cherry picked from commit 54b5205221fe5447b01938abda5fc25b90892994)
# Conflicts:
# erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
# erpnext/buying/doctype/purchase_order/purchase_order.json
---
.../purchase_invoice/purchase_invoice.json | 31 ++++++++++++++-
.../purchase_invoice/purchase_invoice.py | 2 +
.../purchase_order/purchase_order.json | 39 ++++++++++++++++++-
.../doctype/purchase_order/purchase_order.py | 2 +
.../purchase_receipt/purchase_receipt.json | 22 ++++++++++-
.../purchase_receipt/purchase_receipt.py | 2 +
6 files changed, 93 insertions(+), 5 deletions(-)
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
index 0584b6026a7..b7e110e6a69 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
@@ -144,8 +144,10 @@
"contact_email",
"company_shipping_address_section",
"shipping_address",
- "column_break_126",
"shipping_address_display",
+ "column_break_126",
+ "dispatch_address",
+ "dispatch_address_display",
"company_billing_address_section",
"billing_address",
"column_break_130",
@@ -1627,13 +1629,37 @@
"fieldname": "update_outstanding_for_self",
"fieldtype": "Check",
"label": "Update Outstanding for Self"
+<<<<<<< HEAD
+=======
+ },
+ {
+ "fieldname": "sender",
+ "fieldtype": "Data",
+ "label": "Sender",
+ "options": "Email"
+ },
+ {
+ "fieldname": "dispatch_address_display",
+ "fieldtype": "Text Editor",
+ "label": "Dispatch Address",
+ "print_hide": 1,
+ "read_only": 1
+ },
+ {
+ "fieldname": "dispatch_address",
+ "fieldtype": "Link",
+ "label": "Select Dispatch Address ",
+ "options": "Address",
+ "print_hide": 1
+>>>>>>> 54b5205221 (feat: add dispatch address fields to purchase doctypes)
}
],
+ "grid_page_length": 50,
"icon": "fa fa-file-text",
"idx": 204,
"is_submittable": 1,
"links": [],
- "modified": "2025-01-14 11:39:04.564610",
+ "modified": "2025-04-09 16:49:22.175081",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Purchase Invoice",
@@ -1688,6 +1714,7 @@
"write": 1
}
],
+ "row_format": "Dynamic",
"search_fields": "posting_date, supplier, bill_no, base_grand_total, outstanding_amount",
"show_name_in_global_search": 1,
"sort_field": "modified",
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
index 98d1850bd8f..805e76ad64c 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
@@ -117,6 +117,8 @@ class PurchaseInvoice(BuyingController):
currency: DF.Link | None
disable_rounded_total: DF.Check
discount_amount: DF.Currency
+ dispatch_address: DF.Link | None
+ dispatch_address_display: DF.TextEditor | None
due_date: DF.Date | None
from_date: DF.Date | None
grand_total: DF.Currency
diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.json b/erpnext/buying/doctype/purchase_order/purchase_order.json
index 8f4b035361a..3eda1789140 100644
--- a/erpnext/buying/doctype/purchase_order/purchase_order.json
+++ b/erpnext/buying/doctype/purchase_order/purchase_order.json
@@ -110,8 +110,10 @@
"contact_email",
"shipping_address_section",
"shipping_address",
- "column_break_99",
"shipping_address_display",
+ "column_break_99",
+ "dispatch_address",
+ "dispatch_address_display",
"company_billing_address_section",
"billing_address",
"column_break_103",
@@ -1269,13 +1271,47 @@
"fieldtype": "Tab Break",
"label": "Connections",
"show_dashboard": 1
+<<<<<<< HEAD
+=======
+ },
+ {
+ "fieldname": "advance_payment_status",
+ "fieldtype": "Select",
+ "hidden": 1,
+ "in_standard_filter": 1,
+ "label": "Advance Payment Status",
+ "no_copy": 1,
+ "oldfieldname": "status",
+ "oldfieldtype": "Select",
+ "options": "Not Initiated\nInitiated\nPartially Paid\nFully Paid",
+ "print_hide": 1
+ },
+ {
+ "fieldname": "dispatch_address",
+ "fieldtype": "Link",
+ "label": "Dispatch Address",
+ "options": "Address",
+ "print_hide": 1
+ },
+ {
+ "fieldname": "dispatch_address_display",
+ "fieldtype": "Text Editor",
+ "label": "Dispatch Address Details",
+ "print_hide": 1,
+ "read_only": 1
+>>>>>>> 54b5205221 (feat: add dispatch address fields to purchase doctypes)
}
],
+ "grid_page_length": 50,
"icon": "fa fa-file-text",
"idx": 105,
"is_submittable": 1,
"links": [],
+<<<<<<< HEAD
"modified": "2024-03-20 16:03:31.611808",
+=======
+ "modified": "2025-04-09 16:54:08.836106",
+>>>>>>> 54b5205221 (feat: add dispatch address fields to purchase doctypes)
"modified_by": "Administrator",
"module": "Buying",
"name": "Purchase Order",
@@ -1322,6 +1358,7 @@
"write": 1
}
],
+ "row_format": "Dynamic",
"search_fields": "status, transaction_date, supplier, grand_total",
"show_name_in_global_search": 1,
"sort_field": "modified",
diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.py b/erpnext/buying/doctype/purchase_order/purchase_order.py
index ee8ba35222f..a18d9fce186 100644
--- a/erpnext/buying/doctype/purchase_order/purchase_order.py
+++ b/erpnext/buying/doctype/purchase_order/purchase_order.py
@@ -92,6 +92,8 @@ class PurchaseOrder(BuyingController):
customer_name: DF.Data | None
disable_rounded_total: DF.Check
discount_amount: DF.Currency
+ dispatch_address: DF.Link | None
+ dispatch_address_display: DF.TextEditor | None
from_date: DF.Date | None
grand_total: DF.Currency
group_same_items: DF.Check
diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json
index 643f9e7a82f..8fce3be270e 100755
--- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json
+++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json
@@ -113,8 +113,10 @@
"contact_email",
"section_break_98",
"shipping_address",
- "column_break_100",
"shipping_address_display",
+ "column_break_100",
+ "dispatch_address",
+ "dispatch_address_display",
"billing_address_section",
"billing_address",
"column_break_104",
@@ -1267,13 +1269,28 @@
"no_copy": 1,
"print_hide": 1,
"read_only": 1
+ },
+ {
+ "fieldname": "dispatch_address",
+ "fieldtype": "Link",
+ "label": "Dispatch Address Template",
+ "options": "Address",
+ "print_hide": 1
+ },
+ {
+ "fieldname": "dispatch_address_display",
+ "fieldtype": "Text Editor",
+ "label": "Dispatch Address",
+ "print_hide": 1,
+ "read_only": 1
}
],
+ "grid_page_length": 50,
"icon": "fa fa-truck",
"idx": 261,
"is_submittable": 1,
"links": [],
- "modified": "2024-11-13 16:55:14.129055",
+ "modified": "2025-04-09 16:52:19.323878",
"modified_by": "Administrator",
"module": "Stock",
"name": "Purchase Receipt",
@@ -1334,6 +1351,7 @@
"write": 1
}
],
+ "row_format": "Dynamic",
"search_fields": "status, posting_date, supplier",
"show_name_in_global_search": 1,
"sort_field": "modified",
diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
index 48e161980db..b7cf97589af 100644
--- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
+++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
@@ -69,6 +69,8 @@ class PurchaseReceipt(BuyingController):
currency: DF.Link
disable_rounded_total: DF.Check
discount_amount: DF.Currency
+ dispatch_address: DF.Link | None
+ dispatch_address_display: DF.TextEditor | None
grand_total: DF.Currency
group_same_items: DF.Check
ignore_pricing_rule: DF.Check
From 1fe1563daba678875c95ca26e193d94bd1c2039a Mon Sep 17 00:00:00 2001
From: Karm Soni
Date: Mon, 14 Apr 2025 16:41:31 +0530
Subject: [PATCH 13/41] feat: add dispatch address support in party details and
controllers
(cherry picked from commit 53d0b7be232a8af935dfbbee545cd86e9d5a290a)
---
erpnext/accounts/party.py | 38 ++++++++++++++++++++++++
erpnext/controllers/buying_controller.py | 2 ++
erpnext/public/js/controllers/buying.js | 12 ++++++++
erpnext/public/js/utils/party.js | 4 +++
4 files changed, 56 insertions(+)
diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py
index 5ca50b8d7c6..ca63abeb282 100644
--- a/erpnext/accounts/party.py
+++ b/erpnext/accounts/party.py
@@ -71,6 +71,7 @@ def get_party_details(
party_address=None,
company_address=None,
shipping_address=None,
+ dispatch_address=None,
pos_profile=None,
):
if not party:
@@ -92,6 +93,7 @@ def get_party_details(
party_address,
company_address,
shipping_address,
+ dispatch_address,
pos_profile,
)
@@ -111,6 +113,7 @@ def _get_party_details(
party_address=None,
company_address=None,
shipping_address=None,
+ dispatch_address=None,
pos_profile=None,
):
party_details = frappe._dict(
@@ -134,6 +137,7 @@ def _get_party_details(
party_address,
company_address,
shipping_address,
+ dispatch_address,
ignore_permissions=ignore_permissions,
)
set_contact_details(party_details, party, party_type)
@@ -191,6 +195,7 @@ def set_address_details(
party_address=None,
company_address=None,
shipping_address=None,
+ dispatch_address=None,
*,
ignore_permissions=False,
):
@@ -219,6 +224,21 @@ def set_address_details(
get_fetch_values(doctype, "shipping_address_name", party_details.shipping_address_name)
)
+ # dispatch address
+ elif party_type == "Supplier":
+ party_details.dispatch_address = dispatch_address or get_party_shipping_address(
+ party_type, party.name
+ )
+
+ party_details.dispatch_address_display = render_address(
+ party_details["dispatch_address"], check_permissions=not ignore_permissions
+ )
+
+ if doctype:
+ party_details.update(
+ get_fetch_values(doctype, "dispatch_address", party_details.dispatch_address)
+ )
+
if company_address:
party_details.company_address = company_address
else:
@@ -256,6 +276,24 @@ def set_address_details(
**get_fetch_values(doctype, "shipping_address", party_details.billing_address),
)
+ if doctype != "Supplier Quotation":
+ if dispatch_address:
+ party_details.update(
+ dispatch_address=dispatch_address,
+ dispatch_address_display=render_address(
+ dispatch_address, check_permissions=not ignore_permissions
+ ),
+ **get_fetch_values(doctype, "dispatch_address", dispatch_address),
+ )
+
+ # dispatch address - if not already set
+ if not party_details.dispatch_address:
+ party_details.update(
+ dispatch_address=party_details.supplier_address,
+ dispatch_address_display=party_details.address_display,
+ **get_fetch_values(doctype, "dispatch_address", party_details.supplier_address),
+ )
+
party_address, shipping_address = (
party_details.get(billing_address_field),
party_details.shipping_address_name,
diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py
index c2a36ac36d0..267b0ed5dd5 100644
--- a/erpnext/controllers/buying_controller.py
+++ b/erpnext/controllers/buying_controller.py
@@ -141,6 +141,7 @@ class BuyingController(SubcontractingController):
company=self.company,
party_address=self.get("supplier_address"),
shipping_address=self.get("shipping_address"),
+ dispatch_address=self.get("dispatch_address"),
company_address=self.get("billing_address"),
fetch_payment_terms_template=not self.get("ignore_default_payment_terms_template"),
ignore_permissions=self.flags.ignore_permissions,
@@ -238,6 +239,7 @@ class BuyingController(SubcontractingController):
address_dict = {
"supplier_address": "address_display",
"shipping_address": "shipping_address_display",
+ "dispatch_address": "dispatch_address_display",
"billing_address": "billing_address_display",
}
diff --git a/erpnext/public/js/controllers/buying.js b/erpnext/public/js/controllers/buying.js
index cbc59867e46..e0ed9f2b91e 100644
--- a/erpnext/public/js/controllers/buying.js
+++ b/erpnext/public/js/controllers/buying.js
@@ -54,6 +54,18 @@ erpnext.buying = {
return erpnext.queries.company_address_query(this.frm.doc)
});
}
+
+ if(this.frm.get_field('dispatch_address')) {
+ this.frm.set_query("dispatch_address", () => {
+ if(this.frm.doc.supplier) {
+ return {
+ query: 'frappe.contacts.doctype.address.address.address_query',
+ filters: { link_doctype: 'Supplier', link_name: this.frm.doc.supplier }
+ };
+ } else
+ return erpnext.queries.dispatch_address_query(this.frm.doc)
+ });
+ }
}
setup_queries(doc, cdt, cdn) {
diff --git a/erpnext/public/js/utils/party.js b/erpnext/public/js/utils/party.js
index a85423b8340..958defa32c7 100644
--- a/erpnext/public/js/utils/party.js
+++ b/erpnext/public/js/utils/party.js
@@ -71,6 +71,10 @@ erpnext.utils.get_party_details = function (frm, method, args, callback) {
if (!args.shipping_address && frm.doc.shipping_address) {
args.shipping_address = frm.doc.shipping_address;
}
+
+ if (!args.dispatch_address && frm.doc.dispatch_address) {
+ args.dispatch_address = frm.doc.dispatch_address;
+ }
}
if (frappe.meta.get_docfield(frm.doc.doctype, "taxes")) {
From 290f0b94e56cbe92c684fa50b19f69fdef2a8f95 Mon Sep 17 00:00:00 2001
From: Karm Soni
Date: Mon, 14 Apr 2025 18:06:44 +0530
Subject: [PATCH 14/41] fix: enhance dispatch address query logic and add
supplier address query
(cherry picked from commit 9a859e54b62d914a574b6e0005c8daebc1f21a86)
---
erpnext/public/js/controllers/buying.js | 12 +++++-------
erpnext/public/js/queries.js | 17 +++++++++++++++++
2 files changed, 22 insertions(+), 7 deletions(-)
diff --git a/erpnext/public/js/controllers/buying.js b/erpnext/public/js/controllers/buying.js
index e0ed9f2b91e..a39a4ddf13a 100644
--- a/erpnext/public/js/controllers/buying.js
+++ b/erpnext/public/js/controllers/buying.js
@@ -57,13 +57,11 @@ erpnext.buying = {
if(this.frm.get_field('dispatch_address')) {
this.frm.set_query("dispatch_address", () => {
- if(this.frm.doc.supplier) {
- return {
- query: 'frappe.contacts.doctype.address.address.address_query',
- filters: { link_doctype: 'Supplier', link_name: this.frm.doc.supplier }
- };
- } else
- return erpnext.queries.dispatch_address_query(this.frm.doc)
+ if(this.frm.doc.is_return){
+ return erpnext.queries.company_address_query(this.frm.doc);
+ }
+
+ return erpnext.queries.supplier_address_query(this.frm.doc);
});
}
}
diff --git a/erpnext/public/js/queries.js b/erpnext/public/js/queries.js
index 63651ec8759..cfc602acf00 100644
--- a/erpnext/public/js/queries.js
+++ b/erpnext/public/js/queries.js
@@ -112,6 +112,23 @@ $.extend(erpnext.queries, {
};
},
+ supplier_address_query: function (doc) {
+ if (!doc.supplier) {
+ cur_frm.scroll_to_field("supplier");
+ frappe.show_alert({
+ message: __("Please set {0} first.", [
+ __(frappe.meta.get_label(doc.doctype, "supplier", doc.name)),
+ ]),
+ indicator: "orange",
+ });
+ }
+
+ return {
+ query: "frappe.contacts.doctype.address.address.address_query",
+ filters: { link_doctype: "Supplier", link_name: doc.supplier },
+ };
+ },
+
dispatch_address_query: function (doc) {
var filters = { link_doctype: "Company", link_name: doc.company || "" };
var is_drop_ship = doc.items.some((item) => item.delivered_by_supplier);
From 5c300b893b49131f2d3fda27823619925fddea78 Mon Sep 17 00:00:00 2001
From: Karm Soni
Date: Mon, 14 Apr 2025 18:48:32 +0530
Subject: [PATCH 15/41] fix: remove use of cur_frm
(cherry picked from commit c4bd3123fb8e6822d267469ffe652e13381d2741)
---
erpnext/public/js/controllers/buying.js | 2 +-
erpnext/public/js/queries.js | 10 +++++-----
2 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/erpnext/public/js/controllers/buying.js b/erpnext/public/js/controllers/buying.js
index a39a4ddf13a..630c6609c7b 100644
--- a/erpnext/public/js/controllers/buying.js
+++ b/erpnext/public/js/controllers/buying.js
@@ -61,7 +61,7 @@ erpnext.buying = {
return erpnext.queries.company_address_query(this.frm.doc);
}
- return erpnext.queries.supplier_address_query(this.frm.doc);
+ return erpnext.queries.supplier_address_query(this.frm);
});
}
}
diff --git a/erpnext/public/js/queries.js b/erpnext/public/js/queries.js
index cfc602acf00..801a78dad6a 100644
--- a/erpnext/public/js/queries.js
+++ b/erpnext/public/js/queries.js
@@ -112,12 +112,12 @@ $.extend(erpnext.queries, {
};
},
- supplier_address_query: function (doc) {
- if (!doc.supplier) {
- cur_frm.scroll_to_field("supplier");
+ supplier_address_query: function (frm) {
+ if (!frm.doc.supplier) {
+ frm.scroll_to_field("supplier");
frappe.show_alert({
message: __("Please set {0} first.", [
- __(frappe.meta.get_label(doc.doctype, "supplier", doc.name)),
+ __(frappe.meta.get_label(frm.doc.doctype, "supplier", frm.doc.name)),
]),
indicator: "orange",
});
@@ -125,7 +125,7 @@ $.extend(erpnext.queries, {
return {
query: "frappe.contacts.doctype.address.address.address_query",
- filters: { link_doctype: "Supplier", link_name: doc.supplier },
+ filters: { link_doctype: "Supplier", link_name: frm.doc.supplier },
};
},
From 93ea2f93b686a5501f112e8b2fa5832982ff58f2 Mon Sep 17 00:00:00 2001
From: Karm Soni
Date: Thu, 17 Apr 2025 16:32:59 +0530
Subject: [PATCH 16/41] feat: add display dispatch address when dispatch
address is selected
(cherry picked from commit d12998e524469f2e49c9660a3043dca0188352f0)
---
erpnext/public/js/controllers/buying.js | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/erpnext/public/js/controllers/buying.js b/erpnext/public/js/controllers/buying.js
index 630c6609c7b..c14a0d0cdc2 100644
--- a/erpnext/public/js/controllers/buying.js
+++ b/erpnext/public/js/controllers/buying.js
@@ -305,6 +305,12 @@ erpnext.buying = {
"shipping_address_display", true);
}
+ dispatch_address(){
+ var me = this;
+ erpnext.utils.get_address_display(this.frm, "dispatch_address",
+ "dispatch_address_display", true);
+ }
+
billing_address() {
erpnext.utils.get_address_display(this.frm, "billing_address",
"billing_address_display", true);
From ac3b2ba0032eb40e61bb7cdc3eee398bb6563874 Mon Sep 17 00:00:00 2001
From: Karm Soni
Date: Fri, 18 Apr 2025 13:24:47 +0530
Subject: [PATCH 17/41] fix: correct query for dispatch_address; remove
unnecessary code; increase reusability;
(cherry picked from commit 999ffe86a7ef3ca3227290817d5ee6d57ffb8819)
---
erpnext/accounts/party.py | 69 ++++++++++---------------
erpnext/public/js/controllers/buying.js | 6 +--
erpnext/public/js/queries.js | 17 ------
3 files changed, 27 insertions(+), 65 deletions(-)
diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py
index ca63abeb282..ab00097e426 100644
--- a/erpnext/accounts/party.py
+++ b/erpnext/accounts/party.py
@@ -202,43 +202,44 @@ def set_address_details(
billing_address_field = (
"customer_address" if party_type in ["Lead", "Prospect"] else party_type.lower() + "_address"
)
+
party_details[billing_address_field] = party_address or get_default_address(party_type, party.name)
if doctype:
party_details.update(
get_fetch_values(doctype, billing_address_field, party_details[billing_address_field])
)
+
# address display
party_details.address_display = render_address(
party_details[billing_address_field], check_permissions=not ignore_permissions
)
- # shipping address
+
+ # Initialize shipping address fields based on party type
if party_type in ["Customer", "Lead"]:
- party_details.shipping_address_name = shipping_address or get_party_shipping_address(
- party_type, party.name
- )
- party_details.shipping_address = render_address(
- party_details["shipping_address_name"], check_permissions=not ignore_permissions
- )
- if doctype:
- party_details.update(
- get_fetch_values(doctype, "shipping_address_name", party_details.shipping_address_name)
+ party_shipping_address_field = "shipping_address_name"
+ party_shipping_address_display_field = "shipping_address"
+ is_party_type_supplier = False
+
+ else: # Default to Supplier
+ party_shipping_address_field = "dispatch_address"
+ party_shipping_address_display_field = "dispatch_address_display"
+ is_party_type_supplier = True
+
+ party_details[party_shipping_address_field] = (
+ dispatch_address if is_party_type_supplier else shipping_address
+ ) or get_party_shipping_address(party_type, party.name)
+
+ party_details[party_shipping_address_display_field] = render_address(
+ party_details[party_shipping_address_field], check_permissions=not ignore_permissions
+ )
+
+ if doctype:
+ party_details.update(
+ get_fetch_values(
+ doctype, party_shipping_address_field, party_details[party_shipping_address_field]
)
-
- # dispatch address
- elif party_type == "Supplier":
- party_details.dispatch_address = dispatch_address or get_party_shipping_address(
- party_type, party.name
)
- party_details.dispatch_address_display = render_address(
- party_details["dispatch_address"], check_permissions=not ignore_permissions
- )
-
- if doctype:
- party_details.update(
- get_fetch_values(doctype, "dispatch_address", party_details.dispatch_address)
- )
-
if company_address:
party_details.company_address = company_address
else:
@@ -276,27 +277,9 @@ def set_address_details(
**get_fetch_values(doctype, "shipping_address", party_details.billing_address),
)
- if doctype != "Supplier Quotation":
- if dispatch_address:
- party_details.update(
- dispatch_address=dispatch_address,
- dispatch_address_display=render_address(
- dispatch_address, check_permissions=not ignore_permissions
- ),
- **get_fetch_values(doctype, "dispatch_address", dispatch_address),
- )
-
- # dispatch address - if not already set
- if not party_details.dispatch_address:
- party_details.update(
- dispatch_address=party_details.supplier_address,
- dispatch_address_display=party_details.address_display,
- **get_fetch_values(doctype, "dispatch_address", party_details.supplier_address),
- )
-
party_address, shipping_address = (
party_details.get(billing_address_field),
- party_details.shipping_address_name,
+ party_details.get(party_shipping_address_field),
)
party_details["tax_category"] = get_address_tax_category(
diff --git a/erpnext/public/js/controllers/buying.js b/erpnext/public/js/controllers/buying.js
index c14a0d0cdc2..2b854d649d8 100644
--- a/erpnext/public/js/controllers/buying.js
+++ b/erpnext/public/js/controllers/buying.js
@@ -57,11 +57,7 @@ erpnext.buying = {
if(this.frm.get_field('dispatch_address')) {
this.frm.set_query("dispatch_address", () => {
- if(this.frm.doc.is_return){
- return erpnext.queries.company_address_query(this.frm.doc);
- }
-
- return erpnext.queries.supplier_address_query(this.frm);
+ return erpnext.queries.address_query(this.frm.doc);
});
}
}
diff --git a/erpnext/public/js/queries.js b/erpnext/public/js/queries.js
index 801a78dad6a..63651ec8759 100644
--- a/erpnext/public/js/queries.js
+++ b/erpnext/public/js/queries.js
@@ -112,23 +112,6 @@ $.extend(erpnext.queries, {
};
},
- supplier_address_query: function (frm) {
- if (!frm.doc.supplier) {
- frm.scroll_to_field("supplier");
- frappe.show_alert({
- message: __("Please set {0} first.", [
- __(frappe.meta.get_label(frm.doc.doctype, "supplier", frm.doc.name)),
- ]),
- indicator: "orange",
- });
- }
-
- return {
- query: "frappe.contacts.doctype.address.address.address_query",
- filters: { link_doctype: "Supplier", link_name: frm.doc.supplier },
- };
- },
-
dispatch_address_query: function (doc) {
var filters = { link_doctype: "Company", link_name: doc.company || "" };
var is_drop_ship = doc.items.some((item) => item.delivered_by_supplier);
From 62261a276f2ff0fcacf5aae3e4878c543b70c5f6 Mon Sep 17 00:00:00 2001
From: Smit Vora
Date: Mon, 21 Apr 2025 13:44:20 +0530
Subject: [PATCH 18/41] refactor: address field position
(cherry picked from commit 8ccd7a3e61e72a711d9cbdaa5e89dddaa6a42e05)
# Conflicts:
# erpnext/public/scss/erpnext.scss
---
.../doctype/purchase_invoice/purchase_invoice.json | 8 ++++----
.../doctype/purchase_order/purchase_order.json | 6 +++---
erpnext/public/scss/erpnext.scss | 13 +++++++++++++
.../doctype/purchase_receipt/purchase_receipt.json | 8 ++++----
4 files changed, 24 insertions(+), 11 deletions(-)
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
index b7e110e6a69..b4b4e403646 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
@@ -143,11 +143,11 @@
"contact_mobile",
"contact_email",
"company_shipping_address_section",
- "shipping_address",
- "shipping_address_display",
- "column_break_126",
"dispatch_address",
"dispatch_address_display",
+ "column_break_126",
+ "shipping_address",
+ "shipping_address_display",
"company_billing_address_section",
"billing_address",
"column_break_130",
@@ -1548,7 +1548,7 @@
{
"fieldname": "company_shipping_address_section",
"fieldtype": "Section Break",
- "label": "Company Shipping Address"
+ "label": "Shipping Address"
},
{
"fieldname": "column_break_126",
diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.json b/erpnext/buying/doctype/purchase_order/purchase_order.json
index 3eda1789140..fad9f976c8a 100644
--- a/erpnext/buying/doctype/purchase_order/purchase_order.json
+++ b/erpnext/buying/doctype/purchase_order/purchase_order.json
@@ -109,11 +109,11 @@
"contact_mobile",
"contact_email",
"shipping_address_section",
- "shipping_address",
- "shipping_address_display",
- "column_break_99",
"dispatch_address",
"dispatch_address_display",
+ "column_break_99",
+ "shipping_address",
+ "shipping_address_display",
"company_billing_address_section",
"billing_address",
"column_break_103",
diff --git a/erpnext/public/scss/erpnext.scss b/erpnext/public/scss/erpnext.scss
index 29a2696470f..eab615db7e5 100644
--- a/erpnext/public/scss/erpnext.scss
+++ b/erpnext/public/scss/erpnext.scss
@@ -590,6 +590,19 @@ body[data-route="pos"] {
justify-content: center;
}
+<<<<<<< HEAD
.frappe-control[data-fieldname="other_charges_calculation"] .ql-editor {
+=======
+.frappe-control[data-fieldname="other_charges_calculation"] .ql-editor,
+.frappe-control[data-fieldname="address_display"] .ql-editor,
+.frappe-control[data-fieldname="shipping_address_display"] .ql-editor,
+.frappe-control[data-fieldname="shipping_address"] .ql-editor,
+.frappe-control[data-fieldname="dispatch_address_display"] .ql-editor,
+.frappe-control[data-fieldname="dispatch_address"] .ql-editor,
+.frappe-control[data-fieldname="source_address_display"] .ql-editor,
+.frappe-control[data-fieldname="target_address_display"] .ql-editor,
+.frappe-control[data-fieldname="billing_address_display"] .ql-editor,
+.frappe-control[data-fieldname="company_address_display"] .ql-editor {
+>>>>>>> 8ccd7a3e61 (refactor: address field position)
white-space: normal;
}
diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json
index 8fce3be270e..c3933780203 100755
--- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json
+++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json
@@ -112,11 +112,11 @@
"contact_mobile",
"contact_email",
"section_break_98",
- "shipping_address",
- "shipping_address_display",
- "column_break_100",
"dispatch_address",
"dispatch_address_display",
+ "column_break_100",
+ "shipping_address",
+ "shipping_address_display",
"billing_address_section",
"billing_address",
"column_break_104",
@@ -1200,7 +1200,7 @@
{
"fieldname": "section_break_98",
"fieldtype": "Section Break",
- "label": "Company Shipping Address"
+ "label": "Shipping Address"
},
{
"fieldname": "billing_address_section",
From 7baa8f50fb70fa75d655c1b2cf610dcb90288b3b Mon Sep 17 00:00:00 2001
From: Smit Vora
Date: Mon, 21 Apr 2025 13:46:59 +0530
Subject: [PATCH 19/41] refactor: set address details for transactions
(cherry picked from commit fb3b7d8c34a08af742bc83a71847a13d5f61dbcf)
---
erpnext/accounts/party.py | 54 +++++++++++++++++++--------------------
1 file changed, 26 insertions(+), 28 deletions(-)
diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py
index ab00097e426..d6df45d2dfc 100644
--- a/erpnext/accounts/party.py
+++ b/erpnext/accounts/party.py
@@ -199,47 +199,47 @@ def set_address_details(
*,
ignore_permissions=False,
):
- billing_address_field = (
+ # party_billing
+ party_billing_field = (
"customer_address" if party_type in ["Lead", "Prospect"] else party_type.lower() + "_address"
)
- party_details[billing_address_field] = party_address or get_default_address(party_type, party.name)
+ party_details[party_billing_field] = party_address or get_default_address(party_type, party.name)
if doctype:
party_details.update(
- get_fetch_values(doctype, billing_address_field, party_details[billing_address_field])
+ get_fetch_values(doctype, party_billing_field, party_details[party_billing_field])
)
- # address display
party_details.address_display = render_address(
- party_details[billing_address_field], check_permissions=not ignore_permissions
+ party_details[party_billing_field], check_permissions=not ignore_permissions
)
- # Initialize shipping address fields based on party type
+ # party_shipping
if party_type in ["Customer", "Lead"]:
- party_shipping_address_field = "shipping_address_name"
- party_shipping_address_display_field = "shipping_address"
- is_party_type_supplier = False
+ party_shipping_field = "shipping_address_name"
+ party_shipping_display = "shipping_address"
+ default_shipping = shipping_address
- else: # Default to Supplier
- party_shipping_address_field = "dispatch_address"
- party_shipping_address_display_field = "dispatch_address_display"
- is_party_type_supplier = True
+ else:
+ # Supplier
+ party_shipping_field = "dispatch_address"
+ party_shipping_display = "dispatch_address_display"
+ default_shipping = dispatch_address
- party_details[party_shipping_address_field] = (
- dispatch_address if is_party_type_supplier else shipping_address
- ) or get_party_shipping_address(party_type, party.name)
+ party_details[party_shipping_field] = default_shipping or get_party_shipping_address(
+ party_type, party.name
+ )
- party_details[party_shipping_address_display_field] = render_address(
- party_details[party_shipping_address_field], check_permissions=not ignore_permissions
+ party_details[party_shipping_display] = render_address(
+ party_details[party_shipping_field], check_permissions=not ignore_permissions
)
if doctype:
party_details.update(
- get_fetch_values(
- doctype, party_shipping_address_field, party_details[party_shipping_address_field]
- )
+ get_fetch_values(doctype, party_shipping_field, party_details[party_shipping_field])
)
+ # company_address
if company_address:
party_details.company_address = company_address
else:
@@ -277,22 +277,20 @@ def set_address_details(
**get_fetch_values(doctype, "shipping_address", party_details.billing_address),
)
- party_address, shipping_address = (
- party_details.get(billing_address_field),
- party_details.get(party_shipping_address_field),
+ party_billing, party_shipping = (
+ party_details.get(party_billing_field),
+ party_details.get(party_shipping_field),
)
party_details["tax_category"] = get_address_tax_category(
- party.get("tax_category"),
- party_address,
- shipping_address if party_type != "Supplier" else party_address,
+ party.get("tax_category"), party_billing, party_shipping
)
if doctype in TRANSACTION_TYPES:
with temporary_flag("company", company):
get_regional_address_details(party_details, doctype, company)
- return party_address, shipping_address
+ return party_billing, party_shipping
@erpnext.allow_regional
From d8c0e7156e4664d9f7a871e8c1eebda227626ea3 Mon Sep 17 00:00:00 2001
From: Smit Vora
Date: Mon, 21 Apr 2025 13:49:14 +0530
Subject: [PATCH 20/41] fix: map dispatch address correctly for inter company
transactions
(cherry picked from commit ceaba4220bb61eef7f35d54072c8c79049288806)
---
erpnext/accounts/doctype/sales_invoice/sales_invoice.py | 5 ++++-
erpnext/stock/doctype/delivery_note/delivery_note.py | 5 ++++-
2 files changed, 8 insertions(+), 2 deletions(-)
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
index 7761dc832de..ed3003b2511 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
@@ -2298,7 +2298,10 @@ def make_inter_company_transaction(doctype, source_name, target_doc=None):
# Invert Addresses
update_address(target_doc, "supplier_address", "address_display", source_doc.company_address)
update_address(
- target_doc, "shipping_address", "shipping_address_display", source_doc.customer_address
+ target_doc, "dispatch_address", "dispatch_address_display", source_doc.dispatch_address_name
+ )
+ update_address(
+ target_doc, "shipping_address", "shipping_address_display", source_doc.shipping_address_name
)
update_address(
target_doc, "billing_address", "billing_address_display", source_doc.customer_address
diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.py b/erpnext/stock/doctype/delivery_note/delivery_note.py
index ba04abce8f3..689027d55bc 100644
--- a/erpnext/stock/doctype/delivery_note/delivery_note.py
+++ b/erpnext/stock/doctype/delivery_note/delivery_note.py
@@ -1163,7 +1163,10 @@ def make_inter_company_transaction(doctype, source_name, target_doc=None):
# Invert the address on target doc creation
update_address(target_doc, "supplier_address", "address_display", source_doc.company_address)
update_address(
- target_doc, "shipping_address", "shipping_address_display", source_doc.customer_address
+ target_doc, "dispatch_address", "dispatch_address_display", source_doc.dispatch_address_name
+ )
+ update_address(
+ target_doc, "shipping_address", "shipping_address_display", source_doc.shipping_address_name
)
update_address(
target_doc, "billing_address", "billing_address_display", source_doc.customer_address
From feb4038c0655b2e441251311691a1972fd4853e8 Mon Sep 17 00:00:00 2001
From: ruthra kumar
Date: Mon, 28 Apr 2025 11:36:24 +0530
Subject: [PATCH 21/41] chore: resolve conflicts
---
.../purchase_invoice/purchase_invoice.json | 9 ---------
.../purchase_order/purchase_order.json | 19 -------------------
erpnext/public/scss/erpnext.scss | 13 -------------
3 files changed, 41 deletions(-)
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
index b4b4e403646..dfdcea5357a 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
@@ -1629,14 +1629,6 @@
"fieldname": "update_outstanding_for_self",
"fieldtype": "Check",
"label": "Update Outstanding for Self"
-<<<<<<< HEAD
-=======
- },
- {
- "fieldname": "sender",
- "fieldtype": "Data",
- "label": "Sender",
- "options": "Email"
},
{
"fieldname": "dispatch_address_display",
@@ -1651,7 +1643,6 @@
"label": "Select Dispatch Address ",
"options": "Address",
"print_hide": 1
->>>>>>> 54b5205221 (feat: add dispatch address fields to purchase doctypes)
}
],
"grid_page_length": 50,
diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.json b/erpnext/buying/doctype/purchase_order/purchase_order.json
index fad9f976c8a..7c9362ebaf9 100644
--- a/erpnext/buying/doctype/purchase_order/purchase_order.json
+++ b/erpnext/buying/doctype/purchase_order/purchase_order.json
@@ -1271,20 +1271,6 @@
"fieldtype": "Tab Break",
"label": "Connections",
"show_dashboard": 1
-<<<<<<< HEAD
-=======
- },
- {
- "fieldname": "advance_payment_status",
- "fieldtype": "Select",
- "hidden": 1,
- "in_standard_filter": 1,
- "label": "Advance Payment Status",
- "no_copy": 1,
- "oldfieldname": "status",
- "oldfieldtype": "Select",
- "options": "Not Initiated\nInitiated\nPartially Paid\nFully Paid",
- "print_hide": 1
},
{
"fieldname": "dispatch_address",
@@ -1299,7 +1285,6 @@
"label": "Dispatch Address Details",
"print_hide": 1,
"read_only": 1
->>>>>>> 54b5205221 (feat: add dispatch address fields to purchase doctypes)
}
],
"grid_page_length": 50,
@@ -1307,11 +1292,7 @@
"idx": 105,
"is_submittable": 1,
"links": [],
-<<<<<<< HEAD
- "modified": "2024-03-20 16:03:31.611808",
-=======
"modified": "2025-04-09 16:54:08.836106",
->>>>>>> 54b5205221 (feat: add dispatch address fields to purchase doctypes)
"modified_by": "Administrator",
"module": "Buying",
"name": "Purchase Order",
diff --git a/erpnext/public/scss/erpnext.scss b/erpnext/public/scss/erpnext.scss
index eab615db7e5..29a2696470f 100644
--- a/erpnext/public/scss/erpnext.scss
+++ b/erpnext/public/scss/erpnext.scss
@@ -590,19 +590,6 @@ body[data-route="pos"] {
justify-content: center;
}
-<<<<<<< HEAD
.frappe-control[data-fieldname="other_charges_calculation"] .ql-editor {
-=======
-.frappe-control[data-fieldname="other_charges_calculation"] .ql-editor,
-.frappe-control[data-fieldname="address_display"] .ql-editor,
-.frappe-control[data-fieldname="shipping_address_display"] .ql-editor,
-.frappe-control[data-fieldname="shipping_address"] .ql-editor,
-.frappe-control[data-fieldname="dispatch_address_display"] .ql-editor,
-.frappe-control[data-fieldname="dispatch_address"] .ql-editor,
-.frappe-control[data-fieldname="source_address_display"] .ql-editor,
-.frappe-control[data-fieldname="target_address_display"] .ql-editor,
-.frappe-control[data-fieldname="billing_address_display"] .ql-editor,
-.frappe-control[data-fieldname="company_address_display"] .ql-editor {
->>>>>>> 8ccd7a3e61 (refactor: address field position)
white-space: normal;
}
From 2e6112f21b359abace6a5bbc1d43001d967fd919 Mon Sep 17 00:00:00 2001
From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com>
Date: Mon, 28 Apr 2025 14:18:51 +0530
Subject: [PATCH 22/41] =?UTF-8?q?fix:=20update=20quantity=20validation=20u?=
=?UTF-8?q?sing=20asset=20quantity=20field=20instead=20of=E2=80=A6=20(back?=
=?UTF-8?q?port=20#46731)=20(#47284)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* fix: update quantity validation using asset quantity field instead of… (#46731)
* fix: update quantity validation using asset quantity field instead of total records
* fix: update throw message
(cherry picked from commit eae08bc619a9a23698ec79dd0d4f84471bf4edfa)
# Conflicts:
# erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py
* chore: resolved conflicts
---------
Co-authored-by: l0gesh29
Co-authored-by: Khushi Rawat <142375893+khushi8112@users.noreply.github.com>
---
.../landed_cost_voucher.py | 22 ++++++++++++++-----
1 file changed, 16 insertions(+), 6 deletions(-)
diff --git a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py
index eb84fdbc7c0..c80bcc8123b 100644
--- a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py
+++ b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py
@@ -7,7 +7,7 @@ from frappe import _
from frappe.model.document import Document
from frappe.model.meta import get_field_precision
from frappe.query_builder.custom import ConstantColumn
-from frappe.utils import flt
+from frappe.utils import cint, flt
import erpnext
from erpnext.controllers.taxes_and_totals import init_landed_taxes_and_totals
@@ -268,14 +268,24 @@ class LandedCostVoucher(Document):
)
docs = frappe.db.get_all(
"Asset",
- filters={receipt_document_type: item.receipt_document, "item_code": item.item_code},
- fields=["name", "docstatus"],
+ filters={
+ receipt_document_type: item.receipt_document,
+ "item_code": item.item_code,
+ "docstatus": ["!=", 2],
+ },
+ fields=["name", "docstatus", "asset_quantity"],
)
- if not docs or len(docs) < item.qty:
+
+ total_asset_qty = sum((cint(d.asset_quantity)) for d in docs)
+
+ if not docs or total_asset_qty < item.qty:
frappe.throw(
_(
- "There are only {0} asset created or linked to {1}. Please create or link {2} Assets with respective document."
- ).format(len(docs), item.receipt_document, item.qty)
+ "For item {0}, only {1} asset have been created or linked to {2}. "
+ "Please create or link {3} more asset with the respective document."
+ ).format(
+ item.item_code, total_asset_qty, item.receipt_document, item.qty - total_asset_qty
+ )
)
if docs:
for d in docs:
From e0cea492361e9abd57a649ffe22188914ae66aa5 Mon Sep 17 00:00:00 2001
From: Rohit Waghchaure
Date: Mon, 28 Apr 2025 13:27:57 +0530
Subject: [PATCH 23/41] fix: allow to make quality inspection after Purchase /
Delivery
(cherry picked from commit fad1a32e632056d5c62b864d1276b7fce161e228)
---
erpnext/controllers/stock_controller.py | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py
index fbc98a1a2f4..eb33187033d 100644
--- a/erpnext/controllers/stock_controller.py
+++ b/erpnext/controllers/stock_controller.py
@@ -1047,6 +1047,16 @@ class StockController(AccountsController):
def validate_qi_presence(self, row):
"""Check if QI is present on row level. Warn on save and stop on submit if missing."""
+ if self.doctype in [
+ "Purchase Receipt",
+ "Purchase Invoice",
+ "Sales Invoice",
+ "Delivery Note",
+ ] and frappe.db.get_single_value(
+ "Stock Settings", "allow_to_make_quality_inspection_after_purchase_or_delivery"
+ ):
+ return
+
if not row.quality_inspection:
msg = _("Row #{0}: Quality Inspection is required for Item {1}").format(
row.idx, frappe.bold(row.item_code)
From 0763a8d42d7924b7c34efa4728513cec30024ae1 Mon Sep 17 00:00:00 2001
From: Mihir Kandoi
Date: Mon, 28 Apr 2025 15:43:13 +0530
Subject: [PATCH 24/41] fix: set billing hours to hours
---
erpnext/projects/doctype/timesheet/timesheet.py | 13 ++++---------
1 file changed, 4 insertions(+), 9 deletions(-)
diff --git a/erpnext/projects/doctype/timesheet/timesheet.py b/erpnext/projects/doctype/timesheet/timesheet.py
index 09fdfad66ba..ae055b3f5d2 100644
--- a/erpnext/projects/doctype/timesheet/timesheet.py
+++ b/erpnext/projects/doctype/timesheet/timesheet.py
@@ -108,15 +108,10 @@ class Timesheet(Document):
self.per_billed = (self.total_billed_hours * 100) / self.total_billable_hours
def update_billing_hours(self, args):
- if args.is_billable:
- if flt(args.billing_hours) == 0.0:
- args.billing_hours = args.hours
- elif flt(args.billing_hours) > flt(args.hours):
- frappe.msgprint(
- _("Warning - Row {0}: Billing Hours are more than Actual Hours").format(args.idx),
- indicator="orange",
- alert=True,
- )
+ if args.is_billable and (
+ flt(args.billing_hours) == 0.0 or flt(args.billing_hours) != flt(args.hours)
+ ):
+ args.billing_hours = args.hours
else:
args.billing_hours = 0
From a9df1f5f6bac60c04067255c83e0bdfff2f5a262 Mon Sep 17 00:00:00 2001
From: Mihir Kandoi
Date: Mon, 28 Apr 2025 16:07:54 +0530
Subject: [PATCH 25/41] fix: update billing hours when hours is changed
---
erpnext/projects/doctype/timesheet/timesheet.js | 1 +
erpnext/projects/doctype/timesheet/timesheet.py | 15 +++++++++------
2 files changed, 10 insertions(+), 6 deletions(-)
diff --git a/erpnext/projects/doctype/timesheet/timesheet.js b/erpnext/projects/doctype/timesheet/timesheet.js
index 168b891e98c..4c78d939ebc 100644
--- a/erpnext/projects/doctype/timesheet/timesheet.js
+++ b/erpnext/projects/doctype/timesheet/timesheet.js
@@ -296,6 +296,7 @@ frappe.ui.form.on("Timesheet Detail", {
hours: function (frm, cdt, cdn) {
calculate_end_time(frm, cdt, cdn);
+ update_billing_hours(frm, cdt, cdn);
calculate_billing_costing_amount(frm, cdt, cdn);
calculate_time_and_amount(frm);
},
diff --git a/erpnext/projects/doctype/timesheet/timesheet.py b/erpnext/projects/doctype/timesheet/timesheet.py
index ae055b3f5d2..aa344c74fe9 100644
--- a/erpnext/projects/doctype/timesheet/timesheet.py
+++ b/erpnext/projects/doctype/timesheet/timesheet.py
@@ -108,12 +108,15 @@ class Timesheet(Document):
self.per_billed = (self.total_billed_hours * 100) / self.total_billable_hours
def update_billing_hours(self, args):
- if args.is_billable and (
- flt(args.billing_hours) == 0.0 or flt(args.billing_hours) != flt(args.hours)
- ):
- args.billing_hours = args.hours
- else:
- args.billing_hours = 0
+ if args.is_billable:
+ if flt(args.billing_hours) == 0.0:
+ args.billing_hours = args.hours
+ elif flt(args.billing_hours) > flt(args.hours):
+ frappe.msgprint(
+ _("Warning - Row {0}: Billing Hours are more than Actual Hours").format(args.idx),
+ indicator="orange",
+ alert=True,
+ )
def set_status(self):
self.status = {"0": "Draft", "1": "Submitted", "2": "Cancelled"}[str(self.docstatus or 0)]
From 8a30a313023458771b094c824ac704bf378a80f9 Mon Sep 17 00:00:00 2001
From: Mihir Kandoi
Date: Mon, 28 Apr 2025 16:08:40 +0530
Subject: [PATCH 26/41] fix: missing else statement
---
erpnext/projects/doctype/timesheet/timesheet.py | 2 ++
1 file changed, 2 insertions(+)
diff --git a/erpnext/projects/doctype/timesheet/timesheet.py b/erpnext/projects/doctype/timesheet/timesheet.py
index aa344c74fe9..09fdfad66ba 100644
--- a/erpnext/projects/doctype/timesheet/timesheet.py
+++ b/erpnext/projects/doctype/timesheet/timesheet.py
@@ -117,6 +117,8 @@ class Timesheet(Document):
indicator="orange",
alert=True,
)
+ else:
+ args.billing_hours = 0
def set_status(self):
self.status = {"0": "Draft", "1": "Submitted", "2": "Cancelled"}[str(self.docstatus or 0)]
From 91bcefef8ceb43a993989d613e6f6cf7cbba63c9 Mon Sep 17 00:00:00 2001
From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com>
Date: Mon, 28 Apr 2025 19:38:27 +0530
Subject: [PATCH 28/41] fix: validation if no stock ledger entries against
stock reco (backport #47292) (#47293)
fix: validation if no stock ledger entries against stock reco (#47292)
(cherry picked from commit 3d36d0b1df46008d9cec73393968a77d06aafced)
Co-authored-by: rohitwaghchaure
---
.../doctype/stock_reconciliation/stock_reconciliation.py | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
index c3d0480e820..0c28afe07e4 100644
--- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
+++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
@@ -726,6 +726,12 @@ class StockReconciliation(StockController):
)
self.make_sl_entries(sl_entries, allow_negative_stock=allow_negative_stock)
+ elif self.docstatus == 1:
+ frappe.throw(
+ _(
+ "No stock ledger entries were created. Please set the quantity or valuation rate for the items properly and try again."
+ )
+ )
def make_adjustment_entry(self, row, sl_entries):
from erpnext.stock.stock_ledger import get_stock_value_difference
From b0399fe9488101b4787a4508e96b6269ff28933e Mon Sep 17 00:00:00 2001
From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com>
Date: Mon, 28 Apr 2025 22:01:57 +0530
Subject: [PATCH 29/41] =?UTF-8?q?fix:=20QI=20reference=20not=20set=20if=20?=
=?UTF-8?q?'Action=20If=20Quality=20Inspection=20Is=20Not=20Sub=E2=80=A6?=
=?UTF-8?q?=20(backport=20#47294)=20(#47295)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
fix: QI reference not set if 'Action If Quality Inspection Is Not Sub… (#47294)
fix: qi reference not set if 'Action If Quality Inspection Is Not Submitted' is blank
(cherry picked from commit 0701a8cf5a1a410f1ce587e06258bbe8d08fa9c8)
Co-authored-by: rohitwaghchaure
---
.../doctype/quality_inspection/quality_inspection.py | 9 +++++----
1 file changed, 5 insertions(+), 4 deletions(-)
diff --git a/erpnext/stock/doctype/quality_inspection/quality_inspection.py b/erpnext/stock/doctype/quality_inspection/quality_inspection.py
index 8aed2277de3..021b7b1cf17 100644
--- a/erpnext/stock/doctype/quality_inspection/quality_inspection.py
+++ b/erpnext/stock/doctype/quality_inspection/quality_inspection.py
@@ -203,10 +203,11 @@ class QualityInspection(Document):
self.get_item_specification_details()
def on_update(self):
- if (
- frappe.db.get_single_value("Stock Settings", "action_if_quality_inspection_is_not_submitted")
- == "Warn"
- ):
+ action_if_qi_in_draft = frappe.db.get_single_value(
+ "Stock Settings", "action_if_quality_inspection_is_not_submitted"
+ )
+
+ if not action_if_qi_in_draft or action_if_qi_in_draft == "Warn":
self.update_qc_reference()
def on_submit(self):
From 6b1b30a4a613341ac3724230ac1712d826f347ed Mon Sep 17 00:00:00 2001
From: Mihir Kandoi
Date: Thu, 24 Apr 2025 16:10:12 +0530
Subject: [PATCH 30/41] fix: price currency in supplier quotation comparison
(cherry picked from commit 88926eb2a7bb8e6814064236a133b3b3bbc9301b)
---
.../supplier_quotation_comparison.py | 24 +++++++------------
1 file changed, 8 insertions(+), 16 deletions(-)
diff --git a/erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.py b/erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.py
index 085f30f84d9..ad181802c79 100644
--- a/erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.py
+++ b/erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.py
@@ -83,19 +83,11 @@ def prepare_data(supplier_quotation_data, filters):
supplier_qty_price_map = {}
group_by_field = "supplier_name" if filters.get("group_by") == "Group by Supplier" else "item_code"
- company_currency = frappe.db.get_default("currency")
float_precision = cint(frappe.db.get_default("float_precision")) or 2
for data in supplier_quotation_data:
group = data.get(group_by_field) # get item or supplier value for this row
- supplier_currency = frappe.db.get_value("Supplier", data.get("supplier_name"), "default_currency")
-
- if supplier_currency:
- exchange_rate = get_exchange_rate(supplier_currency, company_currency)
- else:
- exchange_rate = 1
-
row = {
"item_code": ""
if group_by_field == "item_code"
@@ -103,7 +95,7 @@ def prepare_data(supplier_quotation_data, filters):
"supplier_name": "" if group_by_field == "supplier_name" else data.get("supplier_name"),
"quotation": data.get("parent"),
"qty": data.get("qty"),
- "price": flt(data.get("amount") * exchange_rate, float_precision),
+ "price": flt(data.get("amount"), float_precision),
"uom": data.get("uom"),
"price_list_currency": data.get("price_list_currency"),
"currency": data.get("currency"),
@@ -209,6 +201,13 @@ def get_columns(filters):
columns = [
{"fieldname": "uom", "label": _("UOM"), "fieldtype": "Link", "options": "UOM", "width": 90},
{"fieldname": "qty", "label": _("Quantity"), "fieldtype": "Float", "width": 80},
+ {
+ "fieldname": "stock_uom",
+ "label": _("Stock UOM"),
+ "fieldtype": "Link",
+ "options": "UOM",
+ "width": 90,
+ },
{
"fieldname": "currency",
"label": _("Currency"),
@@ -223,13 +222,6 @@ def get_columns(filters):
"options": "currency",
"width": 110,
},
- {
- "fieldname": "stock_uom",
- "label": _("Stock UOM"),
- "fieldtype": "Link",
- "options": "UOM",
- "width": 90,
- },
{
"fieldname": "price_per_unit",
"label": _("Price per Unit (Stock UOM)"),
From 954fec16f4a4fff59a064c7ccf2f5d6dfcb9e475 Mon Sep 17 00:00:00 2001
From: Mihir Kandoi
Date: Mon, 28 Apr 2025 23:51:51 +0530
Subject: [PATCH 31/41] fix: commas in rfq portal js
(cherry picked from commit bd727e069be72e3cb819670ced225998e0b6c704)
---
erpnext/templates/includes/rfq.js | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/erpnext/templates/includes/rfq.js b/erpnext/templates/includes/rfq.js
index 37beb5a584b..cc998a90030 100644
--- a/erpnext/templates/includes/rfq.js
+++ b/erpnext/templates/includes/rfq.js
@@ -31,8 +31,8 @@ rfq = class rfq {
var me = this;
$('.rfq-items').on("change", ".rfq-qty", function(){
me.idx = parseFloat($(this).attr('data-idx'));
- me.qty = parseFloat($(this).val()) || 0;
- me.rate = parseFloat($(repl('.rfq-rate[data-idx=%(idx)s]',{'idx': me.idx})).val());
+ me.qty = parseFloat(flt($(this).val())) || 0;
+ me.rate = parseFloat(flt($(repl('.rfq-rate[data-idx=%(idx)s]',{'idx': me.idx})).val()));
me.update_qty_rate();
$(this).val(format_number(me.qty, doc.number_format, 2));
})
@@ -42,8 +42,8 @@ rfq = class rfq {
var me = this;
$(".rfq-items").on("change", ".rfq-rate", function(){
me.idx = parseFloat($(this).attr('data-idx'));
- me.rate = parseFloat($(this).val()) || 0;
- me.qty = parseFloat($(repl('.rfq-qty[data-idx=%(idx)s]',{'idx': me.idx})).val());
+ me.rate = parseFloat(flt($(this).val())) || 0;
+ me.qty = parseFloat(flt($(repl('.rfq-qty[data-idx=%(idx)s]',{'idx': me.idx})).val()));
me.update_qty_rate();
$(this).val(format_number(me.rate, doc.number_format, 2));
})
From 9495a2ac9df2b531e7dc70c3b35b438abb2ae72b Mon Sep 17 00:00:00 2001
From: Mihir Kandoi
Date: Tue, 29 Apr 2025 11:45:46 +0530
Subject: [PATCH 32/41] feat: change sabb qty automatically incase of internal
transfer PR if sabb only has 1 batch (#47256)
* feat: change sabb qty automatically incase of internal transfer PR if sabb only has 1 batch
* fix: prevent creation of SABB on every save
* perf: optimize code
* fix: remove unnecessary conditon
* refactor: change if to elif
* fix: remove dn_item_qty and set to item.qty
* test: added test
(cherry picked from commit 47927b38a9aa185102a12688af4b1d784827f560)
---
erpnext/controllers/buying_controller.py | 22 ++++++++
erpnext/controllers/stock_controller.py | 10 +++-
.../purchase_receipt/test_purchase_receipt.py | 55 +++++++++++++++++++
3 files changed, 85 insertions(+), 2 deletions(-)
diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py
index 267b0ed5dd5..3fdf92e7990 100644
--- a/erpnext/controllers/buying_controller.py
+++ b/erpnext/controllers/buying_controller.py
@@ -98,7 +98,29 @@ class BuyingController(SubcontractingController):
item.from_warehouse,
type_of_transaction="Outward",
do_not_submit=True,
+ qty=item.qty,
)
+ elif (
+ not self.is_new()
+ and item.serial_and_batch_bundle
+ and next(
+ (
+ old_item
+ for old_item in self.get_doc_before_save().items
+ if old_item.name == item.name and old_item.qty != item.qty
+ ),
+ None,
+ )
+ and len(
+ sabe := frappe.get_all(
+ "Serial and Batch Entry",
+ filters={"parent": item.serial_and_batch_bundle, "serial_no": ["is", "not set"]},
+ pluck="name",
+ )
+ )
+ == 1
+ ):
+ frappe.set_value("Serial and Batch Entry", sabe[0], "qty", item.qty)
def set_rate_for_standalone_debit_note(self):
if self.get("is_return") and self.get("update_stock") and not self.return_against:
diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py
index eb33187033d..80f860b4553 100644
--- a/erpnext/controllers/stock_controller.py
+++ b/erpnext/controllers/stock_controller.py
@@ -811,7 +811,7 @@ class StockController(AccountsController):
)
def make_package_for_transfer(
- self, serial_and_batch_bundle, warehouse, type_of_transaction=None, do_not_submit=None
+ self, serial_and_batch_bundle, warehouse, type_of_transaction=None, do_not_submit=None, qty=0
):
return make_bundle_for_material_transfer(
is_new=self.is_new(),
@@ -822,6 +822,7 @@ class StockController(AccountsController):
warehouse=warehouse,
type_of_transaction=type_of_transaction,
do_not_submit=do_not_submit,
+ qty=qty,
)
def get_sl_entries(self, d, args):
@@ -1815,15 +1816,20 @@ def make_bundle_for_material_transfer(**kwargs):
kwargs.type_of_transaction = "Inward"
bundle_doc = frappe.copy_doc(bundle_doc)
+ bundle_doc.docstatus = 0
bundle_doc.warehouse = kwargs.warehouse
bundle_doc.type_of_transaction = kwargs.type_of_transaction
bundle_doc.voucher_type = kwargs.voucher_type
bundle_doc.voucher_no = "" if kwargs.is_new or kwargs.docstatus == 2 else kwargs.voucher_no
bundle_doc.is_cancelled = 0
+ qty = 0
+ if len(bundle_doc.entries) == 1 and kwargs.qty < bundle_doc.total_qty and not bundle_doc.has_serial_no:
+ qty = kwargs.qty
+
for row in bundle_doc.entries:
row.is_outward = 0
- row.qty = abs(row.qty)
+ row.qty = abs(qty or row.qty)
row.stock_value_difference = abs(row.stock_value_difference)
if kwargs.type_of_transaction == "Outward":
row.qty *= -1
diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
index 8e8532a904b..984f313b848 100644
--- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
+++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
@@ -7,6 +7,8 @@ from frappe.utils import add_days, cint, cstr, flt, get_datetime, getdate, nowti
from pypika import functions as fn
import erpnext
+import erpnext.controllers
+import erpnext.controllers.status_updater
from erpnext.accounts.doctype.account.test_account import get_inventory_account
from erpnext.buying.doctype.supplier.test_supplier import create_supplier
from erpnext.controllers.buying_controller import QtyMismatchError
@@ -4129,6 +4131,59 @@ class TestPurchaseReceipt(FrappeTestCase):
self.assertTrue(sles)
+ def test_internal_pr_qty_change_only_single_batch(self):
+ from erpnext.stock.doctype.delivery_note.delivery_note import make_inter_company_purchase_receipt
+ from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note
+
+ prepare_data_for_internal_transfer()
+
+ def get_sabb_qty(sabb):
+ return frappe.get_value("Serial and Batch Bundle", sabb, "total_qty")
+
+ item = make_item("Item with only Batch", {"has_batch_no": 1})
+ item.create_new_batch = 1
+ item.save()
+
+ make_purchase_receipt(
+ item_code=item.item_code,
+ qty=10,
+ rate=100,
+ company="_Test Company with perpetual inventory",
+ warehouse="Stores - TCP1",
+ )
+
+ dn = create_delivery_note(
+ item_code=item.item_code,
+ qty=10,
+ rate=100,
+ company="_Test Company with perpetual inventory",
+ customer="_Test Internal Customer 2",
+ cost_center="Main - TCP1",
+ warehouse="Stores - TCP1",
+ target_warehouse="Work In Progress - TCP1",
+ )
+ pr = make_inter_company_purchase_receipt(dn.name)
+
+ pr.items[0].warehouse = "Stores - TCP1"
+ pr.items[0].qty = 8
+ pr.save()
+
+ # Test 1 - Check if SABB qty is changed on first save
+ self.assertEqual(abs(get_sabb_qty(pr.items[0].serial_and_batch_bundle)), 8)
+
+ pr.items[0].qty = 6
+ pr.items[0].received_qty = 6
+ pr.save()
+
+ # Test 2 - Check if SABB qty is changed when saved again
+ self.assertEqual(abs(get_sabb_qty(pr.items[0].serial_and_batch_bundle)), 6)
+
+ pr.items[0].qty = 12
+ pr.items[0].received_qty = 12
+
+ # Test 3 - OverAllowanceError should be thrown as qty is greater than qty in DN
+ self.assertRaises(erpnext.controllers.status_updater.OverAllowanceError, pr.submit)
+
def prepare_data_for_internal_transfer():
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier
From c2235e2d17d207dc448a3c72450263725b611ada Mon Sep 17 00:00:00 2001
From: venkat102
Date: Thu, 24 Apr 2025 11:33:18 +0530
Subject: [PATCH 33/41] fix(payment request): get advance amount based on
transaction currency
(cherry picked from commit b570d97b4d5ca4ea753cfe11d321fe9ffd1bc52c)
---
.../accounts/doctype/payment_request/payment_request.py | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/erpnext/accounts/doctype/payment_request/payment_request.py b/erpnext/accounts/doctype/payment_request/payment_request.py
index 0027f0aa8be..344248b57b6 100644
--- a/erpnext/accounts/doctype/payment_request/payment_request.py
+++ b/erpnext/accounts/doctype/payment_request/payment_request.py
@@ -670,7 +670,12 @@ def get_amount(ref_doc, payment_account=None):
dt = ref_doc.doctype
if dt in ["Sales Order", "Purchase Order"]:
- grand_total = (flt(ref_doc.rounded_total) or flt(ref_doc.grand_total)) - ref_doc.advance_paid
+ advance_amount = flt(ref_doc.advance_paid)
+ if ref_doc.party_account_currency != ref_doc.currency:
+ advance_amount = flt(flt(ref_doc.advance_paid) / ref_doc.conversion_rate)
+
+ grand_total = (flt(ref_doc.rounded_total) or flt(ref_doc.grand_total)) - advance_amount
+
elif dt in ["Sales Invoice", "Purchase Invoice"]:
if (
dt == "Sales Invoice"
From 0927155171f7075e096443faace4f7f86756753f Mon Sep 17 00:00:00 2001
From: ljain112
Date: Thu, 24 Apr 2025 18:54:03 +0530
Subject: [PATCH 34/41] fix: compare total debit/credit with precision for
Inter Company Journal Entry
(cherry picked from commit 5fe247557ef65034e69879db57a6f3d875a2aa4a)
---
.../accounts/doctype/journal_entry/journal_entry.py | 13 +++++++++++--
1 file changed, 11 insertions(+), 2 deletions(-)
diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py
index e8ac493d659..a29eae8d5aa 100644
--- a/erpnext/accounts/doctype/journal_entry/journal_entry.py
+++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py
@@ -250,11 +250,20 @@ class JournalEntry(AccountsController):
def validate_inter_company_accounts(self):
if self.voucher_type == "Inter Company Journal Entry" and self.inter_company_journal_entry_reference:
- doc = frappe.get_doc("Journal Entry", self.inter_company_journal_entry_reference)
+ doc = frappe.db.get_value(
+ "Journal Entry",
+ self.inter_company_journal_entry_reference,
+ ["company", "total_debit", "total_credit"],
+ as_dict=True,
+ )
account_currency = frappe.get_cached_value("Company", self.company, "default_currency")
previous_account_currency = frappe.get_cached_value("Company", doc.company, "default_currency")
if account_currency == previous_account_currency:
- if self.total_credit != doc.total_debit or self.total_debit != doc.total_credit:
+ credit_precision = self.precision("total_credit")
+ debit_precision = self.precision("total_debit")
+ if (flt(self.total_credit, credit_precision) != flt(doc.total_debit, debit_precision)) or (
+ flt(self.total_debit, debit_precision) != flt(doc.total_credit, credit_precision)
+ ):
frappe.throw(_("Total Credit/ Debit Amount should be same as linked Journal Entry"))
def validate_depr_entry_voucher_type(self):
From afb67f1f0cba831df327f8644cc8f141fd83439e Mon Sep 17 00:00:00 2001
From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com>
Date: Tue, 29 Apr 2025 12:40:10 +0530
Subject: [PATCH 35/41] fix: add transaction_date in field_no_map when creating
PO from SQ (backport #47257) (#47313)
* fix: add transaction_date in field_no_map when creating PO from SQ
(cherry picked from commit 3790c6c551dd03e9b9235a8732c526dd25a3e14a)
* fix: test case
(cherry picked from commit acd152978092d7fcb9a6b82cf47be7d46a4470d8)
# Conflicts:
# erpnext/buying/doctype/supplier_quotation/test_supplier_quotation.py
* fix: remove unused import
(cherry picked from commit 9e640341fd8410b6e2fe4bfddad8188ceb0134ab)
# Conflicts:
# erpnext/buying/doctype/supplier_quotation/test_supplier_quotation.py
* chore: fix conflicts
* chore: remove unused imports
---------
Co-authored-by: Mihir Kandoi
---
.../buying/doctype/supplier_quotation/supplier_quotation.py | 1 +
.../doctype/supplier_quotation/test_supplier_quotation.py | 3 ++-
2 files changed, 3 insertions(+), 1 deletion(-)
diff --git a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.py b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.py
index 69de7068b68..215022e18a6 100644
--- a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.py
+++ b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.py
@@ -234,6 +234,7 @@ def make_purchase_order(source_name, target_doc=None):
{
"Supplier Quotation": {
"doctype": "Purchase Order",
+ "field_no_map": ["transaction_date"],
"validation": {
"docstatus": ["=", 1],
},
diff --git a/erpnext/buying/doctype/supplier_quotation/test_supplier_quotation.py b/erpnext/buying/doctype/supplier_quotation/test_supplier_quotation.py
index 13c851c7353..84df61de373 100644
--- a/erpnext/buying/doctype/supplier_quotation/test_supplier_quotation.py
+++ b/erpnext/buying/doctype/supplier_quotation/test_supplier_quotation.py
@@ -4,6 +4,7 @@
import frappe
from frappe.tests.utils import FrappeTestCase
+from frappe.utils import add_days, today
class TestPurchaseOrder(FrappeTestCase):
@@ -25,7 +26,7 @@ class TestPurchaseOrder(FrappeTestCase):
for doc in po.get("items"):
if doc.get("item_code"):
- doc.set("schedule_date", "2013-04-12")
+ doc.set("schedule_date", add_days(today(), 1))
po.insert()
From fc8a8b5433a5448ce1a03e211e935d63a3e03cbd Mon Sep 17 00:00:00 2001
From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com>
Date: Tue, 29 Apr 2025 12:40:45 +0530
Subject: [PATCH 36/41] fix: remove invalid email account creation (backport
#47318) (#47323)
fix: remove invalid email account creation (#47318)
(cherry picked from commit 7423e4187fd8bb43c1adda9ce4468c51d16df5b5)
Co-authored-by: Diptanil Saha
---
erpnext/setup/setup_wizard/operations/install_fixtures.py | 2 --
1 file changed, 2 deletions(-)
diff --git a/erpnext/setup/setup_wizard/operations/install_fixtures.py b/erpnext/setup/setup_wizard/operations/install_fixtures.py
index 270a9e06054..9f68145e71d 100644
--- a/erpnext/setup/setup_wizard/operations/install_fixtures.py
+++ b/erpnext/setup/setup_wizard/operations/install_fixtures.py
@@ -266,8 +266,6 @@ def install(country=None):
{"doctype": "Issue Priority", "name": _("Low")},
{"doctype": "Issue Priority", "name": _("Medium")},
{"doctype": "Issue Priority", "name": _("High")},
- {"doctype": "Email Account", "email_id": "sales@example.com", "append_to": "Opportunity"},
- {"doctype": "Email Account", "email_id": "support@example.com", "append_to": "Issue"},
{"doctype": "Party Type", "party_type": "Customer", "account_type": "Receivable"},
{"doctype": "Party Type", "party_type": "Supplier", "account_type": "Payable"},
{"doctype": "Party Type", "party_type": "Employee", "account_type": "Payable"},
From 2edd12b26d8d226e32d6ac9c3ae14ff61d90e3f5 Mon Sep 17 00:00:00 2001
From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com>
Date: Tue, 29 Apr 2025 12:51:18 +0530
Subject: [PATCH 37/41] fix: prevent cancellation of last asset movement
(backport #47291) (#47312)
fix: prevent cancellation of last asset movement (#47291)
* fix: prevent cancellation of last asset movement
* test: movement cancellation
* fix: allow cancellation of asset movement when cancelling asset
(cherry picked from commit 9dee4ac891cac2b9121c3d459554b65c52cb4198)
Co-authored-by: Khushi Rawat <142375893+khushi8112@users.noreply.github.com>
---
.../doctype/asset_movement/asset_movement.py | 12 ++++++
.../asset_movement/test_asset_movement.py | 39 +++++++++++++++++++
2 files changed, 51 insertions(+)
diff --git a/erpnext/assets/doctype/asset_movement/asset_movement.py b/erpnext/assets/doctype/asset_movement/asset_movement.py
index ad9b4380fa9..c68f9044ae1 100644
--- a/erpnext/assets/doctype/asset_movement/asset_movement.py
+++ b/erpnext/assets/doctype/asset_movement/asset_movement.py
@@ -152,6 +152,9 @@ class AssetMovement(Document):
""",
args,
)
+
+ self.validate_movement_cancellation(d, latest_movement_entry)
+
if latest_movement_entry:
current_location = latest_movement_entry[0][0]
current_employee = latest_movement_entry[0][1]
@@ -179,3 +182,12 @@ class AssetMovement(Document):
d.asset,
_("Asset issued to Employee {0}").format(get_link_to_form("Employee", current_employee)),
)
+
+ def validate_movement_cancellation(self, row, latest_movement_entry):
+ asset_doc = frappe.get_doc("Asset", row.asset)
+ if not latest_movement_entry and asset_doc.docstatus == 1:
+ frappe.throw(
+ _(
+ "Asset {0} has only one movement record. Please create another movement before deleting this one to maintain asset tracking."
+ ).format(row.asset)
+ )
diff --git a/erpnext/assets/doctype/asset_movement/test_asset_movement.py b/erpnext/assets/doctype/asset_movement/test_asset_movement.py
index 52590d2ba86..2d0d68cb25f 100644
--- a/erpnext/assets/doctype/asset_movement/test_asset_movement.py
+++ b/erpnext/assets/doctype/asset_movement/test_asset_movement.py
@@ -147,6 +147,45 @@ class TestAssetMovement(unittest.TestCase):
movement1.cancel()
self.assertEqual(frappe.db.get_value("Asset", asset.name, "location"), "Test Location")
+ def test_last_movement_cancellation_validation(self):
+ pr = make_purchase_receipt(item_code="Macbook Pro", qty=1, rate=100000.0, location="Test Location")
+
+ asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, "name")
+ asset = frappe.get_doc("Asset", asset_name)
+ asset.calculate_depreciation = 1
+ asset.available_for_use_date = "2020-06-06"
+ asset.purchase_date = "2020-06-06"
+ asset.append(
+ "finance_books",
+ {
+ "expected_value_after_useful_life": 10000,
+ "next_depreciation_date": "2020-12-31",
+ "depreciation_method": "Straight Line",
+ "total_number_of_depreciations": 3,
+ "frequency_of_depreciation": 10,
+ },
+ )
+ if asset.docstatus == 0:
+ asset.submit()
+
+ AssetMovement = frappe.qb.DocType("Asset Movement")
+ AssetMovementItem = frappe.qb.DocType("Asset Movement Item")
+
+ asset_movement = (
+ frappe.qb.from_(AssetMovement)
+ .join(AssetMovementItem)
+ .on(AssetMovementItem.parent == AssetMovement.name)
+ .select(AssetMovement.name)
+ .where(
+ (AssetMovementItem.asset == asset.name)
+ & (AssetMovement.company == asset.company)
+ & (AssetMovement.docstatus == 1)
+ )
+ ).run(as_dict=True)
+
+ asset_movement_doc = frappe.get_doc("Asset Movement", asset_movement[0].name)
+ self.assertRaises(frappe.ValidationError, asset_movement_doc.cancel)
+
def create_asset_movement(**args):
args = frappe._dict(args)
From d640c79c1c4fafd00fe47bbd85b937ebe575c840 Mon Sep 17 00:00:00 2001
From: Mihir Kandoi
Date: Tue, 29 Apr 2025 16:21:41 +0530
Subject: [PATCH 38/41] fix: validate if from and to time are present on
submission of job card (#47325)
(cherry picked from commit 7499c25a3c7045e14ca0c4bd3985e7f408e3eead)
---
erpnext/manufacturing/doctype/job_card/job_card.py | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/erpnext/manufacturing/doctype/job_card/job_card.py b/erpnext/manufacturing/doctype/job_card/job_card.py
index 659bb01b4d4..abea4bca606 100644
--- a/erpnext/manufacturing/doctype/job_card/job_card.py
+++ b/erpnext/manufacturing/doctype/job_card/job_card.py
@@ -705,6 +705,12 @@ class JobCard(Document):
bold("Job Card"), get_link_to_form("Job Card", self.name)
)
)
+ else:
+ for row in self.time_logs:
+ if not row.from_time or not row.to_time:
+ frappe.throw(
+ _("Row #{0}: From Time and To Time fields are required").format(row.idx),
+ )
precision = self.precision("total_completed_qty")
total_completed_qty = flt(
From 0056fb1d0fbbf17ab3d475181e1c63aa853e0418 Mon Sep 17 00:00:00 2001
From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com>
Date: Tue, 29 Apr 2025 16:27:56 +0530
Subject: [PATCH 39/41] fix: require email OR phone in shipment doctype not
both (backport #47300) (#47330)
fix: require email OR phone in shipment doctype not both (#47300)
(cherry picked from commit fc02a6510ee74191383107076c0b7fff3a1d0f91)
Co-authored-by: Mihir Kandoi
---
erpnext/stock/doctype/shipment/shipment.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/erpnext/stock/doctype/shipment/shipment.js b/erpnext/stock/doctype/shipment/shipment.js
index 8843d383531..7f1e1c8d729 100644
--- a/erpnext/stock/doctype/shipment/shipment.js
+++ b/erpnext/stock/doctype/shipment/shipment.js
@@ -162,7 +162,7 @@ frappe.ui.form.on("Shipment", {
args: { contact: contact_name },
callback: function (r) {
if (r.message) {
- if (!(r.message.contact_email && (r.message.contact_phone || r.message.contact_mobile))) {
+ if (!(r.message.contact_email || r.message.contact_phone || r.message.contact_mobile)) {
if (contact_type == "Delivery") {
frm.set_value("delivery_contact_name", "");
frm.set_value("delivery_contact", "");
From 171b6876112fd5ffd19d20999f1a92d5adf6fb61 Mon Sep 17 00:00:00 2001
From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com>
Date: Tue, 29 Apr 2025 17:12:51 +0530
Subject: [PATCH 40/41] fix: allow selling asset at zero rate (backport #47326)
(#47332)
fix: allow selling asset at zero rate (#47326)
(cherry picked from commit 05afad78fcac6fab1b3faa783428c464db7fb00d)
Co-authored-by: Khushi Rawat <142375893+khushi8112@users.noreply.github.com>
---
erpnext/accounts/doctype/sales_invoice/sales_invoice.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
index ed3003b2511..afe41846b70 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
@@ -1348,7 +1348,7 @@ class SalesInvoice(SellingController):
)
for item in self.get("items"):
- if flt(item.base_net_amount, item.precision("base_net_amount")):
+ if flt(item.base_net_amount, item.precision("base_net_amount")) or item.is_fixed_asset:
# Do not book income for transfer within same company
if self.is_internal_transfer():
continue
From d15b7ca9af106989866e9b6fac800cc1ed6e71fd Mon Sep 17 00:00:00 2001
From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com>
Date: Tue, 29 Apr 2025 18:12:36 +0530
Subject: [PATCH 41/41] fix: fix sub assembly qty calculation in production
plan when bom level >= 1 (backport #47296) (#47315)
* fix: fix sub assembly qty calculation in production plan when bom level >= 1
(cherry picked from commit bfc4ce1d5dd67a7b2a3b1d7c931077446bbdd036)
* fix: logical error
(cherry picked from commit ee10afc0747a15f7da623ab6e1c5dceb0364424a)
---------
Co-authored-by: Mihir Kandoi
---
erpnext/manufacturing/doctype/production_plan/production_plan.py | 1 +
1 file changed, 1 insertion(+)
diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py
index 681abc8ddde..dbcf07bbccf 100644
--- a/erpnext/manufacturing/doctype/production_plan/production_plan.py
+++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py
@@ -1768,6 +1768,7 @@ def get_sub_assembly_items(
continue
else:
stock_qty = stock_qty - _bin_dict.projected_qty
+ sub_assembly_items.append(d.item_code)
elif warehouse:
bin_details.setdefault(d.item_code, get_bin_details(d, company, for_warehouse=warehouse))