From 38e27a68d597e9c3cdf4124cb2b53645eb11f53e Mon Sep 17 00:00:00 2001 From: Shadrak Gurupnor <30501401+shadrak98@users.noreply.github.com> Date: Tue, 13 Sep 2022 13:42:45 +0530 Subject: [PATCH 01/27] fix: validate for active sla (#32132) (cherry picked from commit f2b7c9ee6638deefc546fb3fc929b805ff86b801) --- .../doctype/service_level_agreement/service_level_agreement.py | 3 +++ 1 file changed, 3 insertions(+) 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 e49f212f10f..7f4e9efa948 100644 --- a/erpnext/support/doctype/service_level_agreement/service_level_agreement.py +++ b/erpnext/support/doctype/service_level_agreement/service_level_agreement.py @@ -776,6 +776,9 @@ def on_communication_update(doc, status): if not parent.meta.has_field("service_level_agreement"): return + if not parent.get("service_level_agreement"): + return + if ( doc.sent_or_received == "Received" # a reply is received and parent.get("status") == "Open" # issue status is set as open from communication.py From e3c1d736ce6da0d2149dea26051980c06311128c Mon Sep 17 00:00:00 2001 From: "justin.li" Date: Fri, 12 May 2023 02:21:19 +0000 Subject: [PATCH 02/27] fix: bad strings format for update-translations (#34592) (cherry picked from commit 07c9b990728ceba5b74bc81c4155e36bf19d9c77) --- .../doctype/bank_transaction/bank_transaction.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/erpnext/accounts/doctype/bank_transaction/bank_transaction.py b/erpnext/accounts/doctype/bank_transaction/bank_transaction.py index d2d961f7c2a..b441af96600 100644 --- a/erpnext/accounts/doctype/bank_transaction/bank_transaction.py +++ b/erpnext/accounts/doctype/bank_transaction/bank_transaction.py @@ -46,7 +46,7 @@ class BankTransaction(StatusUpdater): def add_payment_entries(self, vouchers): "Add the vouchers with zero allocation. Save() will perform the allocations and clearance" if 0.0 >= self.unallocated_amount: - frappe.throw(frappe._(f"Bank Transaction {self.name} is already fully reconciled")) + frappe.throw(frappe._("Bank Transaction {0} is already fully reconciled").format(self.name)) added = False for voucher in vouchers: @@ -114,9 +114,7 @@ class BankTransaction(StatusUpdater): elif 0.0 > unallocated_amount: self.db_delete_payment_entry(payment_entry) - frappe.throw( - frappe._(f"Voucher {payment_entry.payment_entry} is over-allocated by {unallocated_amount}") - ) + frappe.throw(frappe._("Voucher {0} is over-allocated by {1}").format(unallocated_amount)) self.reload() @@ -178,7 +176,9 @@ def get_clearance_details(transaction, payment_entry): if gle["gl_account"] == gl_bank_account: if gle["amount"] <= 0.0: frappe.throw( - frappe._(f"Voucher {payment_entry.payment_entry} value is broken: {gle['amount']}") + frappe._("Voucher {0} value is broken: {1}").format( + payment_entry.payment_entry, gle["amount"] + ) ) unmatched_gles -= 1 From 5a542966868249165012ccd7985120d71b949a07 Mon Sep 17 00:00:00 2001 From: Danny <66078599+mmdanny89@users.noreply.github.com> Date: Fri, 17 Mar 2023 06:43:32 -0400 Subject: [PATCH 03/27] fix: bad strings format for command get-untraslated (#34361) (cherry picked from commit ca10e2bb9ff61bc1c9ffcd1e7a2a14d6ec4f4d51) --- erpnext/stock/doctype/pick_list/pick_list.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/doctype/pick_list/pick_list.py b/erpnext/stock/doctype/pick_list/pick_list.py index bf3b5ddc54a..46d6e9e7578 100644 --- a/erpnext/stock/doctype/pick_list/pick_list.py +++ b/erpnext/stock/doctype/pick_list/pick_list.py @@ -172,8 +172,8 @@ class PickList(Document): if (row.picked_qty / row.stock_qty) * 100 > over_delivery_receipt_allowance: frappe.throw( _( - f"You are picking more than required quantity for the item {row.item_code}. Check if there is any other pick list created for the sales order {row.sales_order}." - ) + "You are picking more than required quantity for the item {0}. Check if there is any other pick list created for the sales order {1}." + ).format(row.item_code, row.sales_order) ) @frappe.whitelist() From 457846e34c472bad2ae1cc1e728c570241c23ed9 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Thu, 11 May 2023 23:17:09 +0530 Subject: [PATCH 04/27] fix: enqueue submit/cancel action for stock entry to avoid time out error (cherry picked from commit 7a3801578cb3cf6fbaa6fc35814fc97e73820dcd) --- .../stock/doctype/stock_entry/stock_entry.py | 44 ++++++++++++++++++- .../doctype/stock_entry/test_stock_entry.py | 30 ++++++++++++- 2 files changed, 72 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 2e89078440f..2dbfeadf0f4 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -9,7 +9,17 @@ import frappe from frappe import _ from frappe.model.mapper import get_mapped_doc from frappe.query_builder.functions import Sum -from frappe.utils import cint, comma_or, cstr, flt, format_time, formatdate, getdate, nowdate +from frappe.utils import ( + cint, + comma_or, + cstr, + flt, + format_time, + formatdate, + getdate, + month_diff, + nowdate, +) import erpnext from erpnext.accounts.general_ledger import process_gl_map @@ -154,6 +164,38 @@ class StockEntry(StockController): self.reset_default_field_value("from_warehouse", "items", "s_warehouse") self.reset_default_field_value("to_warehouse", "items", "t_warehouse") + def submit(self): + if self.is_enqueue_action(): + frappe.msgprint( + _( + "The task has been enqueued as a background job. In case there is any issue on processing in background, the system will add a comment about the error on this Stock Reconciliation and revert to the Draft stage" + ) + ) + self.queue_action("submit", timeout=2000) + else: + self._submit() + + def cancel(self): + if self.is_enqueue_action(): + frappe.msgprint( + _( + "The task has been enqueued as a background job. In case there is any issue on processing in background, the system will add a comment about the error on this Stock Reconciliation and revert to the Submitted stage" + ) + ) + self.queue_action("cancel", timeout=2000) + else: + self._cancel() + + def is_enqueue_action(self, force=False) -> bool: + if force: + return True + + # If line items are more than 100 or record is older than 6 months + if len(self.items) > 100 or month_diff(nowdate(), self.posting_date) > 6: + return True + + return False + def on_submit(self): self.update_stock_ledger() diff --git a/erpnext/stock/doctype/stock_entry/test_stock_entry.py b/erpnext/stock/doctype/stock_entry/test_stock_entry.py index c43a1b1b81e..9a748abb3be 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, nowdate, nowtime, today +from frappe.utils import add_days, add_to_date, flt, nowdate, nowtime, today from erpnext.accounts.doctype.account.test_account import get_inventory_account from erpnext.stock.doctype.item.test_item import ( @@ -1707,6 +1707,34 @@ class TestStockEntry(FrappeTestCase): self.assertRaises(frappe.ValidationError, sr_doc.submit) + def test_enqueue_action(self): + item_code = "Test Enqueue Item - 001" + create_item(item_code=item_code, is_stock_item=1, valuation_rate=10) + + doc = make_stock_entry( + item_code=item_code, + posting_date=add_to_date(today(), months=-7), + posting_time="00:00:00", + purpose="Material Receipt", + qty=10, + to_warehouse="_Test Warehouse - _TC", + do_not_submit=True, + ) + + self.assertTrue(doc.is_enqueue_action()) + + doc = make_stock_entry( + item_code=item_code, + posting_date=today(), + posting_time="00:00:00", + purpose="Material Receipt", + qty=10, + to_warehouse="_Test Warehouse - _TC", + do_not_submit=True, + ) + + self.assertFalse(doc.is_enqueue_action()) + def make_serialized_item(**args): args = frappe._dict(args) From 9b2b46737e6b105b45b938d54f0e9a4406503425 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Fri, 12 May 2023 10:51:14 +0530 Subject: [PATCH 05/27] fix: test case (cherry picked from commit 2d6f112727185cff40662d8d5873cedb128d9218) --- erpnext/stock/doctype/stock_entry/stock_entry.py | 3 +++ erpnext/stock/doctype/stock_entry/test_stock_entry.py | 2 ++ 2 files changed, 5 insertions(+) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 2dbfeadf0f4..d82fab8749a 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -190,6 +190,9 @@ class StockEntry(StockController): if force: return True + if frappe.flags.in_test: + return False + # If line items are more than 100 or record is older than 6 months if len(self.items) > 100 or month_diff(nowdate(), self.posting_date) > 6: return True diff --git a/erpnext/stock/doctype/stock_entry/test_stock_entry.py b/erpnext/stock/doctype/stock_entry/test_stock_entry.py index 9a748abb3be..de74fda687d 100644 --- a/erpnext/stock/doctype/stock_entry/test_stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/test_stock_entry.py @@ -1708,6 +1708,7 @@ class TestStockEntry(FrappeTestCase): self.assertRaises(frappe.ValidationError, sr_doc.submit) def test_enqueue_action(self): + frappe.flags.in_test = False item_code = "Test Enqueue Item - 001" create_item(item_code=item_code, is_stock_item=1, valuation_rate=10) @@ -1734,6 +1735,7 @@ class TestStockEntry(FrappeTestCase): ) self.assertFalse(doc.is_enqueue_action()) + frappe.flags.in_test = True def make_serialized_item(**args): From 098603dd35ed571bf1cfa27633657e2a986dbb4a Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Fri, 12 May 2023 13:08:32 +0530 Subject: [PATCH 06/27] fix: bom item filter issue (cherry picked from commit 2879cb7c280d82e879dd787c5d4a90e9f27cda14) --- erpnext/manufacturing/doctype/bom/bom.js | 1 - 1 file changed, 1 deletion(-) diff --git a/erpnext/manufacturing/doctype/bom/bom.js b/erpnext/manufacturing/doctype/bom/bom.js index 066a373da24..231c476734c 100644 --- a/erpnext/manufacturing/doctype/bom/bom.js +++ b/erpnext/manufacturing/doctype/bom/bom.js @@ -48,7 +48,6 @@ frappe.ui.form.on("BOM", { return { query: "erpnext.manufacturing.doctype.bom.bom.item_query", filters: { - "item_code": doc.item, "include_item_in_manufacturing": 1, "is_fixed_asset": 0 } From ab564701711657698e56e5480bb9eb0c698dffdf Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Fri, 12 May 2023 13:47:53 +0530 Subject: [PATCH 07/27] fix: incorrect packing items (cherry picked from commit a686b8c337abe8425a041ab82e702d904f48b617) --- .../doctype/sales_order/sales_order.py | 4 ++ .../doctype/sales_order/test_sales_order.py | 69 +++++++++++++++++++ 2 files changed, 73 insertions(+) diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py index de63f6dec58..06467e51a63 100755 --- a/erpnext/selling/doctype/sales_order/sales_order.py +++ b/erpnext/selling/doctype/sales_order/sales_order.py @@ -620,6 +620,8 @@ def make_project(source_name, target_doc=None): @frappe.whitelist() def make_delivery_note(source_name, target_doc=None, skip_item_mapping=False): + from erpnext.stock.doctype.packed_item.packed_item import make_packing_list + def set_missing_values(source, target): target.run_method("set_missing_values") target.run_method("set_po_nos") @@ -634,6 +636,8 @@ def make_delivery_note(source_name, target_doc=None, skip_item_mapping=False): if target.company_address: target.update(get_fetch_values("Delivery Note", "company_address", target.company_address)) + make_packing_list(target) + def update_item(source, target, source_parent): target.base_amount = (flt(source.qty) - flt(source.delivered_qty)) * flt(source.base_rate) target.amount = (flt(source.qty) - flt(source.delivered_qty)) * flt(source.rate) diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py index ba8bbc21857..9854f159cfe 100644 --- a/erpnext/selling/doctype/sales_order/test_sales_order.py +++ b/erpnext/selling/doctype/sales_order/test_sales_order.py @@ -1909,6 +1909,75 @@ class TestSalesOrder(FrappeTestCase): self.assertEqual(mr.items[0].qty, 6) + def test_packed_items_for_partial_sales_order(self): + # test Update Items with product bundle + for product_bundle in [ + "_Test Product Bundle Item Partial 1", + "_Test Product Bundle Item Partial 2", + ]: + if not frappe.db.exists("Item", product_bundle): + bundle_item = make_item(product_bundle, {"is_stock_item": 0}) + bundle_item.append( + "item_defaults", {"company": "_Test Company", "default_warehouse": "_Test Warehouse - _TC"} + ) + bundle_item.save(ignore_permissions=True) + + for product_bundle in ["_Packed Item Partial 1", "_Packed Item Partial 2"]: + if not frappe.db.exists("Item", product_bundle): + make_item(product_bundle, {"is_stock_item": 1, "stock_uom": "Nos"}) + + make_stock_entry(item=product_bundle, target="_Test Warehouse - _TC", qty=2, rate=10) + + make_product_bundle("_Test Product Bundle Item Partial 1", ["_Packed Item Partial 1"], 1) + + make_product_bundle("_Test Product Bundle Item Partial 2", ["_Packed Item Partial 2"], 1) + + so = make_sales_order( + item_code="_Test Product Bundle Item Partial 1", + warehouse="_Test Warehouse - _TC", + qty=1, + uom="Nos", + stock_uom="Nos", + conversion_factor=1, + transaction_date=nowdate(), + delivery_note=nowdate(), + do_not_submit=1, + ) + + so.append( + "items", + { + "item_code": "_Test Product Bundle Item Partial 2", + "warehouse": "_Test Warehouse - _TC", + "qty": 1, + "uom": "Nos", + "stock_uom": "Nos", + "conversion_factor": 1, + "delivery_note": nowdate(), + }, + ) + + so.save() + so.submit() + + dn = make_delivery_note(so.name) + dn.remove(dn.items[1]) + dn.save() + dn.submit() + + self.assertEqual(len(dn.items), 1) + self.assertEqual(len(dn.packed_items), 1) + self.assertEqual(dn.items[0].item_code, "_Test Product Bundle Item Partial 1") + + so.load_from_db() + + dn = make_delivery_note(so.name) + dn.save() + + self.assertEqual(len(dn.items), 1) + self.assertEqual(len(dn.packed_items), 1) + self.assertEqual(dn.items[0].item_code, "_Test Product Bundle Item Partial 2") + def automatically_fetch_payment_terms(enable=1): accounts_settings = frappe.get_doc("Accounts Settings") From 1d8050d24d04ac8f688c788056af32313db07fec Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Tue, 9 May 2023 16:07:14 +0530 Subject: [PATCH 08/27] fix: inventory dimension for material transfer not working (cherry picked from commit 6798b900ef3ef10a0582c074cc535210df72e550) --- erpnext/controllers/stock_controller.py | 24 ++- .../inventory_dimension.py | 139 +++++++++++++++--- .../test_inventory_dimension.py | 97 ++++++++++++ 3 files changed, 237 insertions(+), 23 deletions(-) diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index a27e34819d4..796a069651c 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -442,7 +442,29 @@ class StockController(AccountsController): if not dimension: continue - if row.get(dimension.source_fieldname): + if self.doctype in [ + "Purchase Invoice", + "Purchase Receipt", + "Sales Invoice", + "Delivery Note", + "Stock Entry", + ]: + if (sl_dict.actual_qty > 0 and self.doctype in ["Purchase Invoice", "Purchase Receipt"]) or ( + sl_dict.actual_qty < 0 and self.doctype in ["Sales Invoice", "Delivery Note", "Stock Entry"] + ): + sl_dict[dimension.target_fieldname] = row.get(dimension.source_fieldname) + else: + fieldname_start_with = "to" + if self.doctype in ["Purchase Invoice", "Purchase Receipt"]: + fieldname_start_with = "from" + + fieldname = f"{fieldname_start_with}_{dimension.source_fieldname}" + sl_dict[dimension.target_fieldname] = row.get(fieldname) + + if not sl_dict.get(dimension.target_fieldname): + sl_dict[dimension.target_fieldname] = row.get(dimension.source_fieldname) + + elif row.get(dimension.source_fieldname): sl_dict[dimension.target_fieldname] = row.get(dimension.source_fieldname) if not sl_dict.get(dimension.target_fieldname) and dimension.fetch_from_parent: diff --git a/erpnext/stock/doctype/inventory_dimension/inventory_dimension.py b/erpnext/stock/doctype/inventory_dimension/inventory_dimension.py index db2b5d0a6b6..8bff4d51470 100644 --- a/erpnext/stock/doctype/inventory_dimension/inventory_dimension.py +++ b/erpnext/stock/doctype/inventory_dimension/inventory_dimension.py @@ -75,7 +75,16 @@ class InventoryDimension(Document): self.delete_custom_fields() def delete_custom_fields(self): - filters = {"fieldname": self.source_fieldname} + filters = { + "fieldname": ( + "in", + [ + self.source_fieldname, + f"to_{self.source_fieldname}", + f"from_{self.source_fieldname}", + ], + ) + } if self.document_type: filters["dt"] = self.document_type @@ -88,6 +97,8 @@ class InventoryDimension(Document): def reset_value(self): if self.apply_to_all_doctypes: + self.type_of_transaction = "" + self.istable = 0 for field in ["document_type", "condition"]: self.set(field, None) @@ -111,12 +122,35 @@ class InventoryDimension(Document): def on_update(self): self.add_custom_fields() - def add_custom_fields(self): - dimension_fields = [ + @staticmethod + def get_insert_after_fieldname(doctype): + return frappe.get_all( + "DocField", + fields=["fieldname"], + filters={"parent": doctype}, + order_by="idx desc", + limit=1, + )[0].fieldname + + def get_dimension_fields(self, doctype=None): + if not doctype: + doctype = self.document_type + + label_start_with = "" + if doctype in ["Purchase Invoice Item", "Purchase Receipt Item"]: + label_start_with = "Target" + elif doctype in ["Sales Invoice Item", "Delivery Note Item", "Stock Entry Detail"]: + label_start_with = "Source" + + label = self.dimension_name + if label_start_with: + label = f"{label_start_with} {self.dimension_name}" + + return [ dict( fieldname="inventory_dimension", fieldtype="Section Break", - insert_after="warehouse", + insert_after=self.get_insert_after_fieldname(doctype), label="Inventory Dimension", collapsible=1, ), @@ -125,24 +159,37 @@ class InventoryDimension(Document): fieldtype="Link", insert_after="inventory_dimension", options=self.reference_document, - label=self.dimension_name, + label=label, reqd=self.reqd, mandatory_depends_on=self.mandatory_depends_on, ), ] + def add_custom_fields(self): custom_fields = {} + dimension_fields = [] if self.apply_to_all_doctypes: for doctype in get_inventory_documents(): - if not field_exists(doctype[0], self.source_fieldname): - custom_fields.setdefault(doctype[0], dimension_fields) + if field_exists(doctype[0], self.source_fieldname): + continue + + dimension_fields = self.get_dimension_fields(doctype[0]) + self.add_transfer_field(doctype[0], dimension_fields) + custom_fields.setdefault(doctype[0], dimension_fields) elif not field_exists(self.document_type, self.source_fieldname): + dimension_fields = self.get_dimension_fields() + + self.add_transfer_field(self.document_type, dimension_fields) custom_fields.setdefault(self.document_type, dimension_fields) - if not frappe.db.get_value( - "Custom Field", {"dt": "Stock Ledger Entry", "fieldname": self.target_fieldname} - ) and not field_exists("Stock Ledger Entry", self.target_fieldname): + if ( + dimension_fields + and not frappe.db.get_value( + "Custom Field", {"dt": "Stock Ledger Entry", "fieldname": self.target_fieldname} + ) + and not field_exists("Stock Ledger Entry", self.target_fieldname) + ): dimension_field = dimension_fields[1] dimension_field["mandatory_depends_on"] = "" dimension_field["reqd"] = 0 @@ -152,6 +199,53 @@ class InventoryDimension(Document): if custom_fields: create_custom_fields(custom_fields) + def add_transfer_field(self, doctype, dimension_fields): + if doctype not in [ + "Stock Entry Detail", + "Sales Invoice Item", + "Delivery Note Item", + "Purchase Invoice Item", + "Purchase Receipt Item", + ]: + return + + fieldname_start_with = "to" + label_start_with = "Target" + display_depends_on = "" + + if doctype in ["Purchase Invoice Item", "Purchase Receipt Item"]: + fieldname_start_with = "from" + label_start_with = "Source" + display_depends_on = "eval:parent.is_internal_supplier == 1" + elif doctype != "Stock Entry Detail": + display_depends_on = "eval:parent.is_internal_customer == 1" + elif doctype == "Stock Entry Detail": + display_depends_on = "eval:parent.purpose != 'Material Issue'" + + fieldname = f"{fieldname_start_with}_{self.source_fieldname}" + label = f"{label_start_with} {self.dimension_name}" + + if field_exists(doctype, fieldname): + return + + dimension_fields.extend( + [ + dict( + fieldname="inventory_dimension_col_break", + fieldtype="Column Break", + insert_after=self.source_fieldname, + ), + dict( + fieldname=fieldname, + fieldtype="Link", + insert_after="inventory_dimension_col_break", + options=self.reference_document, + label=label, + depends_on=display_depends_on, + ), + ] + ) + def field_exists(doctype, fieldname) -> str or None: return frappe.db.get_value("DocField", {"parent": doctype, "fieldname": fieldname}, "name") @@ -185,18 +279,19 @@ def get_evaluated_inventory_dimension(doc, sl_dict, parent_doc=None): dimensions = get_document_wise_inventory_dimensions(doc.doctype) filter_dimensions = [] for row in dimensions: - if ( - row.type_of_transaction == "Inward" - if doc.docstatus == 1 - else row.type_of_transaction != "Inward" - ) and sl_dict.actual_qty < 0: - continue - elif ( - row.type_of_transaction == "Outward" - if doc.docstatus == 1 - else row.type_of_transaction != "Outward" - ) and sl_dict.actual_qty > 0: - continue + if row.type_of_transaction: + if ( + row.type_of_transaction == "Inward" + if doc.docstatus == 1 + else row.type_of_transaction != "Inward" + ) and sl_dict.actual_qty < 0: + continue + elif ( + row.type_of_transaction == "Outward" + if doc.docstatus == 1 + else row.type_of_transaction != "Outward" + ) and sl_dict.actual_qty > 0: + continue evals = {"doc": doc} if parent_doc: diff --git a/erpnext/stock/doctype/inventory_dimension/test_inventory_dimension.py b/erpnext/stock/doctype/inventory_dimension/test_inventory_dimension.py index 28b1ed96f0d..ae5f521f2b8 100644 --- a/erpnext/stock/doctype/inventory_dimension/test_inventory_dimension.py +++ b/erpnext/stock/doctype/inventory_dimension/test_inventory_dimension.py @@ -12,6 +12,7 @@ from erpnext.stock.doctype.inventory_dimension.inventory_dimension import ( DoNotChangeError, delete_dimension, ) +from erpnext.stock.doctype.item.test_item import create_item from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse @@ -20,6 +21,7 @@ from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse class TestInventoryDimension(FrappeTestCase): def setUp(self): prepare_test_data() + create_store_dimension() def test_validate_inventory_dimension(self): # Can not be child doc @@ -73,6 +75,8 @@ class TestInventoryDimension(FrappeTestCase): self.assertFalse(custom_field) def test_inventory_dimension(self): + frappe.local.document_wise_inventory_dimensions = {} + warehouse = "Shelf Warehouse - _TC" item_code = "_Test Item" @@ -143,6 +147,8 @@ class TestInventoryDimension(FrappeTestCase): self.assertRaises(DoNotChangeError, inv_dim1.save) def test_inventory_dimension_for_purchase_receipt_and_delivery_note(self): + frappe.local.document_wise_inventory_dimensions = {} + inv_dimension = create_inventory_dimension( reference_document="Rack", dimension_name="Rack", apply_to_all_doctypes=1 ) @@ -250,6 +256,97 @@ class TestInventoryDimension(FrappeTestCase): ) ) + def test_for_purchase_sales_and_stock_transaction(self): + create_inventory_dimension( + reference_document="Store", + type_of_transaction="Outward", + dimension_name="Store", + apply_to_all_doctypes=1, + ) + + item_code = "Test Inventory Dimension Item" + create_item(item_code) + warehouse = create_warehouse("Store Warehouse") + + # Purchase Receipt -> Inward in Store 1 + pr_doc = make_purchase_receipt( + item_code=item_code, warehouse=warehouse, qty=10, rate=100, do_not_submit=True + ) + + pr_doc.items[0].store = "Store 1" + pr_doc.save() + pr_doc.submit() + + entries = get_voucher_sl_entries(pr_doc.name, ["warehouse", "store", "incoming_rate"]) + + self.assertEqual(entries[0].warehouse, warehouse) + self.assertEqual(entries[0].store, "Store 1") + + # Stock Entry -> Transfer from Store 1 to Store 2 + se_doc = make_stock_entry( + item_code=item_code, qty=10, from_warehouse=warehouse, to_warehouse=warehouse, do_not_save=True + ) + + se_doc.items[0].store = "Store 1" + se_doc.items[0].to_store = "Store 2" + + se_doc.save() + se_doc.submit() + + entries = get_voucher_sl_entries( + se_doc.name, ["warehouse", "store", "incoming_rate", "actual_qty"] + ) + + for entry in entries: + self.assertEqual(entry.warehouse, warehouse) + if entry.actual_qty > 0: + self.assertEqual(entry.store, "Store 2") + self.assertEqual(entry.incoming_rate, 100.0) + else: + self.assertEqual(entry.store, "Store 1") + + # Delivery Note -> Outward from Store 2 + + dn_doc = create_delivery_note(item_code=item_code, qty=10, warehouse=warehouse, do_not_save=True) + + dn_doc.items[0].store = "Store 2" + dn_doc.save() + dn_doc.submit() + + entries = get_voucher_sl_entries(dn_doc.name, ["warehouse", "store", "actual_qty"]) + + self.assertEqual(entries[0].warehouse, warehouse) + self.assertEqual(entries[0].store, "Store 2") + self.assertEqual(entries[0].actual_qty, -10.0) + + +def get_voucher_sl_entries(voucher_no, fields): + return frappe.get_all( + "Stock Ledger Entry", filters={"voucher_no": voucher_no}, fields=fields, order_by="creation" + ) + + +def create_store_dimension(): + if not frappe.db.exists("DocType", "Store"): + frappe.get_doc( + { + "doctype": "DocType", + "name": "Store", + "module": "Stock", + "custom": 1, + "naming_rule": "By fieldname", + "autoname": "field:store_name", + "fields": [{"label": "Store Name", "fieldname": "store_name", "fieldtype": "Data"}], + "permissions": [ + {"role": "System Manager", "permlevel": 0, "read": 1, "write": 1, "create": 1, "delete": 1} + ], + } + ).insert(ignore_permissions=True) + + for store in ["Store 1", "Store 2"]: + if not frappe.db.exists("Store", store): + frappe.get_doc({"doctype": "Store", "store_name": store}).insert(ignore_permissions=True) + def prepare_test_data(): if not frappe.db.exists("DocType", "Shelf"): From 3697e8f1f9d2f17672cf83109f4b41b040c2d38b Mon Sep 17 00:00:00 2001 From: Sagar Sharma Date: Fri, 12 May 2023 15:06:03 +0530 Subject: [PATCH 09/27] fix: add missing options for `Content Align` (cherry picked from commit d16caa2d2c0d2bbdf36e2d698b5d3fb9809c47a3) --- erpnext/e_commerce/web_template/hero_slider/hero_slider.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/erpnext/e_commerce/web_template/hero_slider/hero_slider.json b/erpnext/e_commerce/web_template/hero_slider/hero_slider.json index 2b1807c9651..39b2b3eaeb8 100644 --- a/erpnext/e_commerce/web_template/hero_slider/hero_slider.json +++ b/erpnext/e_commerce/web_template/hero_slider/hero_slider.json @@ -165,6 +165,7 @@ "fieldname": "slide_3_content_align", "fieldtype": "Select", "label": "Content Align", + "options": "Left\nCentre\nRight", "reqd": 0 }, { @@ -214,6 +215,7 @@ "fieldname": "slide_4_content_align", "fieldtype": "Select", "label": "Content Align", + "options": "Left\nCentre\nRight", "reqd": 0 }, { @@ -263,6 +265,7 @@ "fieldname": "slide_5_content_align", "fieldtype": "Select", "label": "Content Align", + "options": "Left\nCentre\nRight", "reqd": 0 }, { @@ -274,7 +277,7 @@ } ], "idx": 2, - "modified": "2021-02-24 15:57:05.889709", + "modified": "2023-05-12 15:03:57.604060", "modified_by": "Administrator", "module": "E-commerce", "name": "Hero Slider", From 6d121b8107a80c48738dd4d864f9f72f91974457 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Sat, 13 May 2023 13:00:05 +0530 Subject: [PATCH 10/27] fix: inventory dimension for inter company transfer return use case (cherry picked from commit 38aaba5720b28d0360339b5fb87d955eab392f6f) --- erpnext/controllers/accounts_controller.py | 3 + erpnext/controllers/stock_controller.py | 18 +- .../test_inventory_dimension.py | 171 ++++++++++++++++++ 3 files changed, 190 insertions(+), 2 deletions(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index d0ec6541623..3d930d67e04 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -392,6 +392,9 @@ class AccountsController(TransactionBase): ) def validate_inter_company_reference(self): + if self.get("is_return"): + return + if self.doctype not in ("Purchase Invoice", "Purchase Receipt"): return diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index 796a069651c..09089be8614 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -449,8 +449,22 @@ class StockController(AccountsController): "Delivery Note", "Stock Entry", ]: - if (sl_dict.actual_qty > 0 and self.doctype in ["Purchase Invoice", "Purchase Receipt"]) or ( - sl_dict.actual_qty < 0 and self.doctype in ["Sales Invoice", "Delivery Note", "Stock Entry"] + if ( + ( + sl_dict.actual_qty > 0 + and not self.get("is_return") + or sl_dict.actual_qty < 0 + and self.get("is_return") + ) + and self.doctype in ["Purchase Invoice", "Purchase Receipt"] + ) or ( + ( + sl_dict.actual_qty < 0 + and not self.get("is_return") + or sl_dict.actual_qty > 0 + and self.get("is_return") + ) + and self.doctype in ["Sales Invoice", "Delivery Note", "Stock Entry"] ): sl_dict[dimension.target_fieldname] = row.get(dimension.source_fieldname) else: diff --git a/erpnext/stock/doctype/inventory_dimension/test_inventory_dimension.py b/erpnext/stock/doctype/inventory_dimension/test_inventory_dimension.py index ae5f521f2b8..2d273c66fa6 100644 --- a/erpnext/stock/doctype/inventory_dimension/test_inventory_dimension.py +++ b/erpnext/stock/doctype/inventory_dimension/test_inventory_dimension.py @@ -4,6 +4,7 @@ import frappe from frappe.custom.doctype.custom_field.custom_field import create_custom_field from frappe.tests.utils import FrappeTestCase +from frappe.utils import nowdate, nowtime from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note from erpnext.stock.doctype.inventory_dimension.inventory_dimension import ( @@ -257,6 +258,8 @@ class TestInventoryDimension(FrappeTestCase): ) def test_for_purchase_sales_and_stock_transaction(self): + from erpnext.controllers.sales_and_purchase_return import make_return_doc + create_inventory_dimension( reference_document="Store", type_of_transaction="Outward", @@ -319,6 +322,98 @@ class TestInventoryDimension(FrappeTestCase): self.assertEqual(entries[0].store, "Store 2") self.assertEqual(entries[0].actual_qty, -10.0) + return_dn = make_return_doc("Delivery Note", dn_doc.name) + return_dn.submit() + entries = get_voucher_sl_entries(return_dn.name, ["warehouse", "store", "actual_qty"]) + + self.assertEqual(entries[0].warehouse, warehouse) + self.assertEqual(entries[0].store, "Store 2") + self.assertEqual(entries[0].actual_qty, 10.0) + + se_doc = make_stock_entry( + item_code=item_code, qty=10, from_warehouse=warehouse, to_warehouse=warehouse, do_not_save=True + ) + + se_doc.items[0].store = "Store 2" + se_doc.items[0].to_store = "Store 1" + + se_doc.save() + se_doc.submit() + + return_pr = make_return_doc("Purchase Receipt", pr_doc.name) + return_pr.submit() + entries = get_voucher_sl_entries(return_pr.name, ["warehouse", "store", "actual_qty"]) + + self.assertEqual(entries[0].warehouse, warehouse) + self.assertEqual(entries[0].store, "Store 1") + self.assertEqual(entries[0].actual_qty, -10.0) + + def test_inter_transfer_return_against_inventory_dimension(self): + from erpnext.controllers.sales_and_purchase_return import make_return_doc + from erpnext.stock.doctype.delivery_note.delivery_note import make_inter_company_purchase_receipt + + data = prepare_data_for_internal_transfer() + + dn_doc = create_delivery_note( + customer=data.customer, + company=data.company, + warehouse=data.from_warehouse, + target_warehouse=data.to_warehouse, + qty=5, + cost_center=data.cost_center, + expense_account=data.expense_account, + do_not_submit=True, + ) + + dn_doc.items[0].store = "Inter Transfer Store 1" + dn_doc.items[0].to_store = "Inter Transfer Store 2" + dn_doc.save() + dn_doc.submit() + + for d in get_voucher_sl_entries(dn_doc.name, ["store", "actual_qty"]): + if d.actual_qty > 0: + self.assertEqual(d.store, "Inter Transfer Store 2") + else: + self.assertEqual(d.store, "Inter Transfer Store 1") + + pr_doc = make_inter_company_purchase_receipt(dn_doc.name) + pr_doc.items[0].warehouse = data.store_warehouse + pr_doc.items[0].from_store = "Inter Transfer Store 2" + pr_doc.items[0].store = "Inter Transfer Store 3" + pr_doc.save() + pr_doc.submit() + + for d in get_voucher_sl_entries(pr_doc.name, ["store", "actual_qty"]): + if d.actual_qty > 0: + self.assertEqual(d.store, "Inter Transfer Store 3") + else: + self.assertEqual(d.store, "Inter Transfer Store 2") + + return_doc = make_return_doc("Purchase Receipt", pr_doc.name) + return_doc.submit() + + for d in get_voucher_sl_entries(return_doc.name, ["store", "actual_qty"]): + if d.actual_qty > 0: + self.assertEqual(d.store, "Inter Transfer Store 2") + else: + self.assertEqual(d.store, "Inter Transfer Store 3") + + dn_doc.load_from_db() + + return_doc1 = make_return_doc("Delivery Note", dn_doc.name) + return_doc1.posting_date = nowdate() + return_doc1.posting_time = nowtime() + return_doc1.items[0].target_warehouse = dn_doc.items[0].target_warehouse + return_doc1.items[0].warehouse = dn_doc.items[0].warehouse + return_doc1.save() + return_doc1.submit() + + for d in get_voucher_sl_entries(return_doc1.name, ["store", "actual_qty"]): + if d.actual_qty > 0: + self.assertEqual(d.store, "Inter Transfer Store 1") + else: + self.assertEqual(d.store, "Inter Transfer Store 2") + def get_voucher_sl_entries(voucher_no, fields): return frappe.get_all( @@ -423,3 +518,79 @@ def create_inventory_dimension(**args): doc.insert(ignore_permissions=True) return doc + + +def prepare_data_for_internal_transfer(): + from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier + from erpnext.selling.doctype.customer.test_customer import create_internal_customer + from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt + from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse + + company = "_Test Company with perpetual inventory" + + customer = create_internal_customer( + "_Test Internal Customer 3", + company, + company, + ) + + supplier = create_internal_supplier( + "_Test Internal Supplier 3", + company, + company, + ) + + for store in ["Inter Transfer Store 1", "Inter Transfer Store 2", "Inter Transfer Store 3"]: + if not frappe.db.exists("Store", store): + frappe.get_doc({"doctype": "Store", "store_name": store}).insert(ignore_permissions=True) + + warehouse = create_warehouse("_Test Internal Warehouse New A", company=company) + + to_warehouse = create_warehouse("_Test Internal Warehouse GIT A", company=company) + + pr_doc = make_purchase_receipt( + company=company, warehouse=warehouse, qty=10, rate=100, do_not_submit=True + ) + pr_doc.items[0].store = "Inter Transfer Store 1" + pr_doc.submit() + + if not frappe.db.get_value("Company", company, "unrealized_profit_loss_account"): + account = "Unrealized Profit and Loss - TCP1" + if not frappe.db.exists("Account", account): + frappe.get_doc( + { + "doctype": "Account", + "account_name": "Unrealized Profit and Loss", + "parent_account": "Direct Income - TCP1", + "company": company, + "is_group": 0, + "account_type": "Income Account", + } + ).insert() + + frappe.db.set_value("Company", company, "unrealized_profit_loss_account", account) + + cost_center = frappe.db.get_value("Company", company, "cost_center") or frappe.db.get_value( + "Cost Center", {"company": company}, "name" + ) + + expene_account = frappe.db.get_value( + "Company", company, "stock_adjustment_account" + ) or frappe.db.get_value( + "Account", {"company": company, "account_type": "Expense Account"}, "name" + ) + + return frappe._dict( + { + "from_warehouse": warehouse, + "to_warehouse": to_warehouse, + "customer": customer, + "supplier": supplier, + "company": company, + "cost_center": cost_center, + "expene_account": expene_account, + "store_warehouse": frappe.db.get_value( + "Warehouse", {"name": ("like", "Store%"), "company": company}, "name" + ), + } + ) From 2772a911ed98b6c7e2b9e2b3203a775ebeac6ff6 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Sat, 13 May 2023 15:14:41 +0530 Subject: [PATCH 11/27] fix: typo --- .../doctype/inventory_dimension/test_inventory_dimension.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/doctype/inventory_dimension/test_inventory_dimension.py b/erpnext/stock/doctype/inventory_dimension/test_inventory_dimension.py index 2d273c66fa6..eca50691870 100644 --- a/erpnext/stock/doctype/inventory_dimension/test_inventory_dimension.py +++ b/erpnext/stock/doctype/inventory_dimension/test_inventory_dimension.py @@ -574,7 +574,7 @@ def prepare_data_for_internal_transfer(): "Cost Center", {"company": company}, "name" ) - expene_account = frappe.db.get_value( + expense_account = frappe.db.get_value( "Company", company, "stock_adjustment_account" ) or frappe.db.get_value( "Account", {"company": company, "account_type": "Expense Account"}, "name" @@ -588,7 +588,7 @@ def prepare_data_for_internal_transfer(): "supplier": supplier, "company": company, "cost_center": cost_center, - "expene_account": expene_account, + "expense_account": expense_account, "store_warehouse": frappe.db.get_value( "Warehouse", {"name": ("like", "Store%"), "company": company}, "name" ), From fe9e0c2121d869546d0315adcacd62ed52223543 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Sun, 14 May 2023 11:44:50 +0530 Subject: [PATCH 12/27] fix: allow over-payment against SO (#35079) fix: allow over-payment against SO (#35079) (cherry picked from commit 870b02b03cec923bedf1fa75ffdc488ca958ded7) Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com> --- erpnext/selling/doctype/sales_order/sales_order.js | 2 +- erpnext/startup/boot.py | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/erpnext/selling/doctype/sales_order/sales_order.js b/erpnext/selling/doctype/sales_order/sales_order.js index 449d461561a..e9a6cc385d3 100644 --- a/erpnext/selling/doctype/sales_order/sales_order.js +++ b/erpnext/selling/doctype/sales_order/sales_order.js @@ -264,7 +264,7 @@ erpnext.selling.SalesOrderController = class SalesOrderController extends erpnex } } // payment request - if(flt(doc.per_billed)<100) { + if(flt(doc.per_billed, precision('per_billed', doc)) < 100 + frappe.boot.sysdefaults.over_billing_allowance) { this.frm.add_custom_button(__('Payment Request'), () => this.make_payment_request(), __('Create')); this.frm.add_custom_button(__('Payment'), () => this.make_payment_entry(), __('Create')); } diff --git a/erpnext/startup/boot.py b/erpnext/startup/boot.py index 62936fcfb89..db1cc494e0b 100644 --- a/erpnext/startup/boot.py +++ b/erpnext/startup/boot.py @@ -21,6 +21,10 @@ def boot_session(bootinfo): bootinfo.sysdefaults.allow_stale = cint( frappe.db.get_single_value("Accounts Settings", "allow_stale") ) + bootinfo.sysdefaults.over_billing_allowance = frappe.db.get_single_value( + "Accounts Settings", "over_billing_allowance" + ) + bootinfo.sysdefaults.quotation_valid_till = cint( frappe.db.get_single_value("CRM Settings", "default_valid_till") ) From 26928b395bc25f2733ecdc49ce21939cf9d172de Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Sun, 14 May 2023 11:54:15 +0530 Subject: [PATCH 13/27] fix: function `batch_no` should only be declared once (#35115) fix: function `batch_no` should only be declared once (#35115) fix: remove twice event call of `batch_no` to update batch qty (cherry picked from commit 19cd68778485525bcfcbc0105c4c2591e9aabff1) Co-authored-by: Daizy Modi --- erpnext/selling/sales_common.js | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/erpnext/selling/sales_common.js b/erpnext/selling/sales_common.js index f1df3a11de4..e3de49c57d8 100644 --- a/erpnext/selling/sales_common.js +++ b/erpnext/selling/sales_common.js @@ -299,7 +299,8 @@ erpnext.selling.SellingController = class SellingController extends erpnext.Tran } batch_no(doc, cdt, cdn) { - var me = this; + super.batch_no(doc, cdt, cdn); + var item = frappe.get_doc(cdt, cdn); if (item.serial_no) { @@ -378,10 +379,6 @@ erpnext.selling.SellingController = class SellingController extends erpnext.Tran } } - batch_no(doc, cdt, cdn) { - super.batch_no(doc, cdt, cdn); - } - qty(doc, cdt, cdn) { super.qty(doc, cdt, cdn); From 3f8928be5cca77b6a4cc95464623cf2989ef463a Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Sun, 14 May 2023 15:49:58 +0530 Subject: [PATCH 14/27] fix: test case --- .../doctype/inventory_dimension/test_inventory_dimension.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/doctype/inventory_dimension/test_inventory_dimension.py b/erpnext/stock/doctype/inventory_dimension/test_inventory_dimension.py index eca50691870..b1d7f8f00c6 100644 --- a/erpnext/stock/doctype/inventory_dimension/test_inventory_dimension.py +++ b/erpnext/stock/doctype/inventory_dimension/test_inventory_dimension.py @@ -529,13 +529,13 @@ def prepare_data_for_internal_transfer(): company = "_Test Company with perpetual inventory" customer = create_internal_customer( - "_Test Internal Customer 3", + "_Test Internal Customer 2", company, company, ) supplier = create_internal_supplier( - "_Test Internal Supplier 3", + "_Test Internal Supplier 2", company, company, ) From 3a7c69fc71548f2bc6d9e853e1228c6d6b490aba Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 15 May 2023 13:16:47 +0530 Subject: [PATCH 15/27] fix: sales person allocated amount calculation error nonetype and float (#35293) fix: sales person allocated amount calculation error nonetype and float (#35293) fix: sales person allocated amount calculation error nontype and float (cherry picked from commit 0c8276ec82c1d4b8cfd1d852b6e2e9a8c3cd3f1a) Co-authored-by: Indrajith.vs <91895505+Gubbu77@users.noreply.github.com> --- erpnext/controllers/selling_controller.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py index 590e00fb11e..3d47ee797da 100644 --- a/erpnext/controllers/selling_controller.py +++ b/erpnext/controllers/selling_controller.py @@ -171,7 +171,7 @@ class SellingController(StockController): self.round_floats_in(sales_person) sales_person.allocated_amount = flt( - self.amount_eligible_for_commission * sales_person.allocated_percentage / 100.0, + flt(self.amount_eligible_for_commission) * sales_person.allocated_percentage / 100.0, self.precision("allocated_amount", sales_person), ) From 9a8ee62d5ac2bd5bd874e5296751128c5cc83cca Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Wed, 3 May 2023 09:51:58 +0530 Subject: [PATCH 16/27] fix: recalculate costs in SCR while reposting (cherry picked from commit a6cb6c6f4784194af96d15ece3499415be05d53e) --- erpnext/stock/stock_ledger.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index 103ed4ac3d0..558c0948c9c 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -781,13 +781,19 @@ class update_entries_after(object): d.db_update() def update_rate_on_subcontracting_receipt(self, sle, outgoing_rate): - if frappe.db.exists(sle.voucher_type + " Item", sle.voucher_detail_no): - frappe.db.set_value(sle.voucher_type + " Item", sle.voucher_detail_no, "rate", outgoing_rate) + if frappe.db.exists("Subcontracting Receipt Item", sle.voucher_detail_no): + frappe.db.set_value("Subcontracting Receipt Item", sle.voucher_detail_no, "rate", outgoing_rate) else: frappe.db.set_value( "Subcontracting Receipt Supplied Item", sle.voucher_detail_no, "rate", outgoing_rate ) + scr = frappe.get_doc("Subcontracting Receipt", sle.voucher_no, for_update=True) + scr.set_missing_values() + scr.db_update() + for d in scr.items + scr.get("supplied_items", []): + d.db_update() + def get_serialized_values(self, sle): incoming_rate = flt(sle.incoming_rate) actual_qty = flt(sle.actual_qty) From 4c8dbeddec8b67f7c8c2e0e39b90f0e5449a11f0 Mon Sep 17 00:00:00 2001 From: Sagar Sharma Date: Thu, 4 May 2023 15:07:52 +0530 Subject: [PATCH 17/27] test: add test case (cherry picked from commit e0b22edb2e4fba3224b3dfd26243bcf483a3d71c) --- .../test_subcontracting_receipt.py | 68 ++++++++++++++++++- 1 file changed, 67 insertions(+), 1 deletion(-) diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py b/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py index 72ed4d4e2ef..dfb72c33567 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py @@ -6,7 +6,7 @@ import copy import frappe from frappe.tests.utils import FrappeTestCase -from frappe.utils import cint, flt +from frappe.utils import add_days, cint, cstr, flt, today import erpnext from erpnext.accounts.doctype.account.test_account import get_inventory_account @@ -26,6 +26,9 @@ from erpnext.controllers.tests.test_subcontracting_controller import ( from erpnext.stock.doctype.item.test_item import make_item from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import get_gl_entries from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry +from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import ( + create_stock_reconciliation, +) from erpnext.subcontracting.doctype.subcontracting_order.subcontracting_order import ( make_subcontracting_receipt, ) @@ -528,6 +531,69 @@ class TestSubcontractingReceipt(FrappeTestCase): # consumed_qty should be (accepted_qty * qty_consumed_per_unit) = (6 * 1) = 6 self.assertEqual(scr.supplied_items[0].consumed_qty, 6) + def test_supplied_items_cost_after_reposting(self): + # Set Backflush Based On as "BOM" + set_backflush_based_on("BOM") + + # Create Material Receipt for RM's + make_stock_entry( + item_code="_Test Item", + qty=100, + target="_Test Warehouse 1 - _TC", + basic_rate=100, + posting_date=add_days(today(), -2), + ) + make_stock_entry( + item_code="_Test Item Home Desktop 100", + qty=100, + target="_Test Warehouse 1 - _TC", + basic_rate=100, + ) + + service_items = [ + { + "warehouse": "_Test Warehouse - _TC", + "item_code": "Subcontracted Service Item 1", + "qty": 10, + "rate": 100, + "fg_item": "_Test FG Item", + "fg_item_qty": 10, + }, + ] + + # Create Subcontracting Order + sco = get_subcontracting_order(service_items=service_items) + + # Transfer RM's + rm_items = get_rm_items(sco.supplied_items) + + itemwise_details = make_stock_in_entry(rm_items=rm_items) + make_stock_transfer_entry( + sco_no=sco.name, + rm_items=rm_items, + itemwise_details=copy.deepcopy(itemwise_details), + ) + + # Create Subcontracting Receipt + scr = make_subcontracting_receipt(sco.name) + scr.save() + scr.submit() + + # Create Backdated Stock Reconciliation + sr = create_stock_reconciliation( + item_code=rm_items[0].get("item_code"), + warehouse="_Test Warehouse 1 - _TC", + qty=100, + rate=50, + posting_date=add_days(today(), -1), + ) + + # Cost should be updated in Subcontracting Receipt after reposting + prev_cost = scr.supplied_items[0].rate + scr.load_from_db() + self.assertNotEqual(scr.supplied_items[0].rate, prev_cost) + self.assertEqual(scr.supplied_items[0].rate, sr.items[0].valuation_rate) + def make_return_subcontracting_receipt(**args): args = frappe._dict(args) From 0575b105d0c9c379c603d9acaa1ffa6f6890bdc6 Mon Sep 17 00:00:00 2001 From: Sagar Sharma Date: Fri, 12 May 2023 11:45:33 +0530 Subject: [PATCH 18/27] refactor(minor): rename function to be more descriptive (cherry picked from commit d6433f803b497511873acb7ff3889853e4b06e0e) --- erpnext/controllers/subcontracting_controller.py | 2 +- .../tests/test_subcontracting_controller.py | 2 +- .../subcontracting_order/subcontracting_order.py | 14 +++++++------- .../subcontracting_receipt.py | 10 +++++----- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/erpnext/controllers/subcontracting_controller.py b/erpnext/controllers/subcontracting_controller.py index 05754293b77..c3fa894e895 100644 --- a/erpnext/controllers/subcontracting_controller.py +++ b/erpnext/controllers/subcontracting_controller.py @@ -741,7 +741,7 @@ class SubcontractingController(StockController): sco_doc = frappe.get_doc("Subcontracting Order", sco) sco_doc.update_status() - def set_missing_values_in_additional_costs(self): + def calculate_additional_costs(self): self.total_additional_costs = sum(flt(item.amount) for item in self.get("additional_costs")) if self.total_additional_costs: diff --git a/erpnext/controllers/tests/test_subcontracting_controller.py b/erpnext/controllers/tests/test_subcontracting_controller.py index 0e6fe95d45f..4ea4fd11b4e 100644 --- a/erpnext/controllers/tests/test_subcontracting_controller.py +++ b/erpnext/controllers/tests/test_subcontracting_controller.py @@ -36,7 +36,7 @@ class TestSubcontractingController(FrappeTestCase): sco.remove_empty_rows() self.assertEqual((len_before - 1), len(sco.service_items)) - def test_set_missing_values_in_additional_costs(self): + def test_calculate_additional_costs(self): sco = get_subcontracting_order(do_not_submit=1) rate_without_additional_cost = sco.items[0].rate diff --git a/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.py b/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.py index e6de72d494d..39197332c1c 100644 --- a/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.py +++ b/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.py @@ -77,22 +77,22 @@ class SubcontractingOrder(SubcontractingController): frappe.throw(_(msg)) def set_missing_values(self): - self.set_missing_values_in_additional_costs() - self.set_missing_values_in_service_items() - self.set_missing_values_in_supplied_items() - self.set_missing_values_in_items() + self.calculate_additional_costs() + self.calculate_service_costs() + self.calculate_supplied_items_qty_and_amount() + self.calculate_items_qty_and_amount() - def set_missing_values_in_service_items(self): + def calculate_service_costs(self): for idx, item in enumerate(self.get("service_items")): self.items[idx].service_cost_per_qty = item.amount / self.items[idx].qty - def set_missing_values_in_supplied_items(self): + def calculate_supplied_items_qty_and_amount(self): for item in self.get("items"): bom = frappe.get_doc("BOM", item.bom) rm_cost = sum(flt(rm_item.amount) for rm_item in bom.items) item.rm_cost_per_qty = rm_cost / flt(bom.quantity) - def set_missing_values_in_items(self): + def calculate_items_qty_and_amount(self): total_qty = total = 0 for item in self.items: item.rate = item.rm_cost_per_qty + item.service_cost_per_qty + flt(item.additional_cost_per_qty) diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py index 4f8e045d706..2c842622730 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py @@ -113,9 +113,9 @@ class SubcontractingReceipt(SubcontractingController): @frappe.whitelist() def set_missing_values(self): - self.set_missing_values_in_additional_costs() - self.set_missing_values_in_supplied_items() - self.set_missing_values_in_items() + self.calculate_additional_costs() + self.calculate_supplied_items_qty_and_amount() + self.calculate_items_qty_and_amount() def set_available_qty_for_consumption(self): supplied_items_details = {} @@ -147,13 +147,13 @@ class SubcontractingReceipt(SubcontractingController): item.rm_item_code, 0 ) - def set_missing_values_in_supplied_items(self): + def calculate_supplied_items_qty_and_amount(self): for item in self.get("supplied_items") or []: item.amount = item.rate * item.consumed_qty self.set_available_qty_for_consumption() - def set_missing_values_in_items(self): + def calculate_items_qty_and_amount(self): rm_supp_cost = {} for item in self.get("supplied_items") or []: if item.reference_name in rm_supp_cost: From a8fc17e0aec5200451b257978ea8f84534c3af43 Mon Sep 17 00:00:00 2001 From: Sagar Sharma Date: Fri, 12 May 2023 11:46:32 +0530 Subject: [PATCH 19/27] refactor: use `calculate_items_qty_and_amount()` to update scr items rate (cherry picked from commit 9c72c2a6cb11197b292b0f589f312e038ac5cc3a) --- erpnext/stock/stock_ledger.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index 558c0948c9c..6106809273f 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -785,13 +785,15 @@ class update_entries_after(object): frappe.db.set_value("Subcontracting Receipt Item", sle.voucher_detail_no, "rate", outgoing_rate) else: frappe.db.set_value( - "Subcontracting Receipt Supplied Item", sle.voucher_detail_no, "rate", outgoing_rate + "Subcontracting Receipt Supplied Item", + sle.voucher_detail_no, + {"rate": outgoing_rate, "amount": abs(sle.actual_qty) * outgoing_rate}, ) scr = frappe.get_doc("Subcontracting Receipt", sle.voucher_no, for_update=True) - scr.set_missing_values() + scr.calculate_items_qty_and_amount() scr.db_update() - for d in scr.items + scr.get("supplied_items", []): + for d in scr.items: d.db_update() def get_serialized_values(self, sle): From bc88415e73d24ed96c90cfc50c51741b2ac2cfe6 Mon Sep 17 00:00:00 2001 From: vishnu Date: Mon, 15 May 2023 14:50:40 +0000 Subject: [PATCH 20/27] fix: update workstation hour rate when workstation change in job card --- erpnext/manufacturing/doctype/job_card/job_card.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erpnext/manufacturing/doctype/job_card/job_card.py b/erpnext/manufacturing/doctype/job_card/job_card.py index 877362dcba4..4a4046e47aa 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.py +++ b/erpnext/manufacturing/doctype/job_card/job_card.py @@ -562,6 +562,7 @@ class JobCard(Document): ) def update_work_order_data(self, for_quantity, time_in_mins, wo): + workstation_hour_rate = frappe.get_value("Workstation", self.workstation, "hour_rate") jc = frappe.qb.DocType("Job Card") jctl = frappe.qb.DocType("Job Card Time Log") @@ -587,6 +588,7 @@ class JobCard(Document): if data.get("workstation") != self.workstation: # workstations can change in a job card data.workstation = self.workstation + data.hour_rate = flt(workstation_hour_rate) wo.flags.ignore_validate_update_after_submit = True wo.update_operation_status() From d9efa662d47f340b3070486688c621194c62840d Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Tue, 16 May 2023 00:48:52 +0530 Subject: [PATCH 21/27] fix: item list view not working (cherry picked from commit 0489e302442a6e8a643dbabda922d8c2258dd53e) --- erpnext/stock/doctype/item/item_list.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/item/item_list.js b/erpnext/stock/doctype/item/item_list.js index 534b3419203..22d38e88935 100644 --- a/erpnext/stock/doctype/item/item_list.js +++ b/erpnext/stock/doctype/item/item_list.js @@ -1,5 +1,5 @@ frappe.listview_settings['Item'] = { - add_fields: ["item_name", "stock_uom", "item_group", "image", "variant_of", + add_fields: ["item_name", "stock_uom", "item_group", "image", "has_variants", "end_of_life", "disabled"], filters: [["disabled", "=", "0"]], From 42037f9f739135505eab3eb3668abef3b3d8e3eb Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 16 May 2023 07:29:48 +0530 Subject: [PATCH 22/27] fix: port option for additional_conditions in item wise sales register (#35187) fix: port option for additional_conditions in item wise sales register (#35187) Co-authored-by: Deepesh Garg (cherry picked from commit 2a609616d9da0eaf078ef3f6763aa4b643eafc97) Co-authored-by: Smit Vora --- .../item_wise_sales_register.py | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py b/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py index c987231fe17..dd9c0736128 100644 --- a/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py +++ b/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py @@ -19,14 +19,19 @@ def execute(filters=None): return _execute(filters) -def _execute(filters=None, additional_table_columns=None, additional_query_columns=None): +def _execute( + filters=None, + additional_table_columns=None, + additional_query_columns=None, + additional_conditions=None, +): if not filters: filters = {} columns = get_columns(additional_table_columns, filters) company_currency = frappe.get_cached_value("Company", filters.get("company"), "default_currency") - item_list = get_items(filters, additional_query_columns) + item_list = get_items(filters, additional_query_columns, additional_conditions) if item_list: itemised_tax, tax_columns = get_tax_accounts(item_list, columns, company_currency) @@ -328,7 +333,7 @@ def get_columns(additional_table_columns, filters): return columns -def get_conditions(filters): +def get_conditions(filters, additional_conditions=None): conditions = "" for opts in ( @@ -341,6 +346,9 @@ def get_conditions(filters): if filters.get(opts[0]): conditions += opts[1] + if additional_conditions: + conditions += additional_conditions + if filters.get("mode_of_payment"): conditions += """ and exists(select name from `tabSales Invoice Payment` where parent=`tabSales Invoice`.name @@ -376,8 +384,8 @@ def get_group_by_conditions(filters, doctype): return "ORDER BY `tab{0}`.{1}".format(doctype, frappe.scrub(filters.get("group_by"))) -def get_items(filters, additional_query_columns): - conditions = get_conditions(filters) +def get_items(filters, additional_query_columns, additional_conditions=None): + conditions = get_conditions(filters, additional_conditions) if additional_query_columns: additional_query_columns = ", " + ", ".join(additional_query_columns) From 0e7840301f459005e0003b24578d93df9d45fcb8 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 16 May 2023 13:44:35 +0530 Subject: [PATCH 23/27] perf: cache and simplify queries for holiday list (backport #35315) (#35318) Co-authored-by: Rucha Mahabal --- erpnext/setup/doctype/employee/employee.py | 4 +++- erpnext/setup/doctype/holiday_list/holiday_list.py | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/erpnext/setup/doctype/employee/employee.py b/erpnext/setup/doctype/employee/employee.py index 13a6f20db29..78f2e4935e7 100755 --- a/erpnext/setup/doctype/employee/employee.py +++ b/erpnext/setup/doctype/employee/employee.py @@ -281,7 +281,9 @@ def get_employee_email(employee_doc): def get_holiday_list_for_employee(employee, raise_exception=True): if employee: - holiday_list, company = frappe.db.get_value("Employee", employee, ["holiday_list", "company"]) + holiday_list, company = frappe.get_cached_value( + "Employee", employee, ["holiday_list", "company"] + ) else: holiday_list = "" company = frappe.db.get_single_value("Global Defaults", "default_company") diff --git a/erpnext/setup/doctype/holiday_list/holiday_list.py b/erpnext/setup/doctype/holiday_list/holiday_list.py index fad827ad8ad..84d0d352871 100644 --- a/erpnext/setup/doctype/holiday_list/holiday_list.py +++ b/erpnext/setup/doctype/holiday_list/holiday_list.py @@ -115,6 +115,8 @@ def is_holiday(holiday_list, date=None): if date is None: date = today() if holiday_list: - return bool(frappe.get_all("Holiday List", dict(name=holiday_list, holiday_date=date))) + return bool( + frappe.db.exists("Holiday", {"parent": holiday_list, "holiday_date": date}, cache=True) + ) else: return False From f8c58b6893ef9b747ffd510ab16bd03666f5f472 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 16 May 2023 16:00:25 +0530 Subject: [PATCH 24/27] fix(UX): misc "home" onboarding improvements (backport #35319) (#35321) fix(UX): misc "home" onboarding improvements (#35319) * fix(UX): cleanup "Home" onboarding - Remove company, letterhead, data import etc from home onboarding step * fix(UX): Show quick entry for item * chore: fix copy here and there (cherry picked from commit 5574d9a72d8e4d372e01035ab44a1254538feb50) Co-authored-by: Ankush Menat --- .../setup/module_onboarding/home/home.json | 19 +++++-------------- .../company_set_up/company_set_up.json | 4 ++-- .../create_a_customer/create_a_customer.json | 6 +++--- .../create_a_quotation.json | 4 ++-- .../create_a_supplier/create_a_supplier.json | 6 +++--- .../create_an_item/create_an_item.json | 8 ++++---- .../data_import/data_import.json | 4 ++-- .../letterhead/letterhead.json | 4 ++-- .../navigation_help/navigation_help.json | 6 +++--- 9 files changed, 26 insertions(+), 35 deletions(-) diff --git a/erpnext/setup/module_onboarding/home/home.json b/erpnext/setup/module_onboarding/home/home.json index f02fc454c00..516f12229c5 100644 --- a/erpnext/setup/module_onboarding/home/home.json +++ b/erpnext/setup/module_onboarding/home/home.json @@ -22,24 +22,18 @@ "creation": "2021-11-22 12:19:15.888642", "docstatus": 0, "doctype": "Module Onboarding", - "documentation_url": "https://docs.erpnext.com/docs/v13/user/manual/en/setting-up/company-setup", + "documentation_url": "https://docs.erpnext.com/docs/v14/user/manual/en/setting-up/company-setup", "idx": 0, "is_complete": 0, - "modified": "2022-06-07 14:31:00.575193", + "modified": "2023-05-16 13:13:24.043792", "modified_by": "Administrator", "module": "Setup", "name": "Home", "owner": "Administrator", "steps": [ - { - "step": "Company Set Up" - }, { "step": "Navigation Help" }, - { - "step": "Data import" - }, { "step": "Create an Item" }, @@ -51,12 +45,9 @@ }, { "step": "Create a Quotation" - }, - { - "step": "Letterhead" } ], - "subtitle": "Company, Item, Customer, Supplier, Navigation Help, Data Import, Letter Head, Quotation", - "success_message": "Masters are all set up!", - "title": "Let's Set Up Some Masters" + "subtitle": "Item, Customer, Supplier, Navigation Help and Quotation", + "success_message": "You're ready to start your journey with ERPNext", + "title": "Let's begin your journey with ERPNext" } \ No newline at end of file diff --git a/erpnext/setup/onboarding_step/company_set_up/company_set_up.json b/erpnext/setup/onboarding_step/company_set_up/company_set_up.json index 6f6583231f9..fae2de01129 100644 --- a/erpnext/setup/onboarding_step/company_set_up/company_set_up.json +++ b/erpnext/setup/onboarding_step/company_set_up/company_set_up.json @@ -5,11 +5,11 @@ "description": "# Set Up a Company\n\nA company is a legal entity for which you will set up your books of account and create accounting transactions. In ERPNext, you can create multiple companies, and establish relationships (group/subsidiary) among them.\n\nWithin the company master, you can capture various default accounts for that Company and set crucial settings related to the accounting methodology followed for a company.\n", "docstatus": 0, "doctype": "Onboarding Step", - "idx": 0, + "idx": 1, "is_complete": 0, "is_single": 0, "is_skipped": 0, - "modified": "2021-12-15 14:22:18.317423", + "modified": "2023-05-15 09:18:42.895537", "modified_by": "Administrator", "name": "Company Set Up", "owner": "Administrator", diff --git a/erpnext/setup/onboarding_step/create_a_customer/create_a_customer.json b/erpnext/setup/onboarding_step/create_a_customer/create_a_customer.json index f74d745be9c..e1a8f908663 100644 --- a/erpnext/setup/onboarding_step/create_a_customer/create_a_customer.json +++ b/erpnext/setup/onboarding_step/create_a_customer/create_a_customer.json @@ -5,17 +5,17 @@ "description": "# Create a Customer\n\nThe Customer master is at the heart of your sales transactions. Customers are linked in Quotations, Sales Orders, Invoices, and Payments. Customers can be either numbered or identified by name (you would typically do this based on the number of customers you have).\n\nThrough Customer\u2019s master, you can effectively track essentials like:\n - Customer\u2019s multiple address and contacts\n - Account Receivables\n - Credit Limit and Credit Period\n", "docstatus": 0, "doctype": "Onboarding Step", - "idx": 0, + "idx": 1, "is_complete": 0, "is_single": 0, "is_skipped": 0, - "modified": "2021-12-15 14:20:31.197564", + "modified": "2023-05-16 12:54:54.112364", "modified_by": "Administrator", "name": "Create a Customer", "owner": "Administrator", "reference_document": "Customer", "show_form_tour": 0, "show_full_form": 0, - "title": "Manage Customers", + "title": "Create a Customer", "validate_action": 1 } \ No newline at end of file diff --git a/erpnext/setup/onboarding_step/create_a_quotation/create_a_quotation.json b/erpnext/setup/onboarding_step/create_a_quotation/create_a_quotation.json index 8bdb621c0a5..92b45b4ff80 100644 --- a/erpnext/setup/onboarding_step/create_a_quotation/create_a_quotation.json +++ b/erpnext/setup/onboarding_step/create_a_quotation/create_a_quotation.json @@ -5,11 +5,11 @@ "description": "# Create a Quotation\n\nLet\u2019s get started with business transactions by creating your first Quotation. You can create a Quotation for an existing customer or a prospect. It will be an approved document, with items you sell and the proposed price + taxes applied. After completing the instructions, you will get a Quotation in a ready to share print format.", "docstatus": 0, "doctype": "Onboarding Step", - "idx": 0, + "idx": 1, "is_complete": 0, "is_single": 0, "is_skipped": 0, - "modified": "2021-12-15 14:21:31.675330", + "modified": "2023-05-15 09:18:42.984170", "modified_by": "Administrator", "name": "Create a Quotation", "owner": "Administrator", diff --git a/erpnext/setup/onboarding_step/create_a_supplier/create_a_supplier.json b/erpnext/setup/onboarding_step/create_a_supplier/create_a_supplier.json index 9574141eaab..ef493fe00d1 100644 --- a/erpnext/setup/onboarding_step/create_a_supplier/create_a_supplier.json +++ b/erpnext/setup/onboarding_step/create_a_supplier/create_a_supplier.json @@ -5,17 +5,17 @@ "description": "# Create a Supplier\n\nAlso known as Vendor, is a master at the center of your purchase transactions. Suppliers are linked in Request for Quotation, Purchase Orders, Receipts, and Payments. Suppliers can be either numbered or identified by name.\n\nThrough Supplier\u2019s master, you can effectively track essentials like:\n - Supplier\u2019s multiple address and contacts\n - Account Receivables\n - Credit Limit and Credit Period\n", "docstatus": 0, "doctype": "Onboarding Step", - "idx": 0, + "idx": 1, "is_complete": 0, "is_single": 0, "is_skipped": 0, - "modified": "2021-12-15 14:21:23.518301", + "modified": "2023-05-16 12:55:08.610113", "modified_by": "Administrator", "name": "Create a Supplier", "owner": "Administrator", "reference_document": "Supplier", "show_form_tour": 0, "show_full_form": 0, - "title": "Manage Suppliers", + "title": "Create a Supplier", "validate_action": 1 } \ No newline at end of file diff --git a/erpnext/setup/onboarding_step/create_an_item/create_an_item.json b/erpnext/setup/onboarding_step/create_an_item/create_an_item.json index cd29683346c..15f36bec81d 100644 --- a/erpnext/setup/onboarding_step/create_an_item/create_an_item.json +++ b/erpnext/setup/onboarding_step/create_an_item/create_an_item.json @@ -6,18 +6,18 @@ "docstatus": 0, "doctype": "Onboarding Step", "form_tour": "Item General", - "idx": 0, + "idx": 1, "intro_video_url": "", "is_complete": 0, "is_single": 0, "is_skipped": 0, - "modified": "2021-12-15 14:19:56.297772", + "modified": "2023-05-16 12:56:40.355878", "modified_by": "Administrator", "name": "Create an Item", "owner": "Administrator", "reference_document": "Item", "show_form_tour": 1, - "show_full_form": 1, - "title": "Manage Items", + "show_full_form": 0, + "title": "Create an Item", "validate_action": 1 } \ No newline at end of file diff --git a/erpnext/setup/onboarding_step/data_import/data_import.json b/erpnext/setup/onboarding_step/data_import/data_import.json index 4999a368d37..e5dd7da29db 100644 --- a/erpnext/setup/onboarding_step/data_import/data_import.json +++ b/erpnext/setup/onboarding_step/data_import/data_import.json @@ -5,11 +5,11 @@ "description": "# Import Data from Spreadsheet\n\nIn ERPNext, you can easily migrate your historical data using spreadsheets. You can use it for migrating not just masters (like Customer, Supplier, Items), but also for transactions like (outstanding invoices, opening stock and accounting entries, etc).", "docstatus": 0, "doctype": "Onboarding Step", - "idx": 0, + "idx": 1, "is_complete": 0, "is_single": 0, "is_skipped": 0, - "modified": "2022-06-07 14:28:51.390813", + "modified": "2023-05-15 09:18:42.962231", "modified_by": "Administrator", "name": "Data import", "owner": "Administrator", diff --git a/erpnext/setup/onboarding_step/letterhead/letterhead.json b/erpnext/setup/onboarding_step/letterhead/letterhead.json index 8e1bb8ce827..584fd481ba1 100644 --- a/erpnext/setup/onboarding_step/letterhead/letterhead.json +++ b/erpnext/setup/onboarding_step/letterhead/letterhead.json @@ -5,11 +5,11 @@ "description": "# Create a Letter Head\n\nA Letter Head contains your organization's name, logo, address, etc which appears at the header and footer portion in documents. You can learn more about Setting up Letter Head in ERPNext here.\n", "docstatus": 0, "doctype": "Onboarding Step", - "idx": 0, + "idx": 1, "is_complete": 0, "is_single": 0, "is_skipped": 0, - "modified": "2021-12-15 14:21:39.037742", + "modified": "2023-05-15 09:18:42.995184", "modified_by": "Administrator", "name": "Letterhead", "owner": "Administrator", diff --git a/erpnext/setup/onboarding_step/navigation_help/navigation_help.json b/erpnext/setup/onboarding_step/navigation_help/navigation_help.json index cf07968bc7f..f57818b9f70 100644 --- a/erpnext/setup/onboarding_step/navigation_help/navigation_help.json +++ b/erpnext/setup/onboarding_step/navigation_help/navigation_help.json @@ -2,14 +2,14 @@ "action": "Watch Video", "action_label": "Learn about Navigation options", "creation": "2021-11-22 12:09:52.233872", - "description": "# Navigation in ERPNext\n\nEase of navigating and browsing around the ERPNext is one of our core strengths. In the following video, you will learn how to reach a specific feature in ERPNext via module page or awesome bar\u2019s shortcut.\n", + "description": "# Navigation in ERPNext\n\nEase of navigating and browsing around the ERPNext is one of our core strengths. In the following video, you will learn how to reach a specific feature in ERPNext via module page or AwesomeBar.", "docstatus": 0, "doctype": "Onboarding Step", - "idx": 0, + "idx": 1, "is_complete": 0, "is_single": 0, "is_skipped": 0, - "modified": "2022-06-07 14:28:00.901082", + "modified": "2023-05-16 12:53:25.939908", "modified_by": "Administrator", "name": "Navigation Help", "owner": "Administrator", From 6f96e5dcd4d066c722bb02b350cb2ddfb8c58770 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Tue, 16 May 2023 16:23:52 +0530 Subject: [PATCH 25/27] fix: force to do reposting for cancelled document (cherry picked from commit 6e661e7c0e8251f458d699254423cfb76dc3b5dd) --- erpnext/controllers/stock_controller.py | 3 +++ .../test_repost_item_valuation.py | 16 ++++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index 09089be8614..befde71775a 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -770,6 +770,9 @@ class StockController(AccountsController): } ) + if self.docstatus == 2: + force = True + if force or future_sle_exists(args) or repost_required_for_queue(self): item_based_reposting = cint( frappe.db.get_single_value("Stock Reposting Settings", "item_based_reposting") diff --git a/erpnext/stock/doctype/repost_item_valuation/test_repost_item_valuation.py b/erpnext/stock/doctype/repost_item_valuation/test_repost_item_valuation.py index 96ac4352dcd..9c4d997b316 100644 --- a/erpnext/stock/doctype/repost_item_valuation/test_repost_item_valuation.py +++ b/erpnext/stock/doctype/repost_item_valuation/test_repost_item_valuation.py @@ -376,3 +376,19 @@ class TestRepostItemValuation(FrappeTestCase, StockTestMixin): accounts_settings.acc_frozen_upto = "" accounts_settings.save() + + def test_create_repost_entry_for_cancelled_document(self): + pr = make_purchase_receipt( + company="_Test Company with perpetual inventory", + warehouse="Stores - TCP1", + get_multiple_items=True, + ) + + self.assertTrue(pr.docstatus == 1) + self.assertFalse(frappe.db.exists("Repost Item Valuation", {"voucher_no": pr.name})) + + pr.load_from_db() + + pr.cancel() + self.assertTrue(pr.docstatus == 2) + self.assertTrue(frappe.db.exists("Repost Item Valuation", {"voucher_no": pr.name})) From c41e1d7d71a005c894fd737fe6a6a777c879a497 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 16 May 2023 18:58:04 +0530 Subject: [PATCH 26/27] fix: cancelled vouchers in tax withheld vouchers list (#35309) fix: cancelled vouchers in tax withheld vouchers list (#35309) (cherry picked from commit 776a83066db63e84cc6e7dac804c581343f81ccd) Co-authored-by: ruthra kumar --- erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js index 5c9168bf9c5..ab7884d5209 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js @@ -303,7 +303,7 @@ erpnext.accounts.PurchaseInvoice = class PurchaseInvoice extends erpnext.buying. apply_tds(frm) { var me = this; - + me.frm.set_value("tax_withheld_vouchers", []); if (!me.frm.doc.apply_tds) { me.frm.set_value("tax_withholding_category", ''); me.frm.set_df_property("tax_withholding_category", "hidden", 1); From fa9fa97e05e66dbe3996515ffe70d615d6a49706 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 16 May 2023 18:59:12 +0530 Subject: [PATCH 27/27] fix: unable to create partial invoice with auto fetch terms enabled (#35285) fix: unable to create partial invoice with auto fetch terms enabled (#35285) fix: fetch so/po terms if auto fetch is enabled (cherry picked from commit 0da6c1688b3d37b21d25de81e202e3451fd8dac9) Co-authored-by: ruthra kumar --- erpnext/controllers/accounts_controller.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 3d930d67e04..78bb05671d8 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -1682,6 +1682,9 @@ class AccountsController(TransactionBase): d.base_payment_amount = flt( d.payment_amount * self.get("conversion_rate"), d.precision("base_payment_amount") ) + else: + self.fetch_payment_terms_from_order(po_or_so, doctype) + self.ignore_default_payment_terms_template = 1 def get_order_details(self): if self.doctype == "Sales Invoice":