From d2686ce75bb39bffff4fd1b56ad4880444efb72e Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 27 Dec 2022 17:42:03 +0530 Subject: [PATCH 01/20] fix: Multiple rows for same warehouse and batches in pick list (#33456) --- erpnext/stock/doctype/pick_list/pick_list.js | 10 ++++++++- erpnext/stock/doctype/pick_list/pick_list.py | 22 +++++++++++++++++++- 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/doctype/pick_list/pick_list.js b/erpnext/stock/doctype/pick_list/pick_list.js index 799406cd79e..8213adb89bf 100644 --- a/erpnext/stock/doctype/pick_list/pick_list.js +++ b/erpnext/stock/doctype/pick_list/pick_list.js @@ -51,7 +51,15 @@ frappe.ui.form.on('Pick List', { if (!(frm.doc.locations && frm.doc.locations.length)) { frappe.msgprint(__('Add items in the Item Locations table')); } else { - frm.call('set_item_locations', {save: save}); + frappe.call({ + method: "set_item_locations", + doc: frm.doc, + args: { + "save": save, + }, + freeze: 1, + freeze_message: __("Setting Item Locations..."), + }); } }, get_item_locations: (frm) => { diff --git a/erpnext/stock/doctype/pick_list/pick_list.py b/erpnext/stock/doctype/pick_list/pick_list.py index 8704b6718b9..953fca7419c 100644 --- a/erpnext/stock/doctype/pick_list/pick_list.py +++ b/erpnext/stock/doctype/pick_list/pick_list.py @@ -135,6 +135,7 @@ class PickList(Document): # reset self.delete_key("locations") + updated_locations = frappe._dict() for item_doc in items: item_code = item_doc.item_code @@ -155,7 +156,26 @@ class PickList(Document): for row in locations: location = item_doc.as_dict() location.update(row) - self.append("locations", location) + key = ( + location.item_code, + location.warehouse, + location.uom, + location.batch_no, + location.serial_no, + location.sales_order_item or location.material_request_item, + ) + + if key not in updated_locations: + updated_locations.setdefault(key, location) + else: + updated_locations[key].qty += location.qty + updated_locations[key].stock_qty += location.stock_qty + + for location in updated_locations.values(): + if location.picked_qty > location.stock_qty: + location.picked_qty = location.stock_qty + + self.append("locations", location) # If table is empty on update after submit, set stock_qty, picked_qty to 0 so that indicator is red # and give feedback to the user. This is to avoid empty Pick Lists. From 0b75aa53907e67d440884a2ea0084044265994a5 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 27 Dec 2022 17:53:43 +0530 Subject: [PATCH 02/20] fix: Default dimensions on fetching items from BOM (#33439) --- erpnext/stock/doctype/stock_entry/stock_entry.js | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js index b9102445e01..d4b4efa4cdd 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.js +++ b/erpnext/stock/doctype/stock_entry/stock_entry.js @@ -112,6 +112,10 @@ frappe.ui.form.on('Stock Entry', { } }); attach_bom_items(frm.doc.bom_no); + + if(!check_should_not_attach_bom_items(frm.doc.bom_no)) { + erpnext.accounts.dimensions.update_dimension(frm, frm.doctype); + } }, setup_quality_inspection: function(frm) { @@ -326,7 +330,11 @@ frappe.ui.form.on('Stock Entry', { } frm.trigger("setup_quality_inspection"); - attach_bom_items(frm.doc.bom_no) + attach_bom_items(frm.doc.bom_no); + + if(!check_should_not_attach_bom_items(frm.doc.bom_no)) { + erpnext.accounts.dimensions.update_dimension(frm, frm.doctype); + } }, before_save: function(frm) { @@ -939,7 +947,10 @@ erpnext.stock.StockEntry = class StockEntry extends erpnext.stock.StockControlle method: "get_items", callback: function(r) { if(!r.exc) refresh_field("items"); - if(me.frm.doc.bom_no) attach_bom_items(me.frm.doc.bom_no) + if(me.frm.doc.bom_no) { + attach_bom_items(me.frm.doc.bom_no); + erpnext.accounts.dimensions.update_dimension(me.frm, me.frm.doctype); + } } }); } From 8e271fd7190447577fa15e1d180c33524576d529 Mon Sep 17 00:00:00 2001 From: Sabu Siyad Date: Wed, 28 Dec 2022 08:11:28 +0530 Subject: [PATCH 03/20] feat(exotel): make use of `CustomField` in API (#33338) * feat(exotel): pass kwargs for `make_a_call` https://developer.exotel.com/api/make-a-call-api#call-agent Signed-off-by: Sabu Siyad * feat(exotel): map custom field to doctype Signed-off-by: Sabu Siyad Signed-off-by: Sabu Siyad --- .../exotel_settings/exotel_settings.json | 34 +++++++++++++++++-- .../exotel_integration.py | 23 +++++++++++-- 2 files changed, 52 insertions(+), 5 deletions(-) diff --git a/erpnext/erpnext_integrations/doctype/exotel_settings/exotel_settings.json b/erpnext/erpnext_integrations/doctype/exotel_settings/exotel_settings.json index 72f47b53ec2..0d42ca8c85d 100644 --- a/erpnext/erpnext_integrations/doctype/exotel_settings/exotel_settings.json +++ b/erpnext/erpnext_integrations/doctype/exotel_settings/exotel_settings.json @@ -1,4 +1,5 @@ { + "actions": [], "creation": "2019-05-21 07:41:53.536536", "doctype": "DocType", "engine": "InnoDB", @@ -7,10 +8,14 @@ "section_break_2", "account_sid", "api_key", - "api_token" + "api_token", + "section_break_6", + "map_custom_field_to_doctype", + "target_doctype" ], "fields": [ { + "default": "0", "fieldname": "enabled", "fieldtype": "Check", "label": "Enabled" @@ -18,7 +23,8 @@ { "depends_on": "enabled", "fieldname": "section_break_2", - "fieldtype": "Section Break" + "fieldtype": "Section Break", + "label": "Credentials" }, { "fieldname": "account_sid", @@ -34,10 +40,31 @@ "fieldname": "api_key", "fieldtype": "Data", "label": "API Key" + }, + { + "depends_on": "enabled", + "fieldname": "section_break_6", + "fieldtype": "Section Break", + "label": "Custom Field" + }, + { + "default": "0", + "fieldname": "map_custom_field_to_doctype", + "fieldtype": "Check", + "label": "Map Custom Field to DocType" + }, + { + "depends_on": "map_custom_field_to_doctype", + "fieldname": "target_doctype", + "fieldtype": "Link", + "label": "Target DocType", + "mandatory_depends_on": "map_custom_field_to_doctype", + "options": "DocType" } ], "issingle": 1, - "modified": "2019-05-22 06:25:18.026997", + "links": [], + "modified": "2022-12-14 17:24:50.176107", "modified_by": "Administrator", "module": "ERPNext Integrations", "name": "Exotel Settings", @@ -57,5 +84,6 @@ "quick_entry": 1, "sort_field": "modified", "sort_order": "ASC", + "states": [], "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/erpnext_integrations/exotel_integration.py b/erpnext/erpnext_integrations/exotel_integration.py index fd0f7835759..0d40667e32a 100644 --- a/erpnext/erpnext_integrations/exotel_integration.py +++ b/erpnext/erpnext_integrations/exotel_integration.py @@ -72,6 +72,24 @@ def get_call_log(call_payload): return frappe.get_doc("Call Log", call_log_id) +def map_custom_field(call_payload, call_log): + field_value = call_payload.get("CustomField") + + if not field_value: + return call_log + + settings = get_exotel_settings() + target_doctype = settings.target_doctype + mapping_enabled = settings.map_custom_field_to_doctype + + if not mapping_enabled or not target_doctype: + return call_log + + call_log.append("links", {"link_doctype": target_doctype, "link_name": field_value}) + + return call_log + + def create_call_log(call_payload): call_log = frappe.new_doc("Call Log") call_log.id = call_payload.get("CallSid") @@ -79,6 +97,7 @@ def create_call_log(call_payload): call_log.medium = call_payload.get("To") call_log.status = "Ringing" setattr(call_log, "from", call_payload.get("CallFrom")) + map_custom_field(call_payload, call_log) call_log.save(ignore_permissions=True) frappe.db.commit() return call_log @@ -93,10 +112,10 @@ def get_call_status(call_id): @frappe.whitelist() -def make_a_call(from_number, to_number, caller_id): +def make_a_call(from_number, to_number, caller_id, **kwargs): endpoint = get_exotel_endpoint("Calls/connect.json?details=true") response = requests.post( - endpoint, data={"From": from_number, "To": to_number, "CallerId": caller_id} + endpoint, data={"From": from_number, "To": to_number, "CallerId": caller_id, **kwargs} ) return response.json() From 6f5824cb213cdda10e225322dd417e0258e0a3ce Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Tue, 27 Dec 2022 14:54:23 +0530 Subject: [PATCH 04/20] fix: `fg_item_qty` in non-subcontracted PO --- .../doctype/purchase_order/purchase_order.py | 53 ++++++++++--------- 1 file changed, 29 insertions(+), 24 deletions(-) diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.py b/erpnext/buying/doctype/purchase_order/purchase_order.py index 4c10b4812e7..5a4168a573e 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/purchase_order.py @@ -207,31 +207,36 @@ class PurchaseOrder(BuyingController): ) def validate_fg_item_for_subcontracting(self): - if self.is_subcontracted and not self.is_old_subcontracting_flow: + if self.is_subcontracted: + if not self.is_old_subcontracting_flow: + for item in self.items: + if not item.fg_item: + frappe.throw( + _("Row #{0}: Finished Good Item is not specified for service item {1}").format( + item.idx, item.item_code + ) + ) + else: + if not frappe.get_value("Item", item.fg_item, "is_sub_contracted_item"): + frappe.throw( + _( + "Row #{0}: Finished Good Item {1} must be a sub-contracted item for service item {2}" + ).format(item.idx, item.fg_item, item.item_code) + ) + elif not frappe.get_value("Item", item.fg_item, "default_bom"): + frappe.throw( + _("Row #{0}: Default BOM not found for FG Item {1}").format(item.idx, item.fg_item) + ) + if not item.fg_item_qty: + frappe.throw( + _("Row #{0}: Finished Good Item Qty is not specified for service item {0}").format( + item.idx, item.item_code + ) + ) + else: for item in self.items: - if not item.fg_item: - frappe.throw( - _("Row #{0}: Finished Good Item is not specified for service item {1}").format( - item.idx, item.item_code - ) - ) - else: - if not frappe.get_value("Item", item.fg_item, "is_sub_contracted_item"): - frappe.throw( - _( - "Row #{0}: Finished Good Item {1} must be a sub-contracted item for service item {2}" - ).format(item.idx, item.fg_item, item.item_code) - ) - elif not frappe.get_value("Item", item.fg_item, "default_bom"): - frappe.throw( - _("Row #{0}: Default BOM not found for FG Item {1}").format(item.idx, item.fg_item) - ) - if not item.fg_item_qty: - frappe.throw( - _("Row #{0}: Finished Good Item Qty is not specified for service item {0}").format( - item.idx, item.item_code - ) - ) + item.set("fg_item", None) + item.set("fg_item_qty", 0) def get_schedule_dates(self): for d in self.get("items"): From cabaed9ed2526e2649d173c806f6987d3377b0c3 Mon Sep 17 00:00:00 2001 From: Dany Robert Date: Wed, 28 Dec 2022 18:05:55 +0530 Subject: [PATCH 05/20] fix(pricing rule): consider child tables in condition (#33469) --- erpnext/accounts/doctype/pricing_rule/utils.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/erpnext/accounts/doctype/pricing_rule/utils.py b/erpnext/accounts/doctype/pricing_rule/utils.py index 3989f8a8ac2..1ce780eac86 100644 --- a/erpnext/accounts/doctype/pricing_rule/utils.py +++ b/erpnext/accounts/doctype/pricing_rule/utils.py @@ -252,10 +252,15 @@ def get_other_conditions(conditions, values, args): if args.get("doctype") in [ "Quotation", + "Quotation Item", "Sales Order", + "Sales Order Item", "Delivery Note", + "Delivery Note Item", "Sales Invoice", + "Sales Invoice Item", "POS Invoice", + "POS Invoice Item", ]: conditions += """ and ifnull(`tabPricing Rule`.selling, 0) = 1""" else: From e3a0ce5d6328d2ef68fb00419775ce113889c133 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 29 Dec 2022 09:34:51 +0530 Subject: [PATCH 06/20] fix: use base_net_amount in case of missing stock qty (#33457) --- .../item_wise_purchase_register.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py b/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py index c04b9c71252..d34c21348c8 100644 --- a/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py +++ b/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py @@ -53,9 +53,6 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum item_details = get_item_details() for d in item_list: - if not d.stock_qty: - continue - item_record = item_details.get(d.item_code) purchase_receipt = None @@ -94,7 +91,7 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum "expense_account": expense_account, "stock_qty": d.stock_qty, "stock_uom": d.stock_uom, - "rate": d.base_net_amount / d.stock_qty, + "rate": d.base_net_amount / d.stock_qty if d.stock_qty else d.base_net_amount, "amount": d.base_net_amount, } ) From 123920d0bc7b1d3c0e465d09d569ed2d08687a0b Mon Sep 17 00:00:00 2001 From: Devin Slauenwhite Date: Thu, 29 Dec 2022 00:01:26 -0500 Subject: [PATCH 07/20] feat: add after_refresh hook to item dashboard (#33372) * fix: return promise * fix: use after_refresh hook instead of promise # Because there is already a before_refresh hook. I think it makes sense to do the same for after. --- erpnext/stock/dashboard/item_dashboard.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/erpnext/stock/dashboard/item_dashboard.js b/erpnext/stock/dashboard/item_dashboard.js index 6e7622c067f..1be528f1dd1 100644 --- a/erpnext/stock/dashboard/item_dashboard.js +++ b/erpnext/stock/dashboard/item_dashboard.js @@ -102,6 +102,9 @@ erpnext.stock.ItemDashboard = class ItemDashboard { args: args, callback: function (r) { me.render(r.message); + if(me.after_refresh) { + me.after_refresh(); + } } }); } From 617518389ac0c6459197e27f94efbcb14d409dbf Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Thu, 29 Dec 2022 10:41:36 +0530 Subject: [PATCH 08/20] fix: Conversion factor error for invoices without item code (petty expenses) (#32714) * fix: Set default uom conversion factor to 1 for invoices * chore: set default conversion_factor as 1 * chore: remove print statements --- .../doctype/sales_invoice_item/sales_invoice_item.json | 2 +- erpnext/controllers/accounts_controller.py | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json b/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json index 62c3ced76a7..35d19ed8434 100644 --- a/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json +++ b/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json @@ -890,7 +890,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2022-11-02 12:53:12.693217", + "modified": "2022-12-28 16:17:33.484531", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice Item", diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 334a2d806d6..788dc4982e5 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -584,7 +584,12 @@ class AccountsController(TransactionBase): if bool(uom) != bool(stock_uom): # xor item.stock_uom = item.uom = uom or stock_uom - item.conversion_factor = get_uom_conv_factor(item.get("uom"), item.get("stock_uom")) + # UOM cannot be zero so substitute as 1 + item.conversion_factor = ( + get_uom_conv_factor(item.get("uom"), item.get("stock_uom")) + or item.get("conversion_factor") + or 1 + ) if self.doctype == "Purchase Invoice": self.set_expense_account(for_validate) From c716dcc01e1b29779dac28b4d531b3fa3ee27143 Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Thu, 29 Dec 2022 12:34:18 +0530 Subject: [PATCH 09/20] fix: consider child nodes while getting bin details --- erpnext/controllers/selling_controller.py | 2 +- erpnext/public/js/controllers/buying.js | 3 +- erpnext/stock/get_item_details.py | 38 ++++++++++++++++------- 3 files changed, 30 insertions(+), 13 deletions(-) diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py index 8b073a43202..cd1168d4aca 100644 --- a/erpnext/controllers/selling_controller.py +++ b/erpnext/controllers/selling_controller.py @@ -23,7 +23,7 @@ class SellingController(StockController): super(SellingController, self).onload() if self.doctype in ("Sales Order", "Delivery Note", "Sales Invoice"): for item in self.get("items"): - item.update(get_bin_details(item.item_code, item.warehouse)) + item.update(get_bin_details(item.item_code, item.warehouse, include_child_warehouses=True)) def validate(self): super(SellingController, self).validate() diff --git a/erpnext/public/js/controllers/buying.js b/erpnext/public/js/controllers/buying.js index 09779d89ec1..b0e08cc6f26 100644 --- a/erpnext/public/js/controllers/buying.js +++ b/erpnext/public/js/controllers/buying.js @@ -225,7 +225,8 @@ erpnext.buying.BuyingController = class BuyingController extends erpnext.Transac args: { item_code: item.item_code, warehouse: item.warehouse, - company: doc.company + company: doc.company, + include_child_warehouses: true } }); } diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index 1741d654601..dc00999b463 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -102,9 +102,11 @@ def get_item_details(args, doc=None, for_validate=False, overwrite_warehouse=Tru elif out.get("warehouse"): if doc and doc.get("doctype") == "Purchase Order": # calculate company_total_stock only for po - bin_details = get_bin_details(args.item_code, out.warehouse, args.company) + bin_details = get_bin_details( + args.item_code, out.warehouse, args.company, include_child_warehouses=True + ) else: - bin_details = get_bin_details(args.item_code, out.warehouse) + bin_details = get_bin_details(args.item_code, out.warehouse, include_child_warehouses=True) out.update(bin_details) @@ -1060,7 +1062,9 @@ def get_pos_profile_item_details(company, args, pos_profile=None, update_data=Fa res[fieldname] = pos_profile.get(fieldname) if res.get("warehouse"): - res.actual_qty = get_bin_details(args.item_code, res.warehouse).get("actual_qty") + res.actual_qty = get_bin_details( + args.item_code, res.warehouse, include_child_warehouses=True + ).get("actual_qty") return res @@ -1171,14 +1175,26 @@ def get_projected_qty(item_code, warehouse): @frappe.whitelist() -def get_bin_details(item_code, warehouse, company=None): - bin_details = frappe.db.get_value( - "Bin", - {"item_code": item_code, "warehouse": warehouse}, - ["projected_qty", "actual_qty", "reserved_qty"], - as_dict=True, - cache=True, - ) or {"projected_qty": 0, "actual_qty": 0, "reserved_qty": 0} +def get_bin_details(item_code, warehouse, company=None, include_child_warehouses=False): + bin_details = {"projected_qty": 0, "actual_qty": 0, "reserved_qty": 0} + + if warehouse: + from erpnext.stock.doctype.warehouse.warehouse import get_child_warehouses + + warehouses = get_child_warehouses(warehouse) if include_child_warehouses else [warehouse] + bin_details = frappe.db.get_value( + "Bin", + filters={"item_code": item_code, "warehouse": ["in", warehouses]}, + fieldname=[ + "sum(projected_qty) as projected_qty", + "sum(actual_qty) as actual_qty", + "sum(reserved_qty) as reserved_qty", + ], + as_dict=True, + cache=True, + ) + bin_details = {k: 0 if not v else v for k, v in bin_details.items()} + if company: bin_details["company_total_stock"] = get_company_total_stock(item_code, company) return bin_details From c3911a592a51a390c0b426707f7ec777686cb621 Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Thu, 29 Dec 2022 16:38:08 +0530 Subject: [PATCH 10/20] chore: use `frappe.qb` instead of `frappe.db.get_value` --- erpnext/stock/get_item_details.py | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index dc00999b463..61528809857 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -1179,21 +1179,22 @@ def get_bin_details(item_code, warehouse, company=None, include_child_warehouses bin_details = {"projected_qty": 0, "actual_qty": 0, "reserved_qty": 0} if warehouse: + from frappe.query_builder.functions import Coalesce, Sum + from erpnext.stock.doctype.warehouse.warehouse import get_child_warehouses warehouses = get_child_warehouses(warehouse) if include_child_warehouses else [warehouse] - bin_details = frappe.db.get_value( - "Bin", - filters={"item_code": item_code, "warehouse": ["in", warehouses]}, - fieldname=[ - "sum(projected_qty) as projected_qty", - "sum(actual_qty) as actual_qty", - "sum(reserved_qty) as reserved_qty", - ], - as_dict=True, - cache=True, - ) - bin_details = {k: 0 if not v else v for k, v in bin_details.items()} + + bin = frappe.qb.DocType("Bin") + bin_details = ( + frappe.qb.from_(bin) + .select( + Coalesce(Sum(bin.projected_qty), 0).as_("projected_qty"), + Coalesce(Sum(bin.actual_qty), 0).as_("actual_qty"), + Coalesce(Sum(bin.reserved_qty), 0).as_("reserved_qty"), + ) + .where((bin.item_code == item_code) & (bin.warehouse.isin(warehouses))) + ).run(as_dict=True)[0] if company: bin_details["company_total_stock"] = get_company_total_stock(item_code, company) From 728dc1acf4db979e1dde7d950279d8335028e1a7 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Fri, 30 Dec 2022 11:08:31 +0530 Subject: [PATCH 11/20] Revert "fix: daily scheduler to identify and fix stock transfer entries having incorrect valuation" --- erpnext/hooks.py | 1 - .../stock/doctype/stock_entry/stock_entry.py | 73 +------------------ .../doctype/stock_entry/test_stock_entry.py | 42 +---------- 3 files changed, 2 insertions(+), 114 deletions(-) diff --git a/erpnext/hooks.py b/erpnext/hooks.py index 7d72c76b819..fd19d2585cc 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -420,7 +420,6 @@ scheduler_events = { "erpnext.loan_management.doctype.process_loan_security_shortfall.process_loan_security_shortfall.create_process_loan_security_shortfall", "erpnext.loan_management.doctype.process_loan_interest_accrual.process_loan_interest_accrual.process_loan_interest_accrual_for_term_loans", "erpnext.crm.utils.open_leads_opportunities_based_on_todays_event", - "erpnext.stock.doctype.stock_entry.stock_entry.audit_incorrect_valuation_entries", ], "monthly_long": [ "erpnext.accounts.deferred_revenue.process_deferred_accounting", diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index a047a9b8142..d401f818c68 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -4,24 +4,12 @@ import json from collections import defaultdict -from typing import Dict import frappe from frappe import _ from frappe.model.mapper import get_mapped_doc from frappe.query_builder.functions import Sum -from frappe.utils import ( - add_days, - cint, - comma_or, - cstr, - flt, - format_time, - formatdate, - getdate, - nowdate, - today, -) +from frappe.utils import cint, comma_or, cstr, flt, format_time, formatdate, getdate, nowdate import erpnext from erpnext.accounts.general_ledger import process_gl_map @@ -2712,62 +2700,3 @@ def get_stock_entry_data(work_order): ) .orderby(stock_entry.creation, stock_entry_detail.item_code, stock_entry_detail.idx) ).run(as_dict=1) - - -def audit_incorrect_valuation_entries(): - # Audit of stock transfer entries having incorrect valuation - from erpnext.controllers.stock_controller import create_repost_item_valuation_entry - - stock_entries = get_incorrect_stock_entries() - - for stock_entry, values in stock_entries.items(): - reposting_data = frappe._dict( - { - "posting_date": values.posting_date, - "posting_time": values.posting_time, - "voucher_type": "Stock Entry", - "voucher_no": stock_entry, - "company": values.company, - } - ) - - create_repost_item_valuation_entry(reposting_data) - - -def get_incorrect_stock_entries() -> Dict: - stock_entry = frappe.qb.DocType("Stock Entry") - stock_ledger_entry = frappe.qb.DocType("Stock Ledger Entry") - transfer_purposes = [ - "Material Transfer", - "Material Transfer for Manufacture", - "Send to Subcontractor", - ] - - query = ( - frappe.qb.from_(stock_entry) - .inner_join(stock_ledger_entry) - .on(stock_entry.name == stock_ledger_entry.voucher_no) - .select( - stock_entry.name, - stock_entry.company, - stock_entry.posting_date, - stock_entry.posting_time, - Sum(stock_ledger_entry.stock_value_difference).as_("stock_value"), - ) - .where( - (stock_entry.docstatus == 1) - & (stock_entry.purpose.isin(transfer_purposes)) - & (stock_ledger_entry.modified > add_days(today(), -2)) - ) - .groupby(stock_ledger_entry.voucher_detail_no) - .having(Sum(stock_ledger_entry.stock_value_difference) != 0) - ) - - data = query.run(as_dict=True) - stock_entries = {} - - for row in data: - if abs(row.stock_value) > 0.1 and row.name not in stock_entries: - stock_entries.setdefault(row.name, row) - - return stock_entries diff --git a/erpnext/stock/doctype/stock_entry/test_stock_entry.py b/erpnext/stock/doctype/stock_entry/test_stock_entry.py index 680d209735e..b574b718fe1 100644 --- a/erpnext/stock/doctype/stock_entry/test_stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/test_stock_entry.py @@ -5,7 +5,7 @@ import frappe from frappe.permissions import add_user_permission, remove_user_permission from frappe.tests.utils import FrappeTestCase, change_settings -from frappe.utils import add_days, flt, now, nowdate, nowtime, today +from frappe.utils import add_days, flt, nowdate, nowtime, today from erpnext.accounts.doctype.account.test_account import get_inventory_account from erpnext.stock.doctype.item.test_item import ( @@ -17,8 +17,6 @@ from erpnext.stock.doctype.item.test_item import ( from erpnext.stock.doctype.serial_no.serial_no import * # noqa from erpnext.stock.doctype.stock_entry.stock_entry import ( FinishedGoodError, - audit_incorrect_valuation_entries, - get_incorrect_stock_entries, move_sample_to_retention_warehouse, ) from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry @@ -1616,44 +1614,6 @@ class TestStockEntry(FrappeTestCase): self.assertRaises(BatchExpiredError, se.save) - def test_audit_incorrect_stock_entries(self): - item_code = "Test Incorrect Valuation Rate Item - 001" - create_item(item_code=item_code, is_stock_item=1) - - make_stock_entry( - item_code=item_code, - purpose="Material Receipt", - posting_date=add_days(nowdate(), -10), - qty=2, - rate=500, - to_warehouse="_Test Warehouse - _TC", - ) - - transfer_entry = make_stock_entry( - item_code=item_code, - purpose="Material Transfer", - qty=2, - rate=500, - from_warehouse="_Test Warehouse - _TC", - to_warehouse="_Test Warehouse 1 - _TC", - ) - - sle_name = frappe.db.get_value( - "Stock Ledger Entry", {"voucher_no": transfer_entry.name, "actual_qty": (">", 0)}, "name" - ) - - frappe.db.set_value( - "Stock Ledger Entry", sle_name, {"modified": add_days(now(), -1), "stock_value_difference": 10} - ) - - stock_entries = get_incorrect_stock_entries() - self.assertTrue(transfer_entry.name in stock_entries) - - audit_incorrect_valuation_entries() - - stock_entries = get_incorrect_stock_entries() - self.assertFalse(transfer_entry.name in stock_entries) - def make_serialized_item(**args): args = frappe._dict(args) From 48a9cd5ef38ddc0f10c178c13b9e891764255dbd Mon Sep 17 00:00:00 2001 From: Gughan Ravikumar Date: Fri, 30 Dec 2022 13:16:40 +0530 Subject: [PATCH 12/20] fix: allow arbitary items in Quotation ans Sales Order (#33430) fix: allow arbitary items in Quotation ans Sales Order --- erpnext/selling/doctype/quotation_item/quotation_item.json | 3 +-- erpnext/selling/doctype/sales_order_item/sales_order_item.json | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/erpnext/selling/doctype/quotation_item/quotation_item.json b/erpnext/selling/doctype/quotation_item/quotation_item.json index 31a95896bc1..ca7dfd23378 100644 --- a/erpnext/selling/doctype/quotation_item/quotation_item.json +++ b/erpnext/selling/doctype/quotation_item/quotation_item.json @@ -90,7 +90,6 @@ "oldfieldtype": "Link", "options": "Item", "print_width": "150px", - "reqd": 1, "search_index": 1, "width": "150px" }, @@ -649,7 +648,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2021-07-15 12:40:51.074820", + "modified": "2022-12-25 02:49:53.926625", "modified_by": "Administrator", "module": "Selling", "name": "Quotation Item", diff --git a/erpnext/selling/doctype/sales_order_item/sales_order_item.json b/erpnext/selling/doctype/sales_order_item/sales_order_item.json index b801de314cc..d0dabad5c99 100644 --- a/erpnext/selling/doctype/sales_order_item/sales_order_item.json +++ b/erpnext/selling/doctype/sales_order_item/sales_order_item.json @@ -114,7 +114,6 @@ "oldfieldtype": "Link", "options": "Item", "print_width": "150px", - "reqd": 1, "width": "150px" }, { @@ -865,7 +864,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2022-11-18 11:39:01.741665", + "modified": "2022-12-25 02:51:10.247569", "modified_by": "Administrator", "module": "Selling", "name": "Sales Order Item", From ad53ecf2b48a808dc98f2834dbbfb9c5ebf73c72 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 30 Dec 2022 13:37:41 +0530 Subject: [PATCH 13/20] fix: Multi-currency issues in Bank Recociliation Tool --- .../bank_reconciliation_tool.py | 2 +- .../doctype/bank_transaction/bank_transaction.py | 2 +- .../js/bank_reconciliation_tool/dialog_manager.js | 12 ++++++++++++ 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.py b/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.py index d353270b453..f5f04aeea8b 100644 --- a/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.py +++ b/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.py @@ -302,7 +302,7 @@ def reconcile_vouchers(bank_transaction_name, vouchers): dict( account=account, voucher_type=voucher["payment_doctype"], voucher_no=voucher["payment_name"] ), - ["credit", "debit"], + ["credit_in_account_currency as credit", "debit_in_account_currency as debit"], as_dict=1, ) gl_amount, transaction_amount = ( diff --git a/erpnext/accounts/doctype/bank_transaction/bank_transaction.py b/erpnext/accounts/doctype/bank_transaction/bank_transaction.py index a7885143353..9b36c93a0f3 100644 --- a/erpnext/accounts/doctype/bank_transaction/bank_transaction.py +++ b/erpnext/accounts/doctype/bank_transaction/bank_transaction.py @@ -137,7 +137,7 @@ def get_paid_amount(payment_entry, currency, bank_account): ) elif doc.payment_type == "Pay": paid_amount_field = ( - "paid_amount" if doc.paid_to_account_currency == currency else "base_paid_amount" + "paid_amount" if doc.paid_from_account_currency == currency else "base_paid_amount" ) return frappe.db.get_value( diff --git a/erpnext/public/js/bank_reconciliation_tool/dialog_manager.js b/erpnext/public/js/bank_reconciliation_tool/dialog_manager.js index ca01f68140c..b5e6ab871d1 100644 --- a/erpnext/public/js/bank_reconciliation_tool/dialog_manager.js +++ b/erpnext/public/js/bank_reconciliation_tool/dialog_manager.js @@ -355,12 +355,14 @@ erpnext.accounts.bank_reconciliation.DialogManager = class DialogManager { fieldname: "deposit", fieldtype: "Currency", label: "Deposit", + options: "currency", read_only: 1, }, { fieldname: "withdrawal", fieldtype: "Currency", label: "Withdrawal", + options: "currency", read_only: 1, }, { @@ -378,6 +380,7 @@ erpnext.accounts.bank_reconciliation.DialogManager = class DialogManager { fieldname: "allocated_amount", fieldtype: "Currency", label: "Allocated Amount", + options: "Currency", read_only: 1, }, @@ -385,8 +388,17 @@ erpnext.accounts.bank_reconciliation.DialogManager = class DialogManager { fieldname: "unallocated_amount", fieldtype: "Currency", label: "Unallocated Amount", + options: "Currency", read_only: 1, }, + { + fieldname: "currency", + fieldtype: "Link", + label: "Currency", + options: "Currency", + read_only: 1, + hidden: 1, + } ]; } From 8d62cdfd5f683c20c8ac0bd06e2b40b86579fd56 Mon Sep 17 00:00:00 2001 From: Devin Slauenwhite Date: Sat, 31 Dec 2022 14:14:25 -0500 Subject: [PATCH 14/20] fix: add missing 'ordered_qty' to get_bin_details --- erpnext/stock/get_item_details.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index 1741d654601..02456f3d295 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -1175,10 +1175,10 @@ def get_bin_details(item_code, warehouse, company=None): bin_details = frappe.db.get_value( "Bin", {"item_code": item_code, "warehouse": warehouse}, - ["projected_qty", "actual_qty", "reserved_qty"], + ["projected_qty", "actual_qty", "reserved_qty", "ordered_qty"], as_dict=True, cache=True, - ) or {"projected_qty": 0, "actual_qty": 0, "reserved_qty": 0} + ) or {"projected_qty": 0, "actual_qty": 0, "reserved_qty": 0, "ordered_qty": 0} if company: bin_details["company_total_stock"] = get_company_total_stock(item_code, company) return bin_details From 239a5f8bf4c6dda9a955dc6472d9387d80afe619 Mon Sep 17 00:00:00 2001 From: Devin Slauenwhite Date: Sat, 31 Dec 2022 15:41:07 -0500 Subject: [PATCH 15/20] test: get_item_details contains bin details --- erpnext/stock/doctype/item/test_item.py | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/item/test_item.py b/erpnext/stock/doctype/item/test_item.py index e1ee9389de9..7e426ae4af8 100644 --- a/erpnext/stock/doctype/item/test_item.py +++ b/erpnext/stock/doctype/item/test_item.py @@ -83,6 +83,7 @@ class TestItem(FrappeTestCase): def test_get_item_details(self): # delete modified item price record and make as per test_records frappe.db.sql("""delete from `tabItem Price`""") + frappe.db.sql("""delete from `tabBin`""") to_check = { "item_code": "_Test Item", @@ -103,9 +104,26 @@ class TestItem(FrappeTestCase): "batch_no": None, "uom": "_Test UOM", "conversion_factor": 1.0, + "reserved_qty": 1, + "actual_qty": 5, + "ordered_qty": 10, + "projected_qty": 14, } make_test_objects("Item Price") + make_test_objects( + "Bin", + [ + { + "item_code": "_Test Item", + "warehouse": "_Test Warehouse - _TC", + "reserved_qty": 1, + "actual_qty": 5, + "ordered_qty": 10, + "projected_qty": 14, + } + ], + ) company = "_Test Company" currency = frappe.get_cached_value("Company", company, "default_currency") @@ -129,7 +147,7 @@ class TestItem(FrappeTestCase): ) for key, value in to_check.items(): - self.assertEqual(value, details.get(key)) + self.assertEqual(value, details.get(key), key) def test_item_tax_template(self): expected_item_tax_template = [ From 98c39c4f5f5824839ca6da0ceaa4f6868be27c1c Mon Sep 17 00:00:00 2001 From: Devin Slauenwhite Date: Sun, 1 Jan 2023 22:25:12 -0500 Subject: [PATCH 16/20] fix: update payment entry references (#33274) * fix: set_amounts after deductions and losses are set * test: difference_amount changes after update_references_in_payment_entry * chore: linter * fix: use kwargs instad of destructing a dict [skip ci] * fix(test): test payment entry difference_amount after payment reconciliation. --- erpnext/accounts/test/test_utils.py | 44 +++++++++++++++++++ erpnext/accounts/utils.py | 10 ++--- erpnext/www/book-appointment/__init__.py | 0 .../www/book-appointment/verify/__init__.py | 0 4 files changed, 49 insertions(+), 5 deletions(-) create mode 100644 erpnext/www/book-appointment/__init__.py create mode 100644 erpnext/www/book-appointment/verify/__init__.py diff --git a/erpnext/accounts/test/test_utils.py b/erpnext/accounts/test/test_utils.py index 882cd694a32..3aca60eae5b 100644 --- a/erpnext/accounts/test/test_utils.py +++ b/erpnext/accounts/test/test_utils.py @@ -3,11 +3,14 @@ import unittest import frappe from frappe.test_runner import make_test_objects +from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry +from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice from erpnext.accounts.party import get_party_shipping_address from erpnext.accounts.utils import ( get_future_stock_vouchers, get_voucherwise_gl_entries, sort_stock_vouchers_by_posting_date, + update_reference_in_payment_entry, ) from erpnext.stock.doctype.item.test_item import make_item from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt @@ -73,6 +76,47 @@ class TestUtils(unittest.TestCase): sorted_vouchers = sort_stock_vouchers_by_posting_date(list(reversed(vouchers))) self.assertEqual(sorted_vouchers, vouchers) + def test_update_reference_in_payment_entry(self): + item = make_item().name + + purchase_invoice = make_purchase_invoice( + item=item, supplier="_Test Supplier USD", currency="USD", conversion_rate=82.32 + ) + purchase_invoice.submit() + + payment_entry = get_payment_entry(purchase_invoice.doctype, purchase_invoice.name) + payment_entry.target_exchange_rate = 62.9 + payment_entry.paid_amount = 15725 + payment_entry.deductions = [] + payment_entry.insert() + + self.assertEqual(payment_entry.difference_amount, -4855.00) + payment_entry.references = [] + payment_entry.submit() + + payment_reconciliation = frappe.new_doc("Payment Reconciliation") + payment_reconciliation.company = payment_entry.company + payment_reconciliation.party_type = "Supplier" + payment_reconciliation.party = purchase_invoice.supplier + payment_reconciliation.receivable_payable_account = payment_entry.paid_to + payment_reconciliation.get_unreconciled_entries() + payment_reconciliation.allocate_entries( + { + "payments": [d.__dict__ for d in payment_reconciliation.payments], + "invoices": [d.__dict__ for d in payment_reconciliation.invoices], + } + ) + for d in payment_reconciliation.invoices: + # Reset invoice outstanding_amount because allocate_entries will zero this value out. + d.outstanding_amount = d.amount + for d in payment_reconciliation.allocation: + d.difference_account = "Exchange Gain/Loss - _TC" + payment_reconciliation.reconcile() + + payment_entry.load_from_db() + self.assertEqual(len(payment_entry.references), 1) + self.assertEqual(payment_entry.difference_amount, 0) + ADDRESS_RECORDS = [ { diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index 1e573b01bad..445dcc53c63 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -611,11 +611,6 @@ def update_reference_in_payment_entry(d, payment_entry, do_not_save=False): new_row.docstatus = 1 new_row.update(reference_details) - payment_entry.flags.ignore_validate_update_after_submit = True - payment_entry.setup_party_account_field() - payment_entry.set_missing_values() - payment_entry.set_amounts() - if d.difference_amount and d.difference_account: account_details = { "account": d.difference_account, @@ -627,6 +622,11 @@ def update_reference_in_payment_entry(d, payment_entry, do_not_save=False): payment_entry.set_gain_or_loss(account_details=account_details) + payment_entry.flags.ignore_validate_update_after_submit = True + payment_entry.setup_party_account_field() + payment_entry.set_missing_values() + payment_entry.set_amounts() + if not do_not_save: payment_entry.save(ignore_permissions=True) diff --git a/erpnext/www/book-appointment/__init__.py b/erpnext/www/book-appointment/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/www/book-appointment/verify/__init__.py b/erpnext/www/book-appointment/verify/__init__.py new file mode 100644 index 00000000000..e69de29bb2d From 010718ffed09bef6c485045f9808e7163064130f Mon Sep 17 00:00:00 2001 From: Sabu Siyad Date: Mon, 2 Jan 2023 08:58:01 +0530 Subject: [PATCH 17/20] fix(ecommerce/cart): explicitly set `frappe.boot` (#33431) related: https://github.com/frappe/frappe/pull/18323 Signed-off-by: Sabu Siyad Signed-off-by: Sabu Siyad --- erpnext/templates/includes/cart/cart_address.html | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/templates/includes/cart/cart_address.html b/erpnext/templates/includes/cart/cart_address.html index cf600173731..a8188ec8254 100644 --- a/erpnext/templates/includes/cart/cart_address.html +++ b/erpnext/templates/includes/cart/cart_address.html @@ -55,6 +55,7 @@ {% endif %}