From 9dfe3428383e8b7cbfc0cce343d98cddcde039a8 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Mon, 11 Dec 2023 18:32:49 +0100 Subject: [PATCH 01/37] fix: get customers for leaderboard (cherry picked from commit 137b5a610880684b1c08d868072335debceb3007) --- erpnext/startup/leaderboard.py | 47 ++++++++++++++++++---------------- 1 file changed, 25 insertions(+), 22 deletions(-) diff --git a/erpnext/startup/leaderboard.py b/erpnext/startup/leaderboard.py index da7edbf8144..1a508b5ced1 100644 --- a/erpnext/startup/leaderboard.py +++ b/erpnext/startup/leaderboard.py @@ -54,12 +54,13 @@ def get_leaderboards(): @frappe.whitelist() def get_all_customers(date_range, company, field, limit=None): + filters = [["docstatus", "=", "1"], ["company", "=", company]] + from_date, to_date = parse_date_range(date_range) if field == "outstanding_amount": - filters = [["docstatus", "=", "1"], ["company", "=", company]] - if date_range: - date_range = frappe.parse_json(date_range) - filters.append(["posting_date", ">=", "between", [date_range[0], date_range[1]]]) - return frappe.db.get_all( + if from_date and to_date: + filters.append(["posting_date", "between", [from_date, to_date]]) + + return frappe.get_list( "Sales Invoice", fields=["customer as name", "sum(outstanding_amount) as value"], filters=filters, @@ -69,26 +70,20 @@ def get_all_customers(date_range, company, field, limit=None): ) else: if field == "total_sales_amount": - select_field = "sum(so_item.base_net_amount)" + select_field = "base_net_total" elif field == "total_qty_sold": - select_field = "sum(so_item.stock_qty)" + select_field = "total_qty" - date_condition = get_date_condition(date_range, "so.transaction_date") + if from_date and to_date: + filters.append(["transaction_date", "between", [from_date, to_date]]) - return frappe.db.sql( - """ - select so.customer as name, {0} as value - FROM `tabSales Order` as so JOIN `tabSales Order Item` as so_item - ON so.name = so_item.parent - where so.docstatus = 1 {1} and so.company = %s - group by so.customer - order by value DESC - limit %s - """.format( - select_field, date_condition - ), - (company, cint(limit)), - as_dict=1, + return frappe.get_list( + "Sales Order", + fields=["customer as name", f"sum({select_field}) as value"], + filters=filters, + group_by="customer", + order_by="value desc", + limit=limit, ) @@ -236,3 +231,11 @@ def get_date_condition(date_range, field): field, frappe.db.escape(from_date), frappe.db.escape(to_date) ) return date_condition + + +def parse_date_range(date_range): + if date_range: + date_range = frappe.parse_json(date_range) + return date_range[0], date_range[1] + + return None, None From 812b301aaba35f7bcb490da11047662f46077927 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Mon, 11 Dec 2023 19:34:24 +0100 Subject: [PATCH 02/37] fix: get items for leaderboard (cherry picked from commit 2721ee3a8dda2e61ddf7f59698849c2e15e0e414) --- erpnext/startup/leaderboard.py | 45 +++++++++++++++++----------------- 1 file changed, 23 insertions(+), 22 deletions(-) diff --git a/erpnext/startup/leaderboard.py b/erpnext/startup/leaderboard.py index 1a508b5ced1..48a573116d8 100644 --- a/erpnext/startup/leaderboard.py +++ b/erpnext/startup/leaderboard.py @@ -91,45 +91,46 @@ def get_all_customers(date_range, company, field, limit=None): def get_all_items(date_range, company, field, limit=None): if field in ("available_stock_qty", "available_stock_value"): select_field = "sum(actual_qty)" if field == "available_stock_qty" else "sum(stock_value)" - return frappe.db.get_all( + results = frappe.db.get_all( "Bin", fields=["item_code as name", "{0} as value".format(select_field)], group_by="item_code", order_by="value desc", limit=limit, ) + readable_active_items = set(frappe.get_list("Item", filters={"disabled": 0}, pluck="name")) + return [item for item in results if item["name"] in readable_active_items] else: if field == "total_sales_amount": - select_field = "sum(order_item.base_net_amount)" + select_field = "base_net_amount" select_doctype = "Sales Order" elif field == "total_purchase_amount": - select_field = "sum(order_item.base_net_amount)" + select_field = "base_net_amount" select_doctype = "Purchase Order" elif field == "total_qty_sold": - select_field = "sum(order_item.stock_qty)" + select_field = "stock_qty" select_doctype = "Sales Order" elif field == "total_qty_purchased": - select_field = "sum(order_item.stock_qty)" + select_field = "stock_qty" select_doctype = "Purchase Order" - date_condition = get_date_condition(date_range, "sales_order.transaction_date") + filters = [["docstatus", "=", "1"], ["company", "=", company]] + from_date, to_date = parse_date_range(date_range) + if from_date and to_date: + filters.append(["transaction_date", "between", [from_date, to_date]]) - return frappe.db.sql( - """ - select order_item.item_code as name, {0} as value - from `tab{1}` sales_order join `tab{1} Item` as order_item - on sales_order.name = order_item.parent - where sales_order.docstatus = 1 - and sales_order.company = %s {2} - group by order_item.item_code - order by value desc - limit %s - """.format( - select_field, select_doctype, date_condition - ), - (company, cint(limit)), - as_dict=1, - ) # nosec + child_doctype = f"{select_doctype} Item" + return frappe.get_list( + select_doctype, + fields=[ + f"`tab{child_doctype}`.item_code as name", + f"sum(`tab{child_doctype}`.{select_field}) as value", + ], + filters=filters, + order_by="value desc", + group_by=f"`tab{child_doctype}`.item_code", + limit=limit, + ) @frappe.whitelist() From 7477ff3b5cb7ef49c1642b901e32fc5fa295c855 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Mon, 11 Dec 2023 19:34:54 +0100 Subject: [PATCH 03/37] fix: get suppliers for leaderboard (cherry picked from commit 65df4b6aa84523b917e0b6e83555a7b86157e42e) --- erpnext/startup/leaderboard.py | 44 +++++++++++++++------------------- 1 file changed, 19 insertions(+), 25 deletions(-) diff --git a/erpnext/startup/leaderboard.py b/erpnext/startup/leaderboard.py index 48a573116d8..e9b234dc66e 100644 --- a/erpnext/startup/leaderboard.py +++ b/erpnext/startup/leaderboard.py @@ -135,12 +135,14 @@ def get_all_items(date_range, company, field, limit=None): @frappe.whitelist() def get_all_suppliers(date_range, company, field, limit=None): + filters = [["docstatus", "=", "1"], ["company", "=", company]] + from_date, to_date = parse_date_range(date_range) + if field == "outstanding_amount": - filters = [["docstatus", "=", "1"], ["company", "=", company]] - if date_range: - date_range = frappe.parse_json(date_range) - filters.append(["posting_date", "between", [date_range[0], date_range[1]]]) - return frappe.db.get_all( + if from_date and to_date: + filters.append(["posting_date", "between", [from_date, to_date]]) + + return frappe.get_list( "Purchase Invoice", fields=["supplier as name", "sum(outstanding_amount) as value"], filters=filters, @@ -150,29 +152,21 @@ def get_all_suppliers(date_range, company, field, limit=None): ) else: if field == "total_purchase_amount": - select_field = "sum(purchase_order_item.base_net_amount)" + select_field = "base_net_total" elif field == "total_qty_purchased": - select_field = "sum(purchase_order_item.stock_qty)" + select_field = "total_qty" - date_condition = get_date_condition(date_range, "purchase_order.modified") + if from_date and to_date: + filters.append(["transaction_date", "between", [from_date, to_date]]) - return frappe.db.sql( - """ - select purchase_order.supplier as name, {0} as value - FROM `tabPurchase Order` as purchase_order LEFT JOIN `tabPurchase Order Item` - as purchase_order_item ON purchase_order.name = purchase_order_item.parent - where - purchase_order.docstatus = 1 - {1} - and purchase_order.company = %s - group by purchase_order.supplier - order by value DESC - limit %s""".format( - select_field, date_condition - ), - (company, cint(limit)), - as_dict=1, - ) # nosec + return frappe.get_list( + "Purchase Order", + fields=["supplier as name", f"sum({select_field}) as value"], + filters=filters, + group_by="supplier", + order_by="value desc", + limit=limit, + ) @frappe.whitelist() From df93447d3039c2141c0d088383a10cc53f389777 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Mon, 11 Dec 2023 19:44:24 +0100 Subject: [PATCH 04/37] fix: get sales person for leaderboard (cherry picked from commit 7babfd4ac4e3852d983487f1290799165933c4c0) --- erpnext/startup/leaderboard.py | 36 +++++++++++++++++----------------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/erpnext/startup/leaderboard.py b/erpnext/startup/leaderboard.py index e9b234dc66e..cea174495b1 100644 --- a/erpnext/startup/leaderboard.py +++ b/erpnext/startup/leaderboard.py @@ -1,5 +1,4 @@ import frappe -from frappe.utils import cint def get_leaderboards(): @@ -196,24 +195,25 @@ def get_all_sales_partner(date_range, company, field, limit=None): @frappe.whitelist() def get_all_sales_person(date_range, company, field=None, limit=0): - date_condition = get_date_condition(date_range, "sales_order.transaction_date") + filters = [ + ["docstatus", "=", "1"], + ["company", "=", company], + ["Sales Team", "sales_person", "is", "set"], + ] + from_date, to_date = parse_date_range(date_range) + if from_date and to_date: + filters.append(["transaction_date", "between", [from_date, to_date]]) - return frappe.db.sql( - """ - select sales_team.sales_person as name, sum(sales_order.base_net_total) as value - from `tabSales Order` as sales_order join `tabSales Team` as sales_team - on sales_order.name = sales_team.parent and sales_team.parenttype = 'Sales Order' - where sales_order.docstatus = 1 - and sales_order.company = %s - {date_condition} - group by sales_team.sales_person - order by value DESC - limit %s - """.format( - date_condition=date_condition - ), - (company, cint(limit)), - as_dict=1, + return frappe.get_list( + "Sales Order", + fields=[ + "`tabSales Team`.sales_person as name", + "sum(`tabSales Team`.allocated_amount) as value", + ], + filters=filters, + group_by="`tabSales Team`.sales_person", + order_by="value desc", + limit=limit, ) From c63f873a6f731731785b921351f35cc56a00f98a Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Mon, 11 Dec 2023 19:45:32 +0100 Subject: [PATCH 05/37] fix: get sales partner for leaderboard (cherry picked from commit 40c1acc961d29394fb47ef6108864c7690331457) --- erpnext/startup/leaderboard.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/erpnext/startup/leaderboard.py b/erpnext/startup/leaderboard.py index cea174495b1..9542ed6aa17 100644 --- a/erpnext/startup/leaderboard.py +++ b/erpnext/startup/leaderboard.py @@ -171,20 +171,20 @@ def get_all_suppliers(date_range, company, field, limit=None): @frappe.whitelist() def get_all_sales_partner(date_range, company, field, limit=None): if field == "total_sales_amount": - select_field = "sum(`base_net_total`)" + select_field = "base_net_total" elif field == "total_commission": - select_field = "sum(`total_commission`)" + select_field = "total_commission" - filters = {"sales_partner": ["!=", ""], "docstatus": 1, "company": company} - if date_range: - date_range = frappe.parse_json(date_range) - filters["transaction_date"] = ["between", [date_range[0], date_range[1]]] + filters = [["docstatus", "=", "1"], ["company", "=", company], ["sales_partner", "is", "set"]] + from_date, to_date = parse_date_range(date_range) + if from_date and to_date: + filters.append(["transaction_date", "between", [from_date, to_date]]) return frappe.get_list( "Sales Order", fields=[ - "`sales_partner` as name", - "{} as value".format(select_field), + "sales_partner as name", + f"sum({select_field}) as value", ], filters=filters, group_by="sales_partner", From 038a30feb07bf9db771684d1a9c7b84d979ad5bd Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Mon, 11 Dec 2023 19:46:37 +0100 Subject: [PATCH 06/37] chore: deprecate unused method (cherry picked from commit 956c3c50a09036dbeb9bfdf7ecd42958bdb0ec3e) --- erpnext/startup/leaderboard.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erpnext/startup/leaderboard.py b/erpnext/startup/leaderboard.py index 9542ed6aa17..5a60d2ff967 100644 --- a/erpnext/startup/leaderboard.py +++ b/erpnext/startup/leaderboard.py @@ -1,4 +1,5 @@ import frappe +from frappe.utils.deprecations import deprecated def get_leaderboards(): @@ -217,6 +218,7 @@ def get_all_sales_person(date_range, company, field=None, limit=0): ) +@deprecated def get_date_condition(date_range, field): date_condition = "" if date_range: From 0d49dd364858cba27623f2ea92c6b07e704b4a9a Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 13 Dec 2023 23:16:26 +0530 Subject: [PATCH 07/37] Revert "fix(ux): don't update qty blindly" (backport #38728) (#38729) Revert "fix(ux): don't update qty blindly" (#38728) (cherry picked from commit 6851c5042fc5e2d3f45d070af18a33c24fc10794) Co-authored-by: Ankush Menat --- erpnext/public/js/controllers/transaction.js | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index ff00a3ade16..050b9dcd3db 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -441,6 +441,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe item.pricing_rules = '' return this.frm.call({ method: "erpnext.stock.get_item_details.get_item_details", + child: item, args: { doc: me.frm.doc, args: { @@ -489,19 +490,6 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe callback: function(r) { if(!r.exc) { frappe.run_serially([ - () => { - var child = locals[cdt][cdn]; - var std_field_list = ["doctype"] - .concat(frappe.model.std_fields_list) - .concat(frappe.model.child_table_field_list); - - for (var key in r.message) { - if (std_field_list.indexOf(key) === -1) { - if (key === "qty" && child[key]) continue; - child[key] = r.message[key]; - } - } - }, () => { var d = locals[cdt][cdn]; me.add_taxes_from_item_tax_template(d.item_tax_rate); From 229075048bd17d945da6ef2b8af71f500db7e8ea Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 14 Dec 2023 00:10:58 +0530 Subject: [PATCH 08/37] feat(RFQ): special properties in print preview (backport #38725) (#38726) feat: RFQ print preview (cherry picked from commit 27f05145ae700e1177bb2c2541e6d49f73cdbf7e) Co-authored-by: barredterra <14891507+barredterra@users.noreply.github.com> --- .../request_for_quotation/request_for_quotation.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py index 6b39982bb81..8226aa32c0e 100644 --- a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py +++ b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py @@ -80,6 +80,15 @@ class RequestforQuotation(BuyingController): supplier.quote_status = "Pending" self.send_to_supplier() + def before_print(self, settings=None): + """Use the first suppliers data to render the print preview.""" + if self.vendor or not self.suppliers: + # If a specific supplier is already set, via Tools > Download PDF, + # we don't want to override it. + return + + self.update_supplier_part_no(self.suppliers[0].supplier) + def on_cancel(self): self.db_set("status", "Cancelled") From dda95ea8922c0f6073d36edf3c0a525fbfc7295e Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 13 Dec 2023 19:05:52 +0000 Subject: [PATCH 09/37] fix: supplier removed on selection of item (backport #38712) (#38732) fix: supplier removed on selection of item (#38712) (cherry picked from commit db24e2488247eef326ef52a88dbae8e828893e7e) Co-authored-by: rohitwaghchaure --- erpnext/stock/get_item_details.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index daa7becc1f4..e47971d03e8 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -391,7 +391,6 @@ def get_basic_details(args, item, overwrite_warehouse=True): "net_amount": 0.0, "discount_percentage": 0.0, "discount_amount": flt(args.discount_amount) or 0.0, - "supplier": get_default_supplier(args, item_defaults, item_group_defaults, brand_defaults), "update_stock": args.get("update_stock") if args.get("doctype") in ["Sales Invoice", "Purchase Invoice"] else 0, @@ -411,6 +410,10 @@ def get_basic_details(args, item, overwrite_warehouse=True): } ) + default_supplier = get_default_supplier(args, item_defaults, item_group_defaults, brand_defaults) + if default_supplier: + out.supplier = default_supplier + if item.get("enable_deferred_revenue") or item.get("enable_deferred_expense"): out.update(calculate_service_end_date(args, item)) From bf585de558ca7ac1ba08230177fbd1429b120568 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Wed, 13 Dec 2023 11:25:21 +0530 Subject: [PATCH 10/37] fix: fetch exc rate of multi currency journals (cherry picked from commit 1b3ba25220ca4ef96df5416331ee2f78376da64a) --- .../payment_reconciliation.py | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py index a907dc7acf5..6ce79ee3ea6 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py +++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py @@ -539,6 +539,27 @@ class PaymentReconciliation(Document): invoice_exchange_map.update(purchase_invoice_map) + journals = [ + d.get("invoice_number") for d in invoices if d.get("invoice_type") == "Journal Entry" + ] + journals.extend( + [d.get("reference_name") for d in payments if d.get("reference_type") == "Journal Entry"] + ) + if journals: + journals = list(set(journals)) + journals_map = frappe._dict( + frappe.db.get_all( + "Journal Entry Account", + filters={"parent": ("in", journals), "account": ("in", [self.receivable_payable_account])}, + fields=[ + "parent as `name`", + "exchange_rate", + ], + as_list=1, + ) + ) + invoice_exchange_map.update(journals_map) + return invoice_exchange_map def validate_allocation(self): From 30b6321fd5f471f0678552e5f728c4ec5f1bf746 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 14 Dec 2023 14:28:20 +0530 Subject: [PATCH 11/37] fix: error while filtering on name on reconciliation tool --- .../payment_reconciliation.py | 3 +-- erpnext/controllers/accounts_controller.py | 21 ++++++++++++++++--- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py index d2c75111edd..38419a8af80 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py +++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py @@ -59,8 +59,6 @@ class PaymentReconciliation(Document): def get_payment_entries(self): order_doctype = "Sales Order" if self.party_type == "Customer" else "Purchase Order" condition = self.get_conditions(get_payments=True) - if self.payment_name: - condition += "name like '%%{0}%%'".format(self.payment_name) payment_entries = get_advance_payment_entries_for_regional( self.party_type, @@ -70,6 +68,7 @@ class PaymentReconciliation(Document): against_all_orders=True, limit=self.payment_limit, condition=condition, + payment_name=self.payment_name, ) return payment_entries diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 4ef251f8add..740e4efce8f 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -2504,6 +2504,7 @@ def get_advance_payment_entries( against_all_orders=False, limit=None, condition=None, + payment_name=None, ): party_account_field = "paid_from" if party_type == "Customer" else "paid_to" currency_field = ( @@ -2526,6 +2527,10 @@ def get_advance_payment_entries( reference_condition = "" order_list = [] + payment_name_filter = "" + if payment_name: + payment_name_filter = " and t1.name like '%%{0}%%'".format(payment_name) + if not condition: condition = "" @@ -2540,7 +2545,7 @@ def get_advance_payment_entries( where t1.name = t2.parent and t1.{1} = %s and t1.payment_type = %s and t1.party_type = %s and t1.party = %s and t1.docstatus = 1 - and t2.reference_doctype = %s {2} {3} + and t2.reference_doctype = %s {2} {3} {6} order by t1.posting_date {4} """.format( currency_field, @@ -2549,12 +2554,17 @@ def get_advance_payment_entries( condition, limit_cond, exchange_rate_field, + payment_name_filter, ), [party_account, payment_type, party_type, party, order_doctype] + order_list, as_dict=1, ) if include_unallocated: + payment_name_filter = "" + if payment_name: + payment_name_filter = " and name like '%%{0}%%'".format(payment_name) + unallocated_payment_entries = frappe.db.sql( """ select 'Payment Entry' as reference_type, name as reference_name, posting_date, @@ -2562,10 +2572,15 @@ def get_advance_payment_entries( from `tabPayment Entry` where {0} = %s and party_type = %s and party = %s and payment_type = %s - and docstatus = 1 and unallocated_amount > 0 {condition} + and docstatus = 1 and unallocated_amount > 0 {condition} {4} order by posting_date {1} """.format( - party_account_field, limit_cond, exchange_rate_field, currency_field, condition=condition or "" + party_account_field, + limit_cond, + exchange_rate_field, + currency_field, + payment_name_filter, + condition=condition or "", ), (party_account, party_type, party, payment_type), as_dict=1, From 73d525e84da63a4392501b76111d895fdef6498c Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 14 Dec 2023 15:59:19 +0530 Subject: [PATCH 12/37] fix: timezone aware SLA banner (backport #38745) (#38746) fix: timezone aware SLA banner (#38745) (cherry picked from commit eaf86a6461438720fe941100d6feccefbfa3bfed) Co-authored-by: Ankush Menat --- erpnext/public/js/utils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js index eafc1ed70e6..4c7b2534fcc 100755 --- a/erpnext/public/js/utils.js +++ b/erpnext/public/js/utils.js @@ -971,7 +971,7 @@ function set_time_to_resolve_and_response(frm, apply_sla_for_resolution) { } function get_time_left(timestamp, agreement_status) { - const diff = moment(timestamp).diff(moment()); + const diff = moment(timestamp).diff(frappe.datetime.system_datetime(true)); const diff_display = diff >= 44500 ? moment.duration(diff).humanize() : 'Failed'; let indicator = (diff_display == 'Failed' && agreement_status != 'Fulfilled') ? 'red' : 'green'; return {'diff_display': diff_display, 'indicator': indicator}; From 1469ca7d0b8d7b60da991a8e3b682517113481f3 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Fri, 1 Dec 2023 20:10:51 +0100 Subject: [PATCH 13/37] feat: set lead name from email (cherry picked from commit ceeb724acc54d06ec27f3a3b5e948e5b6236af7c) # Conflicts: # erpnext/crm/doctype/lead/lead.json --- erpnext/crm/doctype/lead/lead.json | 5 +++++ erpnext/crm/doctype/lead/lead.py | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/erpnext/crm/doctype/lead/lead.json b/erpnext/crm/doctype/lead/lead.json index 8f8a086d99e..7747e263a69 100644 --- a/erpnext/crm/doctype/lead/lead.json +++ b/erpnext/crm/doctype/lead/lead.json @@ -514,7 +514,11 @@ "idx": 5, "image_field": "image", "links": [], +<<<<<<< HEAD "modified": "2022-10-13 12:42:04.277879", +======= + "modified": "2023-12-01 18:46:49.468526", +>>>>>>> ceeb724acc (feat: set lead name from email) "modified_by": "Administrator", "module": "CRM", "name": "Lead", @@ -576,6 +580,7 @@ ], "search_fields": "lead_name,lead_owner,status", "sender_field": "email_id", + "sender_name_field": "lead_name", "show_name_in_global_search": 1, "sort_field": "modified", "sort_order": "DESC", diff --git a/erpnext/crm/doctype/lead/lead.py b/erpnext/crm/doctype/lead/lead.py index 2d5b3573ae4..cbef699ce61 100644 --- a/erpnext/crm/doctype/lead/lead.py +++ b/erpnext/crm/doctype/lead/lead.py @@ -14,6 +14,7 @@ from frappe.utils import comma_and, get_link_to_form, has_gravatar, validate_ema from erpnext.accounts.party import set_taxes from erpnext.controllers.selling_controller import SellingController from erpnext.crm.utils import CRMNote, copy_comments, link_communications, link_open_events +from erpnext.selling.doctype.customer.customer import parse_full_name class Lead(SellingController, CRMNote): @@ -48,6 +49,10 @@ class Lead(SellingController, CRMNote): return self.contact_doc = self.create_contact() + # leads created by email inbox only have the full name set + if self.lead_name and not any([self.first_name, self.middle_name, self.last_name]): + self.first_name, self.middle_name, self.last_name = parse_full_name(self.lead_name) + def after_insert(self): self.link_to_contact() From 2c52f2ce13b90a4eb14b1571d0bd70f4899765c2 Mon Sep 17 00:00:00 2001 From: Raffael Meyer <14891507+barredterra@users.noreply.github.com> Date: Thu, 14 Dec 2023 13:51:47 +0100 Subject: [PATCH 14/37] chore: resolve conflicts --- erpnext/crm/doctype/lead/lead.json | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/erpnext/crm/doctype/lead/lead.json b/erpnext/crm/doctype/lead/lead.json index 7747e263a69..b7292a77f7d 100644 --- a/erpnext/crm/doctype/lead/lead.json +++ b/erpnext/crm/doctype/lead/lead.json @@ -514,11 +514,7 @@ "idx": 5, "image_field": "image", "links": [], -<<<<<<< HEAD - "modified": "2022-10-13 12:42:04.277879", -======= "modified": "2023-12-01 18:46:49.468526", ->>>>>>> ceeb724acc (feat: set lead name from email) "modified_by": "Administrator", "module": "CRM", "name": "Lead", @@ -587,4 +583,4 @@ "states": [], "subject_field": "title", "title_field": "title" -} \ No newline at end of file +} From ed9b38bc8d32dd7b03a221b92828dc05d7cc2df9 Mon Sep 17 00:00:00 2001 From: David Arnold Date: Sun, 12 Nov 2023 13:21:18 +0100 Subject: [PATCH 15/37] fix(customer): contact creation for companies (#38055) --- erpnext/selling/doctype/customer/customer.py | 38 ++++++++++++++------ 1 file changed, 27 insertions(+), 11 deletions(-) diff --git a/erpnext/selling/doctype/customer/customer.py b/erpnext/selling/doctype/customer/customer.py index de3c21e6eb8..685c549685d 100644 --- a/erpnext/selling/doctype/customer/customer.py +++ b/erpnext/selling/doctype/customer/customer.py @@ -307,13 +307,15 @@ class Customer(TransactionBase): def create_contact(contact, party_type, party, email): """Create contact based on given contact name""" - contact = contact.split(" ") + names = contact.split(" ") contact = frappe.get_doc( { "doctype": "Contact", - "first_name": contact[0], - "last_name": len(contact) > 1 and contact[1] or "", + "first_name": names[0], + "middle_name": len(names) > 2 and " ".join(names[1:-1]) or "", + "last_name": len(names) > 1 and names[-1] or "", + "is_primary_contact": 1, } ) contact.append("email_ids", dict(email_id=email, is_primary=1)) @@ -684,14 +686,28 @@ def get_credit_limit(customer, company): def make_contact(args, is_primary_contact=1): - contact = frappe.get_doc( - { - "doctype": "Contact", - "first_name": args.get("name"), - "is_primary_contact": is_primary_contact, - "links": [{"link_doctype": args.get("doctype"), "link_name": args.get("name")}], - } - ) + values = { + "doctype": "Contact", + "is_primary_contact": is_primary_contact, + "links": [{"link_doctype": args.get("doctype"), "link_name": args.get("name")}], + } + if args.customer_type == "Individual": + names = args.get("customer_name").split(" ") + values.update( + { + "first_name": names[0], + "middle_name": len(names) > 2 and " ".join(names[1:-1]) or "", + "last_name": len(names) > 1 and names[-1] or "", + } + ) + else: + values.update( + { + "company_name": args.get("customer_name"), + } + ) + contact = frappe.get_doc(values) + if args.get("email_id"): contact.add_email(args.get("email_id"), is_primary=True) if args.get("mobile_no"): From 935e23449fde00f4f9207a445f3164844757e8c5 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Sun, 12 Nov 2023 14:48:18 +0100 Subject: [PATCH 16/37] refactor: parse full name --- erpnext/selling/doctype/customer/customer.py | 35 ++++++++++++-------- 1 file changed, 22 insertions(+), 13 deletions(-) diff --git a/erpnext/selling/doctype/customer/customer.py b/erpnext/selling/doctype/customer/customer.py index 685c549685d..7af290e052d 100644 --- a/erpnext/selling/doctype/customer/customer.py +++ b/erpnext/selling/doctype/customer/customer.py @@ -307,20 +307,19 @@ class Customer(TransactionBase): def create_contact(contact, party_type, party, email): """Create contact based on given contact name""" - names = contact.split(" ") - - contact = frappe.get_doc( + first, middle, last = parse_full_name(contact) + doc = frappe.get_doc( { "doctype": "Contact", - "first_name": names[0], - "middle_name": len(names) > 2 and " ".join(names[1:-1]) or "", - "last_name": len(names) > 1 and names[-1] or "", + "first_name": first, + "middle_name": middle, + "last_name": last, "is_primary_contact": 1, } ) - contact.append("email_ids", dict(email_id=email, is_primary=1)) - contact.append("links", dict(link_doctype=party_type, link_name=party)) - contact.insert() + doc.append("email_ids", dict(email_id=email, is_primary=1)) + doc.append("links", dict(link_doctype=party_type, link_name=party)) + return doc.insert() @frappe.whitelist() @@ -692,12 +691,12 @@ def make_contact(args, is_primary_contact=1): "links": [{"link_doctype": args.get("doctype"), "link_name": args.get("name")}], } if args.customer_type == "Individual": - names = args.get("customer_name").split(" ") + first, middle, last = parse_full_name(args.get("customer_name")) values.update( { - "first_name": names[0], - "middle_name": len(names) > 2 and " ".join(names[1:-1]) or "", - "last_name": len(names) > 1 and names[-1] or "", + "first_name": first, + "middle_name": middle, + "last_name": last, } ) else: @@ -763,3 +762,13 @@ def get_customer_primary_contact(doctype, txt, searchfield, start, page_len, fil .where((dlink.link_name == customer) & (con.name.like(f"%{txt}%"))) .run() ) + + +def parse_full_name(full_name: str) -> tuple[str, str | None, str | None]: + """Parse full name into first name, middle name and last name""" + names = full_name.split() + first_name = names[0] + middle_name = " ".join(names[1:-1]) if len(names) > 2 else None + last_name = names[-1] if len(names) > 1 else None + + return first_name, middle_name, last_name From d6d717c3150a36755669e7ac49f762610bf6b87a Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Sun, 12 Nov 2023 14:51:40 +0100 Subject: [PATCH 17/37] test: parse full name --- .../selling/doctype/customer/test_customer.py | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/erpnext/selling/doctype/customer/test_customer.py b/erpnext/selling/doctype/customer/test_customer.py index a621c737ed3..7a601a78876 100644 --- a/erpnext/selling/doctype/customer/test_customer.py +++ b/erpnext/selling/doctype/customer/test_customer.py @@ -10,7 +10,11 @@ from frappe.utils import flt from erpnext.accounts.party import get_due_date from erpnext.exceptions import PartyDisabled, PartyFrozen -from erpnext.selling.doctype.customer.customer import get_credit_limit, get_customer_outstanding +from erpnext.selling.doctype.customer.customer import ( + get_credit_limit, + get_customer_outstanding, + parse_full_name, +) from erpnext.tests.utils import create_test_contact_and_address test_ignore = ["Price List"] @@ -373,6 +377,22 @@ class TestCustomer(FrappeTestCase): frappe.db.set_value("Selling Settings", None, "cust_master_name", "Customer Name") + def test_parse_full_name(self): + first, middle, last = parse_full_name("John") + self.assertEqual(first, "John") + self.assertEqual(middle, None) + self.assertEqual(last, None) + + first, middle, last = parse_full_name("John Doe") + self.assertEqual(first, "John") + self.assertEqual(middle, None) + self.assertEqual(last, "Doe") + + first, middle, last = parse_full_name("John Michael Doe") + self.assertEqual(first, "John") + self.assertEqual(middle, "Michael") + self.assertEqual(last, "Doe") + def get_customer_dict(customer_name): return { From 6ed7c6610acf328b1378597d4361f9bc707cb2fa Mon Sep 17 00:00:00 2001 From: David Arnold Date: Sat, 11 Nov 2023 15:05:07 +0100 Subject: [PATCH 18/37] fix(customer): quick form and integration fixes (#37386) --- erpnext/selling/doctype/customer/customer.py | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/erpnext/selling/doctype/customer/customer.py b/erpnext/selling/doctype/customer/customer.py index 7af290e052d..4bbce9b2f00 100644 --- a/erpnext/selling/doctype/customer/customer.py +++ b/erpnext/selling/doctype/customer/customer.py @@ -711,12 +711,16 @@ def make_contact(args, is_primary_contact=1): contact.add_email(args.get("email_id"), is_primary=True) if args.get("mobile_no"): contact.add_phone(args.get("mobile_no"), is_primary_mobile_no=True) - contact.insert() + + if flags := args.get("flags"): + contact.insert(ignore_permissions=flags.get("ignore_permissions")) + else: + contact.insert() return contact -def make_address(args, is_primary_address=1): +def make_address(args, is_primary_address=1, is_shipping_address=1): reqd_fields = [] for field in ["city", "country"]: if not args.get(field): @@ -732,16 +736,23 @@ def make_address(args, is_primary_address=1): address = frappe.get_doc( { "doctype": "Address", - "address_title": args.get("name"), + "address_title": args.get("customer_name"), "address_line1": args.get("address_line1"), "address_line2": args.get("address_line2"), "city": args.get("city"), "state": args.get("state"), "pincode": args.get("pincode"), "country": args.get("country"), + "is_primary_address": is_primary_address, + "is_shipping_address": is_shipping_address, "links": [{"link_doctype": args.get("doctype"), "link_name": args.get("name")}], } - ).insert() + ) + + if flags := args.get("flags"): + address.insert(ignore_permissions=flags.get("ignore_permissions")) + else: + address.insert() return address From 9836205725ae8891a76a462937700322177fe1dd Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 15 Dec 2023 11:46:12 +0530 Subject: [PATCH 19/37] fix: validation error on reconciling PE to Journals as Invoice With the same exchange rate on Journal Entry and Payment Entry, reconcilition should not post exc gain/loss journal and should not throw validation error (cherry picked from commit 5eeb650dfd3ab4b20f49bacefc43d98cb856c3f3) --- erpnext/accounts/utils.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index c98b8193538..52b0c34673a 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -626,8 +626,10 @@ def update_reference_in_payment_entry( "total_amount": d.grand_total, "outstanding_amount": d.outstanding_amount, "allocated_amount": d.allocated_amount, - "exchange_rate": d.exchange_rate if d.exchange_gain_loss else payment_entry.get_exchange_rate(), - "exchange_gain_loss": d.exchange_gain_loss, + "exchange_rate": d.exchange_rate + if d.difference_amount is not None + else payment_entry.get_exchange_rate(), + "exchange_gain_loss": d.difference_amount, } if d.voucher_detail_no: From e05b23c38825039c85648557c572c26a3fc913a4 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Fri, 15 Dec 2023 15:32:50 +0530 Subject: [PATCH 20/37] fix: wrong currency in Stock Balance report (backport #38778) (#38779) fix: wrong currency in Stock Balance report (cherry picked from commit 5a83a16e60b2c47f8ef0307edcd0311824007c86) Co-authored-by: s-aga-r --- erpnext/stock/report/stock_balance/stock_balance.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/report/stock_balance/stock_balance.py b/erpnext/stock/report/stock_balance/stock_balance.py index 80bf8508cf3..7a5a8615d0c 100644 --- a/erpnext/stock/report/stock_balance/stock_balance.py +++ b/erpnext/stock/report/stock_balance/stock_balance.py @@ -397,7 +397,7 @@ class StockBalanceReport(object): "fieldname": "bal_val", "fieldtype": "Currency", "width": 100, - "options": "currency", + "options": "Company:company:default_currency", }, { "label": _("Opening Qty"), @@ -411,7 +411,7 @@ class StockBalanceReport(object): "fieldname": "opening_val", "fieldtype": "Currency", "width": 110, - "options": "currency", + "options": "Company:company:default_currency", }, { "label": _("In Qty"), From d593f81a1604e408128f7c031d11bf21ffa42a13 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 12 Dec 2023 15:18:54 +0530 Subject: [PATCH 21/37] fix: Init internal child table values (cherry picked from commit 2588970d5576bbfa085a1f30cf098d7e18e71a84) --- erpnext/controllers/accounts_controller.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index abe5d688c3b..d6fb276ae33 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -165,6 +165,7 @@ class AccountsController(TransactionBase): self.disable_pricing_rule_on_internal_transfer() self.disable_tax_included_prices_for_internal_transfer() self.set_incoming_rate() + self.init_internal_values() if self.meta.get_field("currency"): self.calculate_taxes_and_totals() @@ -224,6 +225,16 @@ class AccountsController(TransactionBase): self.set_total_in_words() + def init_internal_values(self): + # init all the internal values as 0 on sa + if self.docstatus.is_draft(): + # TODO: Add all such pending values here + fields = ["billed_amt", "delivered_qty"] + for item in self.get("items"): + for field in fields: + if hasattr(item, field): + item.set(field, 0) + def before_cancel(self): validate_einvoice_fields(self) From 942f34a73456530ce6bd3723ed7adb10cbb83731 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Fri, 15 Dec 2023 22:02:29 +0530 Subject: [PATCH 22/37] fix: Reset SLA on issue doesn't work (backport #38789) (#38790) fix: Reset SLA on issue doesn't work (#38789) This was broken since last refactor where it was spun off to work with all types of doctypes but client side code was never adapted. (cherry picked from commit fa1c7b663c2e3f433190d29017eaebbe15d4c604) Co-authored-by: Ankush Menat --- erpnext/support/doctype/issue/issue.js | 4 +++- .../service_level_agreement/service_level_agreement.py | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/erpnext/support/doctype/issue/issue.js b/erpnext/support/doctype/issue/issue.js index f96823b2908..9f91dc1726d 100644 --- a/erpnext/support/doctype/issue/issue.js +++ b/erpnext/support/doctype/issue/issue.js @@ -58,7 +58,9 @@ frappe.ui.form.on("Issue", { frappe.call("erpnext.support.doctype.service_level_agreement.service_level_agreement.reset_service_level_agreement", { reason: values.reason, - user: frappe.session.user_email + user: frappe.session.user_email, + doctype: frm.doc.doctype, + docname: frm.doc.name, }, () => { reset_sla.enable_primary_action(); frm.refresh(); diff --git a/erpnext/support/doctype/service_level_agreement/service_level_agreement.py b/erpnext/support/doctype/service_level_agreement/service_level_agreement.py index 2a023e09c49..54d3c31cd80 100644 --- a/erpnext/support/doctype/service_level_agreement/service_level_agreement.py +++ b/erpnext/support/doctype/service_level_agreement/service_level_agreement.py @@ -739,10 +739,12 @@ def get_response_and_resolution_duration(doc): return priority -def reset_service_level_agreement(doc, reason, user): +@frappe.whitelist() +def reset_service_level_agreement(doctype: str, docname: str, reason, user): if not frappe.db.get_single_value("Support Settings", "allow_resetting_service_level_agreement"): frappe.throw(_("Allow Resetting Service Level Agreement from Support Settings.")) + doc = frappe.get_doc(doctype, docname) frappe.get_doc( { "doctype": "Comment", From 24ae45ce84b98e68759fb285b9856dc4f1ac72f7 Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Sun, 17 Dec 2023 13:59:22 +0530 Subject: [PATCH 23/37] fix: fetch item_tax_template values if fields with fetch_from exisit --- erpnext/controllers/accounts_controller.py | 1 + erpnext/public/js/controllers/transaction.js | 1 + erpnext/stock/get_item_details.py | 4 ++++ 3 files changed, 6 insertions(+) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index d6fb276ae33..47aba077b49 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -640,6 +640,7 @@ class AccountsController(TransactionBase): args["doctype"] = self.doctype args["name"] = self.name + args["child_doctype"] = item.doctype args["child_docname"] = item.name args["ignore_pricing_rule"] = ( self.ignore_pricing_rule if hasattr(self, "ignore_pricing_rule") else 0 diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 050b9dcd3db..0f291e1eba7 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -482,6 +482,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe cost_center: item.cost_center, tax_category: me.frm.doc.tax_category, item_tax_template: item.item_tax_template, + child_doctype: item.doctype, child_docname: item.name, is_old_subcontracting_flow: me.frm.doc.is_old_subcontracting_flow, } diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index e47971d03e8..10d3ef4b7a9 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -8,6 +8,7 @@ import frappe from frappe import _, throw from frappe.model import child_table_fields, default_fields from frappe.model.meta import get_field_precision +from frappe.model.utils import get_fetch_values from frappe.query_builder.functions import CombineDatetime, IfNull, Sum from frappe.utils import add_days, add_months, cint, cstr, flt, getdate @@ -608,6 +609,9 @@ def get_item_tax_template(args, item, out): item_tax_template = _get_item_tax_template(args, item_group_doc.taxes, out) item_group = item_group_doc.parent_item_group + if args.get("child_doctype") and item_tax_template: + out.update(get_fetch_values(args.get("child_doctype"), "item_tax_template", item_tax_template)) + def _get_item_tax_template(args, taxes, out=None, for_validate=False): if out is None: From fb5090fd3f23ada507fe8abc5a899f4b06e48d7e Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Mon, 18 Dec 2023 14:11:36 +0530 Subject: [PATCH 24/37] fix: not able to cancel SCR with Batch (#38817) --- .../subcontracting_receipt.py | 2 +- .../test_subcontracting_receipt.py | 73 +++++++++++++++++++ 2 files changed, 74 insertions(+), 1 deletion(-) diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py index 7b679d907e8..b84cbac4843 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py @@ -111,13 +111,13 @@ class SubcontractingReceipt(SubcontractingController): self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry", "Repost Item Valuation") self.update_status_updater_args() self.update_prevdoc_status() - self.delete_auto_created_batches() self.set_consumed_qty_in_subcontract_order() self.set_subcontracting_order_status() self.update_stock_ledger() self.make_gl_entries_on_cancel() self.repost_future_sle_and_gle() self.update_status() + self.delete_auto_created_batches() @frappe.whitelist() def set_missing_values(self): diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py b/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py index 6c962531dfa..b05ed755c7f 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py @@ -655,6 +655,79 @@ class TestSubcontractingReceipt(FrappeTestCase): self.assertEqual(rm_item.rate, 100) self.assertEqual(rm_item.amount, rm_item.consumed_qty * rm_item.rate) + def test_subcontracting_receipt_cancel_with_batch(self): + from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom + + # Step - 1: Set Backflush Based On as "BOM" + set_backflush_based_on("BOM") + + # Step - 2: Create FG and RM Items + fg_item = make_item( + properties={"is_stock_item": 1, "is_sub_contracted_item": 1, "has_batch_no": 1} + ).name + rm_item1 = make_item(properties={"is_stock_item": 1}).name + rm_item2 = make_item(properties={"is_stock_item": 1}).name + make_item("Subcontracted Service Item Test For Batch 1", {"is_stock_item": 0}) + + # Step - 3: Create BOM for FG Item + bom = make_bom(item=fg_item, raw_materials=[rm_item1, rm_item2]) + for rm_item in bom.items: + self.assertEqual(rm_item.rate, 0) + self.assertEqual(rm_item.amount, 0) + bom = bom.name + + # Step - 4: Create PO and SCO + service_items = [ + { + "warehouse": "_Test Warehouse - _TC", + "item_code": "Subcontracted Service Item Test For Batch 1", + "qty": 100, + "rate": 100, + "fg_item": fg_item, + "fg_item_qty": 100, + }, + ] + sco = get_subcontracting_order(service_items=service_items) + for rm_item in sco.supplied_items: + self.assertEqual(rm_item.rate, 0) + self.assertEqual(rm_item.amount, 0) + + # Step - 5: Inward Raw Materials + rm_items = get_rm_items(sco.supplied_items) + for rm_item in rm_items: + rm_item["rate"] = 100 + itemwise_details = make_stock_in_entry(rm_items=rm_items) + + # Step - 6: Transfer RM's to Subcontractor + se = make_stock_transfer_entry( + sco_no=sco.name, + rm_items=rm_items, + itemwise_details=copy.deepcopy(itemwise_details), + ) + for item in se.items: + self.assertEqual(item.qty, 100) + self.assertEqual(item.basic_rate, 100) + self.assertEqual(item.amount, item.qty * item.basic_rate) + + batch_doc = frappe.get_doc( + { + "doctype": "Batch", + "item": fg_item, + "batch_id": frappe.generate_hash(length=10), + } + ).insert(ignore_permissions=True) + + # Step - 7: Create Subcontracting Receipt + scr = make_subcontracting_receipt(sco.name) + scr.items[0].batch_no = batch_doc.batch_id + scr.save() + scr.submit() + scr.load_from_db() + + # Step - 8: Cancel Subcontracting Receipt + scr.cancel() + self.assertTrue(scr.docstatus == 2) + def make_return_subcontracting_receipt(**args): args = frappe._dict(args) From e18dc5cea37302aec374bb34cbd980a01ced6b35 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Mon, 18 Dec 2023 15:59:56 +0530 Subject: [PATCH 25/37] fix: incorrect limit (#38818) --- erpnext/stock/doctype/pick_list/pick_list.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/erpnext/stock/doctype/pick_list/pick_list.py b/erpnext/stock/doctype/pick_list/pick_list.py index e89e22ab336..3c5384d4a88 100644 --- a/erpnext/stock/doctype/pick_list/pick_list.py +++ b/erpnext/stock/doctype/pick_list/pick_list.py @@ -13,7 +13,7 @@ from frappe.model.mapper import map_child_doc from frappe.query_builder import Case from frappe.query_builder.custom import GROUP_CONCAT from frappe.query_builder.functions import Coalesce, IfNull, Locate, Replace, Sum -from frappe.utils import cint, floor, flt, today +from frappe.utils import ceil, cint, floor, flt, today from frappe.utils.nestedset import get_descendants_of from erpnext.selling.doctype.sales_order.sales_order import ( @@ -605,7 +605,7 @@ def get_available_item_locations_for_serialized_item( .select(sn.name, sn.warehouse) .where((sn.item_code == item_code) & (sn.company == company)) .orderby(sn.purchase_date) - .limit(cint(required_qty + total_picked_qty)) + .limit(ceil(required_qty + total_picked_qty)) ) if from_warehouses: @@ -647,7 +647,7 @@ def get_available_item_locations_for_batched_item( .groupby(sle.warehouse, sle.batch_no, sle.item_code) .having(Sum(sle.actual_qty) > 0) .orderby(IfNull(batch.expiry_date, "2200-01-01"), batch.creation, sle.batch_no, sle.warehouse) - .limit(cint(required_qty + total_picked_qty)) + .limit(ceil(required_qty + total_picked_qty)) ) if from_warehouses: @@ -680,7 +680,7 @@ def get_available_item_locations_for_serial_and_batched_item( (conditions) & (sn.batch_no == location.batch_no) & (sn.warehouse == location.warehouse) ) .orderby(sn.purchase_date) - .limit(cint(location.qty + total_picked_qty)) + .limit(ceil(location.qty + total_picked_qty)) ).run(as_dict=True) serial_nos = [sn.name for sn in serial_nos] @@ -699,7 +699,7 @@ def get_available_item_locations_for_other_item( .select(bin.warehouse, bin.actual_qty.as_("qty")) .where((bin.item_code == item_code) & (bin.actual_qty > 0)) .orderby(bin.creation) - .limit(cint(required_qty + total_picked_qty)) + .limit(ceil(required_qty + total_picked_qty)) ) if from_warehouses: From 6478c80359cf7d6fd9c83c98466016f6d0dc5cbe Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 18 Dec 2023 13:39:26 +0530 Subject: [PATCH 26/37] refactor: ignore ERR journals in Statment of Accounts (cherry picked from commit 39ef75e2d0f18aa8d7434297be596b9223aa7910) --- .../process_statement_of_accounts.json | 9 ++++++++- .../process_statement_of_accounts.py | 14 ++++++++++++++ .../report/general_ledger/general_ledger.py | 3 +++ 3 files changed, 25 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.json b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.json index e711ae0de2b..bdbb1419784 100644 --- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.json +++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.json @@ -15,6 +15,7 @@ "group_by", "cost_center", "territory", + "ignore_exchange_rate_revaluation_journals", "column_break_14", "to_date", "finance_book", @@ -374,10 +375,16 @@ "fieldname": "pdf_name", "fieldtype": "Data", "label": "PDF Name" + }, + { + "default": "0", + "fieldname": "ignore_exchange_rate_revaluation_journals", + "fieldtype": "Check", + "label": "Ignore Exchange Rate Revaluation Journals" } ], "links": [], - "modified": "2023-08-28 12:59:53.071334", + "modified": "2023-12-18 12:20:08.965120", "modified_by": "Administrator", "module": "Accounts", "name": "Process Statement Of Accounts", diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py index b7d6827f64c..d4b4b37b4ee 100644 --- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py +++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py @@ -64,6 +64,18 @@ def get_statement_dict(doc, get_statement_dict=False): statement_dict = {} ageing = "" + err_journals = None + if doc.report == "General Ledger" and doc.ignore_exchange_rate_revaluation_journals: + err_journals = frappe.db.get_all( + "Journal Entry", + filters={ + "company": doc.company, + "docstatus": 1, + "voucher_type": ("in", ["Exchange Rate Revaluation", "Exchange Gain Or Loss"]), + }, + as_list=True, + ) + for entry in doc.customers: if doc.include_ageing: ageing = set_ageing(doc, entry) @@ -76,6 +88,8 @@ def get_statement_dict(doc, get_statement_dict=False): ) filters = get_common_filters(doc) + if err_journals: + filters.update({"voucher_no_not_in": [x[0] for x in err_journals]}) if doc.report == "General Ledger": filters.update(get_gl_filters(doc, entry, tax_id, presentation_currency)) diff --git a/erpnext/accounts/report/general_ledger/general_ledger.py b/erpnext/accounts/report/general_ledger/general_ledger.py index 759bb71ab24..95397452b01 100644 --- a/erpnext/accounts/report/general_ledger/general_ledger.py +++ b/erpnext/accounts/report/general_ledger/general_ledger.py @@ -231,6 +231,9 @@ def get_conditions(filters): if filters.get("voucher_no"): conditions.append("voucher_no=%(voucher_no)s") + if filters.get("voucher_no_not_in"): + conditions.append("voucher_no not in %(voucher_no_not_in)s") + if filters.get("group_by") == "Group by Party" and not filters.get("party_type"): conditions.append("party_type in ('Customer', 'Supplier')") From 23042dfc3c0d02374c5710ed679731b1910f9b9a Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Mon, 18 Dec 2023 17:44:17 +0530 Subject: [PATCH 27/37] fix: not able to make inter-company po from so (#38826) --- .../accounts/doctype/sales_invoice/sales_invoice.py | 11 ++++++++++- erpnext/controllers/buying_controller.py | 6 +++++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 434662c298b..3d18a860361 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -2195,9 +2195,18 @@ def make_inter_company_transaction(doctype, source_name, target_doc=None): def get_received_items(reference_name, doctype, reference_fieldname): + reference_field = "inter_company_invoice_reference" + if doctype == "Purchase Order": + reference_field = "inter_company_order_reference" + + filters = { + reference_field: reference_name, + "docstatus": 1, + } + target_doctypes = frappe.get_all( doctype, - filters={"inter_company_invoice_reference": reference_name, "docstatus": 1}, + filters=filters, as_list=True, ) diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py index 81736915f55..8e9b2289079 100644 --- a/erpnext/controllers/buying_controller.py +++ b/erpnext/controllers/buying_controller.py @@ -354,7 +354,11 @@ class BuyingController(SubcontractingController): rate = flt(outgoing_rate * (d.conversion_factor or 1), d.precision("rate")) else: - field = "incoming_rate" if self.get("is_internal_supplier") else "rate" + field = ( + "incoming_rate" + if self.get("is_internal_supplier") and not self.doctype == "Purchase Order" + else "rate" + ) rate = flt( frappe.db.get_value(ref_doctype, d.get(frappe.scrub(ref_doctype)), field) * (d.conversion_factor or 1), From 4114760efd0628a2502cff4f4e0a81c54e4eeeb4 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 18 Dec 2023 18:10:54 +0530 Subject: [PATCH 28/37] perf: index `return_against` on delivery note (backport #38827) (#38831) * perf: index `return_against` on delivery note (#38827) There's a multi-column index but that's useful IFF all parts of column are part of query. return against on it's own is VERY unique because it's a primary key, we don't need a multi-column index here. (cherry picked from commit 8d79365e0ddef42e788f97c668a64c479a803617) # Conflicts: # erpnext/stock/doctype/purchase_receipt/purchase_receipt.json * chore: conflict --------- Co-authored-by: Ankush Menat --- erpnext/patches.txt | 1 + .../doctype/delivery_note/delivery_note.json | 5 +++-- .../stock/doctype/delivery_note/delivery_note.py | 4 ---- .../doctype/delivery_note/patches/__init__.py | 0 .../patches/drop_unused_return_against_index.py | 15 +++++++++++++++ .../purchase_receipt/purchase_receipt.json | 5 +++-- .../doctype/purchase_receipt/purchase_receipt.py | 4 ---- 7 files changed, 22 insertions(+), 12 deletions(-) create mode 100644 erpnext/stock/doctype/delivery_note/patches/__init__.py create mode 100644 erpnext/stock/doctype/delivery_note/patches/drop_unused_return_against_index.py diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 279610e56c6..d96f45fd676 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -354,3 +354,4 @@ execute:frappe.db.set_single_value("Buying Settings", "project_update_frequency" erpnext.patches.v14_0.clear_reconciliation_values_from_singles # below migration patch should always run last erpnext.patches.v14_0.migrate_gl_to_payment_ledger +erpnext.stock.doctype.delivery_note.patches.drop_unused_return_against_index diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.json b/erpnext/stock/doctype/delivery_note/delivery_note.json index 11f2cafc35d..5731bda495e 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.json +++ b/erpnext/stock/doctype/delivery_note/delivery_note.json @@ -301,7 +301,8 @@ "no_copy": 1, "options": "Delivery Note", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "search_index": 1 }, { "collapsible": 1, @@ -1400,7 +1401,7 @@ "idx": 146, "is_submittable": 1, "links": [], - "modified": "2023-09-04 14:15:28.363184", + "modified": "2023-12-18 17:19:39.368239", "modified_by": "Administrator", "module": "Stock", "name": "Delivery Note", diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.py b/erpnext/stock/doctype/delivery_note/delivery_note.py index d9d9a52482a..4216ca0cdc3 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/delivery_note.py @@ -997,7 +997,3 @@ def make_inter_company_transaction(doctype, source_name, target_doc=None): ) return doclist - - -def on_doctype_update(): - frappe.db.add_index("Delivery Note", ["customer", "is_return", "return_against"]) diff --git a/erpnext/stock/doctype/delivery_note/patches/__init__.py b/erpnext/stock/doctype/delivery_note/patches/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/stock/doctype/delivery_note/patches/drop_unused_return_against_index.py b/erpnext/stock/doctype/delivery_note/patches/drop_unused_return_against_index.py new file mode 100644 index 00000000000..8fe4ffb58f1 --- /dev/null +++ b/erpnext/stock/doctype/delivery_note/patches/drop_unused_return_against_index.py @@ -0,0 +1,15 @@ +import frappe + + +def execute(): + """Drop unused return_against index""" + + try: + frappe.db.sql_ddl( + "ALTER TABLE `tabDelivery Note` DROP INDEX `customer_is_return_return_against_index`" + ) + frappe.db.sql_ddl( + "ALTER TABLE `tabPurchase Receipt` DROP INDEX `supplier_is_return_return_against_index`" + ) + except Exception: + frappe.log_error("Failed to drop unused index") diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json index aae1bad0977..de1263d8f66 100755 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json @@ -288,7 +288,8 @@ "no_copy": 1, "options": "Purchase Receipt", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "search_index": 1 }, { "fieldname": "section_addresses", @@ -1241,7 +1242,7 @@ "idx": 261, "is_submittable": 1, "links": [], - "modified": "2023-10-01 21:00:44.556816", + "modified": "2023-12-18 17:26:41.279663", "modified_by": "Administrator", "module": "Stock", "name": "Purchase Receipt", diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index c12048077e7..f35dc136990 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -1199,10 +1199,6 @@ def get_item_account_wise_additional_cost(purchase_document): return item_account_wise_cost -def on_doctype_update(): - frappe.db.add_index("Purchase Receipt", ["supplier", "is_return", "return_against"]) - - @erpnext.allow_regional def update_regional_gl_entries(gl_list, doc): return From 77da0da945583e153ef2b7b5a55e28a322d072d3 Mon Sep 17 00:00:00 2001 From: Dany Robert Date: Sat, 16 Dec 2023 04:35:43 +0000 Subject: [PATCH 29/37] fix: wrong paid and cn amount on pos invoice (cherry picked from commit 5cb5e09dbbac878906023c07423d5d8233279790) --- .../report/accounts_receivable/accounts_receivable.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py index f1abc1d4ddb..e4d5938c0b5 100755 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py @@ -244,8 +244,12 @@ class ReceivablePayableReport(object): row.invoiced_in_account_currency += amount_in_account_currency else: if self.is_invoice(ple): - row.credit_note -= amount - row.credit_note_in_account_currency -= amount_in_account_currency + if row.voucher_no == ple.voucher_no == ple.against_voucher_no: + row.paid -= amount + row.paid_in_account_currency -= amount_in_account_currency + else: + row.credit_note -= amount + row.credit_note_in_account_currency -= amount_in_account_currency else: row.paid -= amount row.paid_in_account_currency -= amount_in_account_currency From 939b7f7cbf91c87663eafdffce0cd8e7be0736db Mon Sep 17 00:00:00 2001 From: Dany Robert Date: Sat, 16 Dec 2023 05:34:43 +0000 Subject: [PATCH 30/37] test: partial payment for pos invoice (cherry picked from commit 877262891235b21447c5b74684fb7173910427e1) --- .../test_accounts_receivable.py | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py index f83285a1a72..77f8c6eaaa9 100644 --- a/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py @@ -76,6 +76,41 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase): return credit_note + def test_pos_receivable(self): + filters = { + "company": self.company, + "party_type": "Customer", + "party": [self.customer], + "report_date": add_days(today(), 2), + "based_on_payment_terms": 0, + "range1": 30, + "range2": 60, + "range3": 90, + "range4": 120, + "show_remarks": False, + } + + pos_inv = self.create_sales_invoice(no_payment_schedule=True, do_not_submit=True) + pos_inv.posting_date = add_days(today(), 2) + pos_inv.is_pos = 1 + pos_inv.append( + "payments", + frappe._dict( + mode_of_payment="Cash", + amount=flt(pos_inv.grand_total / 2), + ), + ) + pos_inv.disable_rounded_total = 1 + pos_inv.save() + pos_inv.submit() + + report = execute(filters) + expected_data = [[pos_inv.grand_total, pos_inv.paid_amount, 0]] + + row = report[1][-1] + self.assertEqual(expected_data[0], [row.invoiced, row.paid, row.credit_note]) + pos_inv.cancel() + def test_accounts_receivable(self): filters = { "company": self.company, From e0c8ff10daeed0829266aea9142805f68ceedb2b Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Tue, 19 Dec 2023 12:45:08 +0530 Subject: [PATCH 31/37] fix: item variant with manufacturer (#38845) --- erpnext/controllers/item_variant.py | 20 +++++++++-- erpnext/stock/doctype/item/test_item.py | 44 +++++++++---------------- 2 files changed, 32 insertions(+), 32 deletions(-) diff --git a/erpnext/controllers/item_variant.py b/erpnext/controllers/item_variant.py index e68ee909d9f..1ba10259a8e 100644 --- a/erpnext/controllers/item_variant.py +++ b/erpnext/controllers/item_variant.py @@ -52,10 +52,24 @@ def make_variant_based_on_manufacturer(template, manufacturer, manufacturer_part copy_attributes_to_variant(template, variant) - variant.manufacturer = manufacturer - variant.manufacturer_part_no = manufacturer_part_no - variant.item_code = append_number_if_name_exists("Item", template.name) + variant.flags.ignore_mandatory = True + variant.save() + + if not frappe.db.exists( + "Item Manufacturer", {"item_code": variant.name, "manufacturer": manufacturer} + ): + manufacturer_doc = frappe.new_doc("Item Manufacturer") + manufacturer_doc.update( + { + "item_code": variant.name, + "manufacturer": manufacturer, + "manufacturer_part_no": manufacturer_part_no, + } + ) + + manufacturer_doc.flags.ignore_mandatory = True + manufacturer_doc.save(ignore_permissions=True) return variant diff --git a/erpnext/stock/doctype/item/test_item.py b/erpnext/stock/doctype/item/test_item.py index 9aa66a9a1ec..22015e8a65d 100644 --- a/erpnext/stock/doctype/item/test_item.py +++ b/erpnext/stock/doctype/item/test_item.py @@ -522,39 +522,25 @@ class TestItem(FrappeTestCase): self.assertEqual(factor, 1.0) def test_item_variant_by_manufacturer(self): - fields = [{"field_name": "description"}, {"field_name": "variant_based_on"}] - set_item_variant_settings(fields) + template = make_item( + "_Test Item Variant By Manufacturer", {"has_variants": 1, "variant_based_on": "Manufacturer"} + ) - if frappe.db.exists("Item", "_Test Variant Mfg"): - frappe.delete_doc("Item", "_Test Variant Mfg") - if frappe.db.exists("Item", "_Test Variant Mfg-1"): - frappe.delete_doc("Item", "_Test Variant Mfg-1") - if frappe.db.exists("Manufacturer", "MSG1"): - frappe.delete_doc("Manufacturer", "MSG1") + for manufacturer in ["DFSS", "DASA", "ASAAS"]: + if not frappe.db.exists("Manufacturer", manufacturer): + m_doc = frappe.new_doc("Manufacturer") + m_doc.short_name = manufacturer + m_doc.insert() - template = frappe.get_doc( - dict( - doctype="Item", - item_code="_Test Variant Mfg", - has_variant=1, - item_group="Products", - variant_based_on="Manufacturer", - ) - ).insert() + self.assertFalse(frappe.db.exists("Item Manufacturer", {"manufacturer": "DFSS"})) + variant = get_variant(template, manufacturer="DFSS", manufacturer_part_no="DFSS-123") - manufacturer = frappe.get_doc(dict(doctype="Manufacturer", short_name="MSG1")).insert() + item_manufacturer = frappe.db.exists( + "Item Manufacturer", {"manufacturer": "DFSS", "item_code": variant.name} + ) + self.assertTrue(item_manufacturer) - variant = get_variant(template.name, manufacturer=manufacturer.name) - self.assertEqual(variant.item_code, "_Test Variant Mfg-1") - self.assertEqual(variant.description, "_Test Variant Mfg") - self.assertEqual(variant.manufacturer, "MSG1") - variant.insert() - - variant = get_variant(template.name, manufacturer=manufacturer.name, manufacturer_part_no="007") - self.assertEqual(variant.item_code, "_Test Variant Mfg-2") - self.assertEqual(variant.description, "_Test Variant Mfg") - self.assertEqual(variant.manufacturer, "MSG1") - self.assertEqual(variant.manufacturer_part_no, "007") + frappe.delete_doc("Item Manufacturer", item_manufacturer) def test_stock_exists_against_template_item(self): stock_item = frappe.get_all("Stock Ledger Entry", fields=["item_code"], limit=1) From 857843bc29628122ab1a5fd1618bab5b0ea5128a Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Tue, 19 Dec 2023 15:54:29 +0530 Subject: [PATCH 32/37] chore: fix test case (#38856) --- erpnext/stock/doctype/item/test_item.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/item/test_item.py b/erpnext/stock/doctype/item/test_item.py index 22015e8a65d..7c665b973d8 100644 --- a/erpnext/stock/doctype/item/test_item.py +++ b/erpnext/stock/doctype/item/test_item.py @@ -524,7 +524,7 @@ class TestItem(FrappeTestCase): def test_item_variant_by_manufacturer(self): template = make_item( "_Test Item Variant By Manufacturer", {"has_variants": 1, "variant_based_on": "Manufacturer"} - ) + ).name for manufacturer in ["DFSS", "DASA", "ASAAS"]: if not frappe.db.exists("Manufacturer", manufacturer): From 2184e8ef58379f53ef8f1d069afa26e64796b073 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Tue, 19 Dec 2023 17:20:41 +0530 Subject: [PATCH 33/37] fix: on closed unreserved the production plan qty (#38848) --- .../production_plan/production_plan.py | 4 ++ .../production_plan/test_production_plan.py | 41 +++++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py index 7778f060146..b546594a4ed 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -508,6 +508,7 @@ class ProductionPlan(Document): if close: self.db_set("status", "Closed") + self.update_bin_qty() return if self.total_produced_qty > 0: @@ -522,6 +523,9 @@ class ProductionPlan(Document): if close is not None: self.db_set("status", self.status) + if self.docstatus == 1 and self.status != "Completed": + self.update_bin_qty() + def update_ordered_status(self): update_status = False for d in self.po_items: diff --git a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py index 6a50a10d07a..d546306f080 100644 --- a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py @@ -1447,6 +1447,47 @@ class TestProductionPlan(FrappeTestCase): self.assertEqual(row.get("uom"), "Nos") self.assertEqual(row.get("conversion_factor"), 10.0) + def test_unreserve_qty_on_closing_of_pp(self): + from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse + from erpnext.stock.utils import get_or_make_bin + + fg_item = make_item(properties={"is_stock_item": 1, "stock_uom": "_Test UOM 1"}).name + rm_item = make_item(properties={"is_stock_item": 1, "stock_uom": "_Test UOM 1"}).name + + store_warehouse = create_warehouse("Store Warehouse", company="_Test Company") + rm_warehouse = create_warehouse("RM Warehouse", company="_Test Company") + + make_bom(item=fg_item, raw_materials=[rm_item], source_warehouse="_Test Warehouse - _TC") + + pln = create_production_plan( + item_code=fg_item, planned_qty=10, stock_uom="_Test UOM 1", do_not_submit=1 + ) + + pln.for_warehouse = rm_warehouse + mr_items = get_items_for_material_requests(pln.as_dict()) + for d in mr_items: + pln.append("mr_items", d) + + pln.save() + pln.submit() + + bin_name = get_or_make_bin(rm_item, rm_warehouse) + before_qty = flt(frappe.db.get_value("Bin", bin_name, "reserved_qty_for_production_plan")) + + pln.reload() + pln.set_status(close=True) + + bin_name = get_or_make_bin(rm_item, rm_warehouse) + after_qty = flt(frappe.db.get_value("Bin", bin_name, "reserved_qty_for_production_plan")) + self.assertAlmostEqual(after_qty, before_qty - 10) + + pln.reload() + pln.set_status(close=False) + + bin_name = get_or_make_bin(rm_item, rm_warehouse) + after_qty = flt(frappe.db.get_value("Bin", bin_name, "reserved_qty_for_production_plan")) + self.assertAlmostEqual(after_qty, before_qty) + def create_production_plan(**args): """ From 7fdac62393ee1e96969cca38a4ce0c07993dce7e Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Tue, 19 Dec 2023 18:01:50 +0530 Subject: [PATCH 34/37] fix: incoming rate for sales return with Moving Average valuation method (#38849) --- erpnext/controllers/selling_controller.py | 8 +-- .../delivery_note/test_delivery_note.py | 50 +++++++++++++++++++ 2 files changed, 55 insertions(+), 3 deletions(-) diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py index 7cef623148e..bac94f98af4 100644 --- a/erpnext/controllers/selling_controller.py +++ b/erpnext/controllers/selling_controller.py @@ -12,7 +12,7 @@ from erpnext.controllers.sales_and_purchase_return import get_rate_for_return from erpnext.controllers.stock_controller import StockController from erpnext.stock.doctype.item.item import set_item_default from erpnext.stock.get_item_details import get_bin_details, get_conversion_factor -from erpnext.stock.utils import get_incoming_rate +from erpnext.stock.utils import get_incoming_rate, get_valuation_method class SellingController(StockController): @@ -422,11 +422,13 @@ class SellingController(StockController): items = self.get("items") + (self.get("packed_items") or []) for d in items: - if not self.get("return_against"): + if not self.get("return_against") or ( + get_valuation_method(d.item_code) == "Moving Average" and self.get("is_return") + ): # Get incoming rate based on original item cost based on valuation method qty = flt(d.get("stock_qty") or d.get("actual_qty")) - if not (self.get("is_return") and d.incoming_rate): + if not d.incoming_rate: d.incoming_rate = get_incoming_rate( { "item_code": d.item_code, diff --git a/erpnext/stock/doctype/delivery_note/test_delivery_note.py b/erpnext/stock/doctype/delivery_note/test_delivery_note.py index 65828f3a4ac..476b4959813 100644 --- a/erpnext/stock/doctype/delivery_note/test_delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/test_delivery_note.py @@ -1332,6 +1332,56 @@ class TestDeliveryNote(FrappeTestCase): dn.reload() self.assertFalse(dn.items[0].target_warehouse) + def test_sales_return_valuation_for_moving_average(self): + item_code = make_item( + "_Test Item Sales Return with MA", {"is_stock_item": 1, "valuation_method": "Moving Average"} + ).name + + make_stock_entry( + item_code=item_code, + target="_Test Warehouse - _TC", + qty=5, + basic_rate=100.0, + posting_date=add_days(nowdate(), -5), + ) + dn = create_delivery_note( + item_code=item_code, qty=5, rate=500, posting_date=add_days(nowdate(), -4) + ) + self.assertEqual(dn.items[0].incoming_rate, 100.0) + + make_stock_entry( + item_code=item_code, + target="_Test Warehouse - _TC", + qty=5, + basic_rate=200.0, + posting_date=add_days(nowdate(), -3), + ) + make_stock_entry( + item_code=item_code, + target="_Test Warehouse - _TC", + qty=5, + basic_rate=300.0, + posting_date=add_days(nowdate(), -2), + ) + + dn1 = create_delivery_note( + is_return=1, + item_code=item_code, + return_against=dn.name, + qty=-5, + rate=500, + company=dn.company, + expense_account="Cost of Goods Sold - _TC", + cost_center="Main - _TC", + do_not_submit=1, + posting_date=add_days(nowdate(), -1), + ) + + # (300 * 5) + (200 * 5) = 2500 + # 2500 / 10 = 250 + + self.assertAlmostEqual(dn1.items[0].incoming_rate, 250.0) + def create_delivery_note(**args): dn = frappe.new_doc("Delivery Note") From d375164100158db9b974742caa3e05062c481d7d Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Tue, 19 Dec 2023 18:19:20 +0530 Subject: [PATCH 35/37] fix: if not budget then don't validate (#38861) --- erpnext/stock/doctype/material_request/material_request.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/material_request/material_request.py b/erpnext/stock/doctype/material_request/material_request.py index 659bc42f0a6..b84ccf770b2 100644 --- a/erpnext/stock/doctype/material_request/material_request.py +++ b/erpnext/stock/doctype/material_request/material_request.py @@ -123,7 +123,9 @@ class MaterialRequest(BuyingController): def on_submit(self): self.update_requested_qty_in_production_plan() self.update_requested_qty() - if self.material_request_type == "Purchase": + if self.material_request_type == "Purchase" and frappe.db.exists( + "Budget", {"applicable_on_material_request": 1, "docstatus": 1} + ): self.validate_budget() def before_save(self): From f7bfbd82dd9eb82780bfad60194a4348ba2f03fa Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 20 Dec 2023 09:14:03 +0530 Subject: [PATCH 36/37] fix(ux): don't override Item Name and Description in MR (backport #38720) (#38762) fix(ux): don't override Item Name and Description in MR (cherry picked from commit 726ac6bda1ee3b25c1d62b312d96aa32466ba11e) Co-authored-by: s-aga-r --- erpnext/stock/doctype/material_request/material_request.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/erpnext/stock/doctype/material_request/material_request.js b/erpnext/stock/doctype/material_request/material_request.js index ec075bb6bad..5922af25879 100644 --- a/erpnext/stock/doctype/material_request/material_request.js +++ b/erpnext/stock/doctype/material_request/material_request.js @@ -199,9 +199,8 @@ frappe.ui.form.on('Material Request', { get_item_data: function(frm, item, overwrite_warehouse=false) { if (item && !item.item_code) { return; } - frm.call({ + frappe.call({ method: "erpnext.stock.get_item_details.get_item_details", - child: item, args: { args: { item_code: item.item_code, From 22b442d593182184cb98a994520109737b8e479c Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 20 Dec 2023 09:14:54 +0530 Subject: [PATCH 37/37] fix: close PO on SCO close (backport #38667) (#38680) fix: close PO on SCO close (cherry picked from commit b023e5d6b3af376a22397c51f4ec8939da463686) Co-authored-by: s-aga-r --- .../doctype/subcontracting_order/subcontracting_order.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.py b/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.py index c0064996099..4ed8a0eab16 100644 --- a/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.py +++ b/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.py @@ -7,6 +7,7 @@ from frappe.model.mapper import get_mapped_doc from frappe.utils import flt from erpnext.buying.doctype.purchase_order.purchase_order import is_subcontracting_order_created +from erpnext.buying.doctype.purchase_order.purchase_order import update_status as update_po_status from erpnext.controllers.subcontracting_controller import SubcontractingController from erpnext.stock.stock_balance import update_bin_qty from erpnext.stock.utils import get_bin @@ -234,6 +235,9 @@ class SubcontractingOrder(SubcontractingController): "Subcontracting Order", self.name, "status", status, update_modified=update_modified ) + if status == "Closed": + update_po_status("Closed", self.purchase_order) + @frappe.whitelist() def make_subcontracting_receipt(source_name, target_doc=None):