From 1cbe1e894d1fe4d760e75e72b0b8792f78741f5b Mon Sep 17 00:00:00 2001 From: anandbaburajan Date: Sun, 31 Dec 2023 23:34:15 +0530 Subject: [PATCH 01/29] fix: asset WDV depreciation calc according to IT act --- erpnext/assets/doctype/asset/asset.py | 60 +++++++++++-------- erpnext/assets/doctype/asset/depreciation.py | 8 +++ .../asset_finance_book.json | 3 +- 3 files changed, 44 insertions(+), 27 deletions(-) diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index f83d7071aa7..2120535a295 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -428,10 +428,7 @@ class Asset(AccountsController): n == 0 and (has_pro_rata or has_wdv_or_dd_non_yearly_pro_rata) and not self.opening_accumulated_depreciation - and get_updated_rate_of_depreciation_for_wdv_and_dd( - self, value_after_depreciation, finance_book, False - ) - == finance_book.rate_of_depreciation + and not self.flags.wdv_it_act_applied ): from_date = add_days( self.available_for_use_date, -1 @@ -1378,26 +1375,16 @@ def get_depreciation_amount( asset, fb_row, schedule_idx, number_of_pending_depreciations ) else: - rate_of_depreciation = get_updated_rate_of_depreciation_for_wdv_and_dd( - asset, depreciable_value, fb_row - ) return get_wdv_or_dd_depr_amount( + asset, + fb_row, depreciable_value, - rate_of_depreciation, - fb_row.frequency_of_depreciation, schedule_idx, prev_depreciation_amount, has_wdv_or_dd_non_yearly_pro_rata, ) -@erpnext.allow_regional -def get_updated_rate_of_depreciation_for_wdv_and_dd( - asset, depreciable_value, fb_row, show_msg=True -): - return fb_row.rate_of_depreciation - - def get_straight_line_or_manual_depr_amount( asset, row, schedule_idx, number_of_pending_depreciations ): @@ -1532,30 +1519,53 @@ def get_asset_shift_factors_map(): return dict(frappe.db.get_all("Asset Shift Factor", ["shift_name", "shift_factor"], as_list=True)) +@erpnext.allow_regional def get_wdv_or_dd_depr_amount( + asset, + fb_row, depreciable_value, - rate_of_depreciation, - frequency_of_depreciation, schedule_idx, prev_depreciation_amount, has_wdv_or_dd_non_yearly_pro_rata, ): - if cint(frequency_of_depreciation) == 12: - return flt(depreciable_value) * (flt(rate_of_depreciation) / 100) + return get_default_wdv_or_dd_depr_amount( + asset, + fb_row, + depreciable_value, + schedule_idx, + prev_depreciation_amount, + has_wdv_or_dd_non_yearly_pro_rata, + ) + + +def get_default_wdv_or_dd_depr_amount( + asset, + fb_row, + depreciable_value, + schedule_idx, + prev_depreciation_amount, + has_wdv_or_dd_non_yearly_pro_rata, +): + if cint(fb_row.frequency_of_depreciation) == 12: + return flt(depreciable_value) * (flt(fb_row.rate_of_depreciation) / 100) else: if has_wdv_or_dd_non_yearly_pro_rata: if schedule_idx == 0: - return flt(depreciable_value) * (flt(rate_of_depreciation) / 100) - elif schedule_idx % (12 / cint(frequency_of_depreciation)) == 1: + return flt(depreciable_value) * (flt(fb_row.rate_of_depreciation) / 100) + elif schedule_idx % (12 / cint(fb_row.frequency_of_depreciation)) == 1: return ( - flt(depreciable_value) * flt(frequency_of_depreciation) * (flt(rate_of_depreciation) / 1200) + flt(depreciable_value) + * flt(fb_row.frequency_of_depreciation) + * (flt(fb_row.rate_of_depreciation) / 1200) ) else: return prev_depreciation_amount else: - if schedule_idx % (12 / cint(frequency_of_depreciation)) == 0: + if schedule_idx % (12 / cint(fb_row.frequency_of_depreciation)) == 0: return ( - flt(depreciable_value) * flt(frequency_of_depreciation) * (flt(rate_of_depreciation) / 1200) + flt(depreciable_value) + * flt(fb_row.frequency_of_depreciation) + * (flt(fb_row.rate_of_depreciation) / 1200) ) else: return prev_depreciation_amount diff --git a/erpnext/assets/doctype/asset/depreciation.py b/erpnext/assets/doctype/asset/depreciation.py index 6f76955e12d..7eb600025e0 100644 --- a/erpnext/assets/doctype/asset/depreciation.py +++ b/erpnext/assets/doctype/asset/depreciation.py @@ -19,6 +19,7 @@ from frappe.utils import ( from frappe.utils.data import get_link_to_form from frappe.utils.user import get_users_with_role +import erpnext from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import ( get_checks_for_pl_and_bs_accounts, ) @@ -473,6 +474,13 @@ def depreciate_asset(asset, date): make_depreciation_entry(asset.name, date) + cancel_depreciation_entries(asset, date) + + +@erpnext.allow_regional +def cancel_depreciation_entries(asset, date): + pass + def reset_depreciation_schedule(asset, date): if not asset.calculate_depreciation: diff --git a/erpnext/assets/doctype/asset_finance_book/asset_finance_book.json b/erpnext/assets/doctype/asset_finance_book/asset_finance_book.json index 172b81d98b0..9e5dd3e1a68 100644 --- a/erpnext/assets/doctype/asset_finance_book/asset_finance_book.json +++ b/erpnext/assets/doctype/asset_finance_book/asset_finance_book.json @@ -90,7 +90,6 @@ }, { "default": "0", - "depends_on": "eval:doc.depreciation_method == \"Straight Line\" || doc.depreciation_method == \"Manual\"", "fieldname": "daily_prorata_based", "fieldtype": "Check", "label": "Depreciate based on daily pro-rata" @@ -106,7 +105,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2023-11-29 03:53:03.591098", + "modified": "2023-12-29 08:49:39.876439", "modified_by": "Administrator", "module": "Assets", "name": "Asset Finance Book", From 7d64df05bc3a265023a27d92ea6fdc5e444a6864 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 3 Jan 2024 13:00:44 +0530 Subject: [PATCH 02/29] fix: Ignore UP on "allowed to transact with" (backport #39103) (#39104) fix: Ignore UP on "allowed to transact with" (#39103) If a customer is allowed to transact with some company it usually doesn't imply that customer is somehow "linked with" that company. (cherry picked from commit 6401908f419a7d34d7d42d2083fe9f7cd82e7b87) Co-authored-by: Ankush Menat --- .../allowed_to_transact_with/allowed_to_transact_with.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/allowed_to_transact_with/allowed_to_transact_with.json b/erpnext/accounts/doctype/allowed_to_transact_with/allowed_to_transact_with.json index e3f2d59c065..234ffc8a870 100644 --- a/erpnext/accounts/doctype/allowed_to_transact_with/allowed_to_transact_with.json +++ b/erpnext/accounts/doctype/allowed_to_transact_with/allowed_to_transact_with.json @@ -11,6 +11,7 @@ { "fieldname": "company", "fieldtype": "Link", + "ignore_user_permissions": 1, "in_list_view": 1, "label": "Company", "options": "Company", @@ -19,7 +20,7 @@ ], "istable": 1, "links": [], - "modified": "2020-05-01 12:32:34.044911", + "modified": "2024-01-03 11:13:02.669632", "modified_by": "Administrator", "module": "Accounts", "name": "Allowed To Transact With", @@ -28,5 +29,6 @@ "quick_entry": 1, "sort_field": "modified", "sort_order": "DESC", + "states": [], "track_changes": 1 } \ No newline at end of file From 26ae708d6d59ea04f862a20dcc08555f7bf553ab Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 3 Jan 2024 20:32:55 +0530 Subject: [PATCH 03/29] fix(UX): dont override framework's permission check messages (backport #39118) (#39119) fix(UX): dont override framework's permission check messages (#39118) (cherry picked from commit e84c9f7c51fc6e8c5c631c74131f9290624b2a05) Co-authored-by: Ankush Menat --- erpnext/accounts/party.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py index c24d28b3249..c83969b34bc 100644 --- a/erpnext/accounts/party.py +++ b/erpnext/accounts/party.py @@ -114,14 +114,12 @@ def _get_party_details( set_account_and_due_date(party, account, party_type, company, posting_date, bill_date, doctype) ) party = party_details[party_type.lower()] - - if not ignore_permissions and not ( - frappe.has_permission(party_type, "read", party) - or frappe.has_permission(party_type, "select", party) - ): - frappe.throw(_("Not permitted for {0}").format(party), frappe.PermissionError) - party = frappe.get_doc(party_type, party) + + if not ignore_permissions: + ptype = "select" if frappe.only_has_select_perm(party_type) else "read" + frappe.has_permission(party_type, ptype, party, throw=True) + currency = party.get("default_currency") or currency or get_company_currency(company) party_address, shipping_address = set_address_details( From 71ecf081c312ffca15059de9e3dc446d43815411 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Wed, 3 Jan 2024 17:59:15 +0530 Subject: [PATCH 04/29] fix: typerror on multi select dialog (cherry picked from commit 7da9ffa3bd072fbfc9627e6eb5afb3dd3078f560) --- erpnext/public/js/utils.js | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js index 35c3828d5b9..682762678ba 100755 --- a/erpnext/public/js/utils.js +++ b/erpnext/public/js/utils.js @@ -759,16 +759,20 @@ erpnext.utils.map_current_doc = function(opts) { } if (opts.source_doctype) { + let data_fields = []; + if(opts.source_doctype == "Purchase Receipt") { + data_fields.push({ + fieldname: 'merge_taxes', + fieldtype: 'Check', + label: __('Merge taxes from multiple documents'), + }); + } const d = new frappe.ui.form.MultiSelectDialog({ doctype: opts.source_doctype, target: opts.target, date_field: opts.date_field || undefined, setters: opts.setters, - data_fields: [{ - fieldname: 'merge_taxes', - fieldtype: 'Check', - label: __('Merge taxes from multiple documents'), - }], + data_fields: data_fields, get_query: opts.get_query, add_filters_group: 1, allow_child_item_selection: opts.allow_child_item_selection, @@ -782,7 +786,10 @@ erpnext.utils.map_current_doc = function(opts) { return; } opts.source_name = values; - opts.args = args; + if (opts.allow_child_item_selection || opts.source_doctype == "Purchase Receipt") { + // args contains filtered child docnames + opts.args = args; + } d.dialog.hide(); _map(); }, From 080a742725f37c0b4514f534a7fc4362c5276db0 Mon Sep 17 00:00:00 2001 From: Raffael Meyer <14891507+barredterra@users.noreply.github.com> Date: Thu, 4 Jan 2024 13:33:28 +0100 Subject: [PATCH 05/29] fix(Employee): treeview (#39126) (cherry picked from commit e912e9597dbd83724afbcd00f3b2acd75b50a8f0) --- erpnext/setup/doctype/employee/employee.json | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/erpnext/setup/doctype/employee/employee.json b/erpnext/setup/doctype/employee/employee.json index 1143ccb7b10..daf2df5a590 100644 --- a/erpnext/setup/doctype/employee/employee.json +++ b/erpnext/setup/doctype/employee/employee.json @@ -616,8 +616,8 @@ "fieldname": "relieving_date", "fieldtype": "Date", "label": "Relieving Date", - "no_copy": 1, "mandatory_depends_on": "eval:doc.status == \"Left\"", + "no_copy": 1, "oldfieldname": "relieving_date", "oldfieldtype": "Date" }, @@ -822,12 +822,14 @@ "icon": "fa fa-user", "idx": 24, "image_field": "image", + "is_tree": 1, "links": [], - "modified": "2023-10-04 10:57:05.174592", + "modified": "2024-01-03 17:36:20.984421", "modified_by": "Administrator", "module": "Setup", "name": "Employee", "naming_rule": "By \"Naming Series\" field", + "nsm_parent_field": "reports_to", "owner": "Administrator", "permissions": [ { @@ -860,7 +862,6 @@ "read": 1, "report": 1, "role": "HR Manager", - "set_user_permissions": 1, "share": 1, "write": 1 } @@ -871,4 +872,4 @@ "sort_order": "DESC", "states": [], "title_field": "employee_name" -} +} \ No newline at end of file From 2866f7c44169098b691a5fa31cef5cb2b6ce6e81 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Fri, 5 Jan 2024 05:03:22 +0530 Subject: [PATCH 06/29] fix: inventory dimension negative stock validation (backport #39149) (#39150) fix: inventory dimension negative stock validation (#39149) (cherry picked from commit bae7c6496402466df3af15c6bffcf51b97ae2037) Co-authored-by: rohitwaghchaure --- .../inventory_dimension/inventory_dimension.py | 2 +- .../test_inventory_dimension.py | 8 ++++++++ .../stock_ledger_entry/stock_ledger_entry.py | 14 +++++++++----- 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/erpnext/stock/doctype/inventory_dimension/inventory_dimension.py b/erpnext/stock/doctype/inventory_dimension/inventory_dimension.py index 257d18fc33a..a7ead064c97 100644 --- a/erpnext/stock/doctype/inventory_dimension/inventory_dimension.py +++ b/erpnext/stock/doctype/inventory_dimension/inventory_dimension.py @@ -281,7 +281,7 @@ 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: + if row.type_of_transaction and row.type_of_transaction != "Both": if ( row.type_of_transaction == "Inward" if doc.docstatus == 1 diff --git a/erpnext/stock/doctype/inventory_dimension/test_inventory_dimension.py b/erpnext/stock/doctype/inventory_dimension/test_inventory_dimension.py index 531bc3f109f..59389c7d7df 100644 --- a/erpnext/stock/doctype/inventory_dimension/test_inventory_dimension.py +++ b/erpnext/stock/doctype/inventory_dimension/test_inventory_dimension.py @@ -429,6 +429,14 @@ class TestInventoryDimension(FrappeTestCase): ) warehouse = create_warehouse("Negative Stock Warehouse") + + doc = make_stock_entry(item_code=item_code, source=warehouse, qty=10, do_not_submit=True) + doc.items[0].inv_site = "Site 1" + self.assertRaises(frappe.ValidationError, doc.submit) + doc.reload() + if doc.docstatus == 1: + doc.cancel() + doc = make_stock_entry(item_code=item_code, target=warehouse, qty=10, do_not_submit=True) doc.items[0].to_inv_site = "Site 1" diff --git a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py index 921b04aab8c..1d52b17149b 100644 --- a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py +++ b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py @@ -71,16 +71,20 @@ class StockLedgerEntry(Document): "posting_date": self.posting_date, "posting_time": self.posting_time, "company": self.company, + "sle": self.name, } ) sle = get_previous_sle(kwargs, extra_cond=extra_cond) + qty_after_transaction = 0.0 + flt_precision = cint(frappe.db.get_default("float_precision")) or 2 if sle: - flt_precision = cint(frappe.db.get_default("float_precision")) or 2 - diff = sle.qty_after_transaction + flt(self.actual_qty) - diff = flt(diff, flt_precision) - if diff < 0 and abs(diff) > 0.0001: - self.throw_validation_error(diff, dimensions) + qty_after_transaction = sle.qty_after_transaction + + diff = qty_after_transaction + flt(self.actual_qty) + diff = flt(diff, flt_precision) + if diff < 0 and abs(diff) > 0.0001: + self.throw_validation_error(diff, dimensions) def throw_validation_error(self, diff, dimensions): dimension_msg = _(", with the inventory {0}: {1}").format( From f236a2c081d8d10522848af417c7a061811991ee Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 5 Jan 2024 14:32:05 +0530 Subject: [PATCH 07/29] refactor: prevent permissions by always processing in background (cherry picked from commit 15dc5c7e9966e3bf06ec0aa951d7185fb6cf6d0e) --- erpnext/utilities/bulk_transaction.py | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/erpnext/utilities/bulk_transaction.py b/erpnext/utilities/bulk_transaction.py index 7577f0a379e..b519617435f 100644 --- a/erpnext/utilities/bulk_transaction.py +++ b/erpnext/utilities/bulk_transaction.py @@ -15,18 +15,15 @@ def transaction_processing(data, from_doctype, to_doctype): length_of_data = len(deserialized_data) - if length_of_data > 10: - frappe.msgprint( - _("Started a background job to create {1} {0}").format(to_doctype, length_of_data) - ) - frappe.enqueue( - job, - deserialized_data=deserialized_data, - from_doctype=from_doctype, - to_doctype=to_doctype, - ) - else: - job(deserialized_data, from_doctype, to_doctype) + frappe.msgprint( + _("Started a background job to create {1} {0}").format(to_doctype, length_of_data) + ) + frappe.enqueue( + job, + deserialized_data=deserialized_data, + from_doctype=from_doctype, + to_doctype=to_doctype, + ) @frappe.whitelist() From 06d193ad87ebb98bbfad1d79fa0c9afaf5f860d2 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 8 Jan 2024 11:44:52 +0530 Subject: [PATCH 08/29] fix: don't set rate for non-stock item in Internal Transfer (backport #39140) (#39168) * fix: don't set rate for non-stock item in Internal Transfer (cherry picked from commit e1b0fffd0c934d376562e3ba8707b38426ea09a1) * test: internal transfer for non-stock item (cherry picked from commit 57b6a987034522266caca853d4ee26316526e526) # Conflicts: # erpnext/stock/doctype/delivery_note/test_delivery_note.py * chore: `conflicts` --------- Co-authored-by: s-aga-r --- erpnext/controllers/selling_controller.py | 3 +++ .../delivery_note/test_delivery_note.py | 19 +++++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py index d3157e7037b..d55aeeacc08 100644 --- a/erpnext/controllers/selling_controller.py +++ b/erpnext/controllers/selling_controller.py @@ -422,6 +422,9 @@ class SellingController(StockController): items = self.get("items") + (self.get("packed_items") or []) for d in items: + if not frappe.get_cached_value("Item", d.item_code, "is_stock_item"): + continue + if not self.get("return_against") or ( get_valuation_method(d.item_code) == "Moving Average" and self.get("is_return") ): diff --git a/erpnext/stock/doctype/delivery_note/test_delivery_note.py b/erpnext/stock/doctype/delivery_note/test_delivery_note.py index a931ee2c254..9f2c0be75d0 100644 --- a/erpnext/stock/doctype/delivery_note/test_delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/test_delivery_note.py @@ -1435,6 +1435,25 @@ class TestDeliveryNote(FrappeTestCase): returned_dn.reload() self.assertAlmostEqual(returned_dn.items[0].incoming_rate, 200.0) + def test_internal_transfer_for_non_stock_item(self): + from erpnext.selling.doctype.customer.test_customer import create_internal_customer + from erpnext.selling.doctype.sales_order.sales_order import make_delivery_note + + item = make_item(properties={"is_stock_item": 0}).name + warehouse = "_Test Warehouse - _TC" + target = "Stores - _TC" + company = "_Test Company" + customer = create_internal_customer(represents_company=company) + rate = 100 + + so = make_sales_order(item_code=item, qty=1, rate=rate, customer=customer, warehouse=warehouse) + dn = make_delivery_note(so.name) + dn.items[0].target_warehouse = target + dn.save().submit() + + self.assertEqual(so.items[0].rate, rate) + self.assertEqual(dn.items[0].rate, so.items[0].rate) + def create_delivery_note(**args): dn = frappe.new_doc("Delivery Note") From 69c460c7562103771d5238c1f02af04493651dd8 Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Mon, 8 Jan 2024 11:36:37 +0530 Subject: [PATCH 09/29] fix: set `First Name` in Supplier Contact --- erpnext/selling/doctype/customer/customer.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/erpnext/selling/doctype/customer/customer.py b/erpnext/selling/doctype/customer/customer.py index fea27da61b2..b7fcadb1eca 100644 --- a/erpnext/selling/doctype/customer/customer.py +++ b/erpnext/selling/doctype/customer/customer.py @@ -706,7 +706,9 @@ def make_contact(args, is_primary_contact=1): else: values.update( { - "first_name": args.get("customer_name"), + "first_name": args.get("customer_name") + if args.doctype == "Customer" + else args.get("supplier_name"), "company_name": args.get(party_name_key), } ) From 580e9f6e10e21dab07080ac2884d0095412239b1 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Fri, 5 Jan 2024 17:12:08 +0530 Subject: [PATCH 10/29] fix: improved validation message (cherry picked from commit fe43dab4d78091e626ddfb6432b2d69f84793d92) --- erpnext/assets/doctype/asset_category/asset_category.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/assets/doctype/asset_category/asset_category.py b/erpnext/assets/doctype/asset_category/asset_category.py index 8d351412ca8..2a204e11477 100644 --- a/erpnext/assets/doctype/asset_category/asset_category.py +++ b/erpnext/assets/doctype/asset_category/asset_category.py @@ -67,12 +67,12 @@ class AssetCategory(Document): if selected_key_type not in expected_key_types: frappe.throw( _( - "Row #{}: {} of {} should be {}. Please modify the account or select a different account." + "Row #{0}: {1} of {2} should be {3}. Please update the {1} or select a different account." ).format( d.idx, frappe.unscrub(key_to_match), frappe.bold(selected_account), - frappe.bold(expected_key_types), + frappe.bold(" or ".join(expected_key_types)), ), title=_("Invalid Account"), ) From 0f6477a253844d6e1fc530cbc5d0f7a38eaf0aa4 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Fri, 5 Jan 2024 17:38:51 +0530 Subject: [PATCH 11/29] fix: Purchase date and amount is not mandatory for composite asset creation (cherry picked from commit c34f09c503b367f6ea1eee5e24f8aa6e94976e01) # Conflicts: # erpnext/assets/doctype/asset/asset.py --- erpnext/assets/doctype/asset/asset.json | 12 ++-- erpnext/assets/doctype/asset/asset.py | 91 ++++++++++++++++++++++++- 2 files changed, 96 insertions(+), 7 deletions(-) diff --git a/erpnext/assets/doctype/asset/asset.json b/erpnext/assets/doctype/asset/asset.json index 91ae62bfad7..cdccf81507b 100644 --- a/erpnext/assets/doctype/asset/asset.json +++ b/erpnext/assets/doctype/asset/asset.json @@ -202,9 +202,9 @@ "fieldname": "purchase_date", "fieldtype": "Date", "label": "Purchase Date", + "mandatory_depends_on": "eval:!doc.is_existing_asset", "read_only": 1, - "read_only_depends_on": "eval:!doc.is_existing_asset && !doc.is_composite_asset", - "reqd": 1 + "read_only_depends_on": "eval:!doc.is_existing_asset && !doc.is_composite_asset" }, { "fieldname": "disposal_date", @@ -227,15 +227,15 @@ "fieldname": "gross_purchase_amount", "fieldtype": "Currency", "label": "Gross Purchase Amount", + "mandatory_depends_on": "eval:(!doc.is_composite_asset || doc.docstatus==1)", "options": "Company:company:default_currency", - "read_only_depends_on": "eval:!doc.is_existing_asset", - "reqd": 1 + "read_only_depends_on": "eval:!doc.is_existing_asset" }, { "fieldname": "available_for_use_date", "fieldtype": "Date", "label": "Available-for-use Date", - "reqd": 1 + "mandatory_depends_on": "eval:(!doc.is_composite_asset || doc.docstatus==1)" }, { "default": "0", @@ -583,7 +583,7 @@ "link_fieldname": "target_asset" } ], - "modified": "2023-12-21 16:46:20.732869", + "modified": "2024-01-05 17:36:53.131512", "modified_by": "Administrator", "module": "Assets", "name": "Asset", diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index f83d7071aa7..300d95d37ee 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -34,6 +34,86 @@ from erpnext.controllers.accounts_controller import AccountsController class Asset(AccountsController): +<<<<<<< HEAD +======= + # begin: auto-generated types + # This code is auto-generated. Do not modify anything in this block. + + from typing import TYPE_CHECKING + + if TYPE_CHECKING: + from frappe.types import DF + + from erpnext.assets.doctype.asset_finance_book.asset_finance_book import AssetFinanceBook + + additional_asset_cost: DF.Currency + amended_from: DF.Link | None + asset_category: DF.Link | None + asset_name: DF.Data + asset_owner: DF.Literal["", "Company", "Supplier", "Customer"] + asset_owner_company: DF.Link | None + asset_quantity: DF.Int + available_for_use_date: DF.Date | None + booked_fixed_asset: DF.Check + calculate_depreciation: DF.Check + capitalized_in: DF.Link | None + company: DF.Link + comprehensive_insurance: DF.Data | None + cost_center: DF.Link | None + custodian: DF.Link | None + customer: DF.Link | None + default_finance_book: DF.Link | None + department: DF.Link | None + depr_entry_posting_status: DF.Literal["", "Successful", "Failed"] + depreciation_method: DF.Literal["", "Straight Line", "Double Declining Balance", "Manual"] + disposal_date: DF.Date | None + finance_books: DF.Table[AssetFinanceBook] + frequency_of_depreciation: DF.Int + gross_purchase_amount: DF.Currency + image: DF.AttachImage | None + insurance_end_date: DF.Date | None + insurance_start_date: DF.Date | None + insured_value: DF.Data | None + insurer: DF.Data | None + is_composite_asset: DF.Check + is_existing_asset: DF.Check + is_fully_depreciated: DF.Check + item_code: DF.Link + item_name: DF.ReadOnly | None + journal_entry_for_scrap: DF.Link | None + location: DF.Link + maintenance_required: DF.Check + naming_series: DF.Literal["ACC-ASS-.YYYY.-"] + next_depreciation_date: DF.Date | None + number_of_depreciations_booked: DF.Int + opening_accumulated_depreciation: DF.Currency + policy_number: DF.Data | None + purchase_date: DF.Date | None + purchase_invoice: DF.Link | None + purchase_receipt: DF.Link | None + purchase_receipt_amount: DF.Currency + split_from: DF.Link | None + status: DF.Literal[ + "Draft", + "Submitted", + "Partially Depreciated", + "Fully Depreciated", + "Sold", + "Scrapped", + "In Maintenance", + "Out of Order", + "Issue", + "Receipt", + "Capitalized", + "Decapitalized", + ] + supplier: DF.Link | None + total_asset_cost: DF.Currency + total_number_of_depreciations: DF.Int + value_after_depreciation: DF.Currency + # end: auto-generated types + +>>>>>>> c34f09c503 (fix: Purchase date and amount is not mandatory for composite asset creation) def validate(self): self.validate_asset_values() self.validate_asset_and_reference() @@ -247,7 +327,16 @@ class Asset(AccountsController): frappe.throw(_("Gross Purchase Amount is mandatory"), frappe.MandatoryError) if is_cwip_accounting_enabled(self.asset_category): +<<<<<<< HEAD if not self.is_existing_asset and not (self.purchase_receipt or self.purchase_invoice): +======= + if ( + not self.is_existing_asset + and not self.is_composite_asset + and not self.purchase_receipt + and not self.purchase_invoice + ): +>>>>>>> c34f09c503 (fix: Purchase date and amount is not mandatory for composite asset creation) frappe.throw( _("Please create purchase receipt or purchase invoice for the item {0}").format( self.item_code @@ -260,7 +349,7 @@ class Asset(AccountsController): and not frappe.db.get_value("Purchase Invoice", self.purchase_invoice, "update_stock") ): frappe.throw( - _("Update stock must be enable for the purchase invoice {0}").format(self.purchase_invoice) + _("Update stock must be enabled for the purchase invoice {0}").format(self.purchase_invoice) ) if not self.calculate_depreciation: From 2ea2146b343d13e4cc230e4d1ab8924f997e2b15 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 8 Jan 2024 16:27:19 +0530 Subject: [PATCH 12/29] fix: update Maintenance Schedule status on Maintenance Visit submit (backport #39167) (#39185) * fix: make `Sales Person` non-mandatory (cherry picked from commit 4d56f725fe23495fa2392be01545c5d2fa8889b3) * fix: update Maintenance Schedule status on Maintenance Visit submit (cherry picked from commit cd293a5173aaec693afee23e9b874b1db649a267) # Conflicts: # erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py # erpnext/maintenance/doctype/maintenance_visit_purpose/maintenance_visit_purpose.py * chore: `conflicts` --------- Co-authored-by: s-aga-r --- .../maintenance_schedule.py | 48 +++++++++------ .../maintenance_visit/maintenance_visit.py | 59 +++++++++++++++---- .../maintenance_visit_purpose.json | 15 ++++- 3 files changed, 89 insertions(+), 33 deletions(-) diff --git a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py index 95e2d694a58..8e4af3ac172 100644 --- a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py +++ b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py @@ -80,7 +80,7 @@ class MaintenanceSchedule(TransactionBase): self.update_amc_date(serial_nos, d.end_date) no_email_sp = [] - if d.sales_person not in email_map: + if d.sales_person and d.sales_person not in email_map: sp = frappe.get_doc("Sales Person", d.sales_person) try: email_map[d.sales_person] = sp.get_email_id() @@ -94,12 +94,11 @@ class MaintenanceSchedule(TransactionBase): ).format(self.owner, "
" + "
".join(no_email_sp)) ) - scheduled_date = frappe.db.sql( - """select scheduled_date from - `tabMaintenance Schedule Detail` where sales_person=%s and item_code=%s and - parent=%s""", - (d.sales_person, d.item_code, self.name), - as_dict=1, + scheduled_date = frappe.db.get_all( + "Maintenance Schedule Detail", + {"parent": self.name, "item_code": d.item_code}, + ["scheduled_date"], + as_list=False, ) for key in scheduled_date: @@ -195,8 +194,6 @@ class MaintenanceSchedule(TransactionBase): throw(_("Please select Start Date and End Date for Item {0}").format(d.item_code)) elif not d.no_of_visits: throw(_("Please mention no of visits required")) - elif not d.sales_person: - throw(_("Please select a Sales Person for item: {0}").format(d.item_name)) if getdate(d.start_date) >= getdate(d.end_date): throw(_("Start date should be less than end date for Item {0}").format(d.item_code)) @@ -392,16 +389,28 @@ def get_serial_nos_from_schedule(item_code, schedule=None): def make_maintenance_visit(source_name, target_doc=None, item_name=None, s_id=None): from frappe.model.mapper import get_mapped_doc + def condition(doc): + if s_id: + return doc.name == s_id + elif item_name: + return doc.item_name == item_name + + return True + def update_status_and_detail(source, target, parent): target.maintenance_type = "Scheduled" - target.maintenance_schedule_detail = s_id def update_serial(source, target, parent): - serial_nos = get_serial_nos(target.serial_no) - if len(serial_nos) == 1: - target.serial_no = serial_nos[0] - else: - target.serial_no = "" + if source.item_reference: + if serial_nos := frappe.db.get_value( + "Maintenance Schedule Item", source.item_reference, "serial_no" + ): + serial_nos = serial_nos.split("\n") + + if len(serial_nos) == 1: + target.serial_no = serial_nos[0] + else: + target.serial_no = "" doclist = get_mapped_doc( "Maintenance Schedule", @@ -413,10 +422,13 @@ def make_maintenance_visit(source_name, target_doc=None, item_name=None, s_id=No "validation": {"docstatus": ["=", 1]}, "postprocess": update_status_and_detail, }, - "Maintenance Schedule Item": { + "Maintenance Schedule Detail": { "doctype": "Maintenance Visit Purpose", - "condition": lambda doc: doc.item_name == item_name if item_name else True, - "field_map": {"sales_person": "service_person"}, + "condition": condition, + "field_map": { + "sales_person": "service_person", + "name": "maintenance_schedule_detail", + }, "postprocess": update_serial, }, }, diff --git a/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.py b/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.py index 0d319bf7424..a0974e99040 100644 --- a/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.py +++ b/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.py @@ -23,20 +23,39 @@ class MaintenanceVisit(TransactionBase): frappe.throw(_("Add Items in the Purpose Table"), title=_("Purposes Required")) def validate_maintenance_date(self): - if self.maintenance_type == "Scheduled" and self.maintenance_schedule_detail: - item_ref = frappe.db.get_value( - "Maintenance Schedule Detail", self.maintenance_schedule_detail, "item_reference" - ) - if item_ref: - start_date, end_date = frappe.db.get_value( - "Maintenance Schedule Item", item_ref, ["start_date", "end_date"] + if self.maintenance_type == "Scheduled": + if self.maintenance_schedule_detail: + item_ref = frappe.db.get_value( + "Maintenance Schedule Detail", self.maintenance_schedule_detail, "item_reference" ) - if get_datetime(self.mntc_date) < get_datetime(start_date) or get_datetime( - self.mntc_date - ) > get_datetime(end_date): - frappe.throw( - _("Date must be between {0} and {1}").format(format_date(start_date), format_date(end_date)) + if item_ref: + start_date, end_date = frappe.db.get_value( + "Maintenance Schedule Item", item_ref, ["start_date", "end_date"] ) + if get_datetime(self.mntc_date) < get_datetime(start_date) or get_datetime( + self.mntc_date + ) > get_datetime(end_date): + frappe.throw( + _("Date must be between {0} and {1}").format(format_date(start_date), format_date(end_date)) + ) + else: + for purpose in self.purposes: + if purpose.maintenance_schedule_detail: + item_ref = frappe.db.get_value( + "Maintenance Schedule Detail", purpose.maintenance_schedule_detail, "item_reference" + ) + if item_ref: + start_date, end_date = frappe.db.get_value( + "Maintenance Schedule Item", item_ref, ["start_date", "end_date"] + ) + if get_datetime(self.mntc_date) < get_datetime(start_date) or get_datetime( + self.mntc_date + ) > get_datetime(end_date): + frappe.throw( + _("Date must be between {0} and {1}").format( + format_date(start_date), format_date(end_date) + ) + ) def validate(self): self.validate_serial_no() @@ -49,6 +68,7 @@ class MaintenanceVisit(TransactionBase): if not cancel: status = self.completion_status actual_date = self.mntc_date + if self.maintenance_schedule_detail: frappe.db.set_value( "Maintenance Schedule Detail", self.maintenance_schedule_detail, "completion_status", status @@ -56,6 +76,21 @@ class MaintenanceVisit(TransactionBase): frappe.db.set_value( "Maintenance Schedule Detail", self.maintenance_schedule_detail, "actual_date", actual_date ) + else: + for purpose in self.purposes: + if purpose.maintenance_schedule_detail: + frappe.db.set_value( + "Maintenance Schedule Detail", + purpose.maintenance_schedule_detail, + "completion_status", + status, + ) + frappe.db.set_value( + "Maintenance Schedule Detail", + purpose.maintenance_schedule_detail, + "actual_date", + actual_date, + ) def update_customer_issue(self, flag): if not self.maintenance_schedule: diff --git a/erpnext/maintenance/doctype/maintenance_visit_purpose/maintenance_visit_purpose.json b/erpnext/maintenance/doctype/maintenance_visit_purpose/maintenance_visit_purpose.json index ba053555531..a5a63c4c4de 100644 --- a/erpnext/maintenance/doctype/maintenance_visit_purpose/maintenance_visit_purpose.json +++ b/erpnext/maintenance/doctype/maintenance_visit_purpose/maintenance_visit_purpose.json @@ -17,7 +17,8 @@ "work_details", "work_done", "prevdoc_doctype", - "prevdoc_docname" + "prevdoc_docname", + "maintenance_schedule_detail" ], "fields": [ { @@ -49,6 +50,8 @@ "options": "Serial No" }, { + "fetch_from": "item_code.description", + "fetch_if_empty": 1, "fieldname": "description", "fieldtype": "Text Editor", "in_list_view": 1, @@ -56,7 +59,6 @@ "oldfieldname": "description", "oldfieldtype": "Small Text", "print_width": "300px", - "reqd": 1, "width": "300px" }, { @@ -103,12 +105,19 @@ { "fieldname": "section_break_6", "fieldtype": "Section Break" + }, + { + "fieldname": "maintenance_schedule_detail", + "fieldtype": "Data", + "hidden": 1, + "label": "Maintenance Schedule Detail", + "options": "Maintenance Schedule Detail" } ], "idx": 1, "istable": 1, "links": [], - "modified": "2023-02-27 11:09:33.114458", + "modified": "2024-01-05 21:46:53.239830", "modified_by": "Administrator", "module": "Maintenance", "name": "Maintenance Visit Purpose", From 5e517cfc775184681a4549b617e7ccaf56f1bbfc Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 8 Jan 2024 17:43:36 +0530 Subject: [PATCH 13/29] fix: FG Item incorrect qty in the work order (backport #39200) (#39210) fix: FG Item incorrect qty in the work order (#39200) (cherry picked from commit 466625213b6355b53b7af3ee1682cee6fc8649a4) Co-authored-by: rohitwaghchaure --- .../production_plan/production_plan.py | 11 ++--- .../production_plan/test_production_plan.py | 41 ++++++++++++++++++- 2 files changed, 46 insertions(+), 6 deletions(-) diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py index ee2e58bfb46..767b4ccbb1a 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -570,6 +570,10 @@ class ProductionPlan(Document): "project": self.project, } + key = (d.item_code, d.sales_order, d.warehouse) + if not d.sales_order: + key = (d.name, d.item_code, d.warehouse) + if not item_details["project"] and d.sales_order: item_details["project"] = frappe.get_cached_value("Sales Order", d.sales_order, "project") @@ -578,12 +582,9 @@ class ProductionPlan(Document): item_dict[(d.item_code, d.material_request_item, d.warehouse)] = item_details else: item_details.update( - { - "qty": flt(item_dict.get((d.item_code, d.sales_order, d.warehouse), {}).get("qty")) - + (flt(d.planned_qty) - flt(d.ordered_qty)) - } + {"qty": flt(item_dict.get(key, {}).get("qty")) + (flt(d.planned_qty) - flt(d.ordered_qty))} ) - item_dict[(d.item_code, d.sales_order, d.warehouse)] = item_details + item_dict[key] = item_details return item_dict diff --git a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py index 3695ae9d9d8..4f6280ade8f 100644 --- a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py @@ -661,7 +661,7 @@ class TestProductionPlan(FrappeTestCase): items_data = pln.get_production_items() # Update qty - items_data[(item, None, None)]["qty"] = qty + items_data[(pln.po_items[0].name, item, None)]["qty"] = qty # Create and Submit Work Order for each item in items_data for key, item in items_data.items(): @@ -1511,6 +1511,45 @@ class TestProductionPlan(FrappeTestCase): for d in mr_items: self.assertEqual(d.get("quantity"), 1000.0) + def test_fg_item_quantity(self): + from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse + from erpnext.stock.utils import get_or_make_bin + + fg_item = make_item(properties={"is_stock_item": 1}).name + rm_item = make_item(properties={"is_stock_item": 1}).name + + make_bom(item=fg_item, raw_materials=[rm_item], source_warehouse="_Test Warehouse - _TC") + + pln = create_production_plan(item_code=fg_item, planned_qty=10, do_not_submit=1) + + pln.append( + "po_items", + { + "item_code": rm_item, + "planned_qty": 20, + "bom_no": pln.po_items[0].bom_no, + "warehouse": pln.po_items[0].warehouse, + "planned_start_date": add_to_date(nowdate(), days=1), + }, + ) + pln.submit() + wo_qty = {} + + for row in pln.po_items: + wo_qty[row.name] = row.planned_qty + + pln.make_work_order() + + work_orders = frappe.get_all( + "Work Order", + fields=["qty", "production_plan_item as name"], + filters={"production_plan": pln.name}, + ) + self.assertEqual(len(work_orders), 2) + + for row in work_orders: + self.assertEqual(row.qty, wo_qty[row.name]) + def create_production_plan(**args): """ From 7b4b630b2c441481b7121df90e4640654e9aed2e Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Mon, 8 Jan 2024 18:15:55 +0530 Subject: [PATCH 14/29] fix: Show maintain-stock and is-fixed-asset checkbox in item quick entry dialog (cherry picked from commit c14986f9e6e617b67e97c8d25843a4dc33b77dcf) --- erpnext/stock/doctype/item/item.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/item/item.json b/erpnext/stock/doctype/item/item.json index 7afc031d87d..b9256c8ee56 100644 --- a/erpnext/stock/doctype/item/item.json +++ b/erpnext/stock/doctype/item/item.json @@ -203,6 +203,7 @@ "label": "Allow Alternative Item" }, { + "allow_in_quick_entry": 1, "bold": 1, "default": "1", "depends_on": "eval:!doc.is_fixed_asset", @@ -240,6 +241,7 @@ "label": "Standard Selling Rate" }, { + "allow_in_quick_entry": 1, "default": "0", "fieldname": "is_fixed_asset", "fieldtype": "Check", @@ -247,6 +249,7 @@ "set_only_once": 1 }, { + "allow_in_quick_entry": 1, "depends_on": "is_fixed_asset", "fieldname": "asset_category", "fieldtype": "Link", @@ -897,7 +900,7 @@ "index_web_pages_for_search": 1, "links": [], "make_attachments_public": 1, - "modified": "2023-09-18 15:41:32.688051", + "modified": "2024-01-08 18:09:30.225085", "modified_by": "Administrator", "module": "Stock", "name": "Item", From d0e1162c964bb08c96d6b9a2ed726761d8bc2488 Mon Sep 17 00:00:00 2001 From: Kevin Shenk Date: Fri, 20 Jan 2023 10:09:37 -0800 Subject: [PATCH 15/29] feat: Copy project_name, from_time, to_time from timesheet details to sales invoice (#33726) feat: Copy project_name, from_time, to_time from timesheet details to sales invoice (cherry picked from commit e4bceaaf6627fafce12bdf5effce95b9088a7898) --- erpnext/projects/doctype/timesheet/timesheet.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/erpnext/projects/doctype/timesheet/timesheet.py b/erpnext/projects/doctype/timesheet/timesheet.py index e63ac144f64..9e52befd22e 100644 --- a/erpnext/projects/doctype/timesheet/timesheet.py +++ b/erpnext/projects/doctype/timesheet/timesheet.py @@ -389,6 +389,9 @@ def make_sales_invoice(source_name, item_code=None, customer=None, currency=None "timesheets", { "time_sheet": timesheet.name, + "project_name": time_log.project_name, + "from_time": time_log.from_time, + "to_time": time_log.to_time, "billing_hours": time_log.billing_hours, "billing_amount": time_log.billing_amount, "timesheet_detail": time_log.name, From 71f9b7f67508ef04ad5f1f463e4df6c4dca83554 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 9 Jan 2024 12:40:41 +0530 Subject: [PATCH 16/29] fix: total allocated percentage for sales team issue (cherry picked from commit b498094a9759bb95a3c77c6ec6877aafc6daa0ea) --- .../selling/doctype/quotation/quotation.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/erpnext/selling/doctype/quotation/quotation.py b/erpnext/selling/doctype/quotation/quotation.py index 88e24ad542a..539960a508f 100644 --- a/erpnext/selling/doctype/quotation/quotation.py +++ b/erpnext/selling/doctype/quotation/quotation.py @@ -288,15 +288,16 @@ def _make_sales_order(source_name, target_doc=None, ignore_permissions=False): ) # sales team - for d in customer.get("sales_team") or []: - target.append( - "sales_team", - { - "sales_person": d.sales_person, - "allocated_percentage": d.allocated_percentage or None, - "commission_rate": d.commission_rate, - }, - ) + if not target.get("sales_team"): + for d in customer.get("sales_team") or []: + target.append( + "sales_team", + { + "sales_person": d.sales_person, + "allocated_percentage": d.allocated_percentage or None, + "commission_rate": d.commission_rate, + }, + ) target.flags.ignore_permissions = ignore_permissions target.delivery_date = nowdate() From c2eeeecac8c934098d289eb32bfbe995587f6987 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 9 Jan 2024 15:59:24 +0530 Subject: [PATCH 17/29] fix: BOM replace tool does not update exploded items of root (backport #39244) (#39249) fix: BOM replace tool does not update exploded items of root (#39244) (cherry picked from commit 5e0d017497ef7afaaa70abffbd3d3924f9e56063) Co-authored-by: rohitwaghchaure --- .../bom_update_log/bom_updation_utils.py | 6 +- .../bom_update_log/test_bom_update_log.py | 62 +++++++++++++++++++ 2 files changed, 66 insertions(+), 2 deletions(-) diff --git a/erpnext/manufacturing/doctype/bom_update_log/bom_updation_utils.py b/erpnext/manufacturing/doctype/bom_update_log/bom_updation_utils.py index a2919b79b80..f013b88e946 100644 --- a/erpnext/manufacturing/doctype/bom_update_log/bom_updation_utils.py +++ b/erpnext/manufacturing/doctype/bom_update_log/bom_updation_utils.py @@ -86,10 +86,12 @@ def get_ancestor_boms(new_bom: str, bom_list: Optional[List] = None) -> List: if new_bom == d.parent: frappe.throw(_("BOM recursion: {0} cannot be child of {1}").format(new_bom, d.parent)) - bom_list.append(d.parent) + if d.parent not in tuple(bom_list): + bom_list.append(d.parent) + get_ancestor_boms(d.parent, bom_list) - return list(set(bom_list)) + return bom_list def update_new_bom_in_bom_items(unit_cost: float, current_bom: str, new_bom: str) -> None: diff --git a/erpnext/manufacturing/doctype/bom_update_log/test_bom_update_log.py b/erpnext/manufacturing/doctype/bom_update_log/test_bom_update_log.py index b38fc8976b2..30e6f5e2091 100644 --- a/erpnext/manufacturing/doctype/bom_update_log/test_bom_update_log.py +++ b/erpnext/manufacturing/doctype/bom_update_log/test_bom_update_log.py @@ -57,6 +57,68 @@ class TestBOMUpdateLog(FrappeTestCase): log.reload() self.assertEqual(log.status, "Completed") + def test_bom_replace_for_root_bom(self): + """ + - B-Item A (Root Item) + - B-Item B + - B-Item C + - B-Item D + - B-Item E + - B-Item F + + Create New BOM for B-Item E with B-Item G and replace it in the above BOM. + """ + + from erpnext.manufacturing.doctype.bom.test_bom import create_nested_bom + from erpnext.stock.doctype.item.test_item import make_item + + items = ["B-Item A", "B-Item B", "B-Item C", "B-Item D", "B-Item E", "B-Item F", "B-Item G"] + + for item_code in items: + if not frappe.db.exists("Item", item_code): + make_item(item_code) + + for item_code in items: + remove_bom(item_code) + + bom_tree = { + "B-Item A": {"B-Item B": {"B-Item C": {}}, "B-Item D": {"B-Item E": {"B-Item F": {}}}} + } + + root_bom = create_nested_bom(bom_tree, prefix="") + + exploded_items = frappe.get_all( + "BOM Explosion Item", filters={"parent": root_bom.name}, fields=["item_code"] + ) + + exploded_items = [item.item_code for item in exploded_items] + expected_exploded_items = ["B-Item C", "B-Item F"] + self.assertEqual(sorted(exploded_items), sorted(expected_exploded_items)) + + old_bom = frappe.db.get_value("BOM", {"item": "B-Item E"}, "name") + bom_tree = {"B-Item E": {"B-Item G": {}}} + + new_bom = create_nested_bom(bom_tree, prefix="") + enqueue_replace_bom(boms=frappe._dict(current_bom=old_bom, new_bom=new_bom.name)) + + exploded_items = frappe.get_all( + "BOM Explosion Item", filters={"parent": root_bom.name}, fields=["item_code"] + ) + + exploded_items = [item.item_code for item in exploded_items] + expected_exploded_items = ["B-Item C", "B-Item G"] + self.assertEqual(sorted(exploded_items), sorted(expected_exploded_items)) + + +def remove_bom(item_code): + boms = frappe.get_all("BOM", fields=["docstatus", "name"], filters={"item": item_code}) + + for row in boms: + if row.docstatus == 1: + frappe.get_doc("BOM", row.name).cancel() + + frappe.delete_doc("BOM", row.name) + def update_cost_in_all_boms_in_test(): """ From 36ba33c5009e1526a25cc55ed39de5208aa5f090 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 9 Jan 2024 17:42:04 +0530 Subject: [PATCH 18/29] fix: TypeError is pricing rules (backport #39252) (#39259) fix: TypeError is pricing rules (#39252) (cherry picked from commit 274c65c451ccc71f11f29a2abc6d13b2b49b776c) Co-authored-by: rohitwaghchaure --- erpnext/accounts/doctype/pricing_rule/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/pricing_rule/utils.py b/erpnext/accounts/doctype/pricing_rule/utils.py index 65d4595fcd5..8396783ffa1 100644 --- a/erpnext/accounts/doctype/pricing_rule/utils.py +++ b/erpnext/accounts/doctype/pricing_rule/utils.py @@ -521,7 +521,7 @@ def get_qty_amount_data_for_cumulative(pr_doc, doc, items=None): values.extend(warehouses) if items: - condition = " and `tab{child_doc}`.{apply_on} in ({items})".format( + condition += " and `tab{child_doc}`.{apply_on} in ({items})".format( child_doc=child_doctype, apply_on=apply_on, items=",".join(["%s"] * len(items)) ) From 8c496fbf2b0653a4c2ef1cb23c9b07cab98b8ebe Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 9 Jan 2024 17:43:18 +0530 Subject: [PATCH 19/29] fix: incorrect indicator title for portal sales order (backport #39247) (#39254) fix: incorrect indicator title for portal sales order (#39247) (cherry picked from commit 2d2ff7cf52a548b8e880cd212cbbe6ef4569ea28) Co-authored-by: rohitwaghchaure --- .../doctype/sales_order/sales_order.py | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py index cbc11ed02af..bec24fe5e99 100755 --- a/erpnext/selling/doctype/sales_order/sales_order.py +++ b/erpnext/selling/doctype/sales_order/sales_order.py @@ -432,17 +432,17 @@ class SalesOrder(SellingController): def set_indicator(self): """Set indicator for portal""" - if self.per_billed < 100 and self.per_delivered < 100: - self.indicator_color = "orange" - self.indicator_title = _("Not Paid and Not Delivered") + self.indicator_color = { + "Draft": "red", + "On Hold": "orange", + "To Deliver and Bill": "orange", + "To Bill": "orange", + "To Deliver": "orange", + "Completed": "green", + "Cancelled": "red", + }.get(self.status, "blue") - elif self.per_billed == 100 and self.per_delivered < 100: - self.indicator_color = "orange" - self.indicator_title = _("Paid and Not Delivered") - - else: - self.indicator_color = "green" - self.indicator_title = _("Paid") + self.indicator_title = _(self.status) def on_recurring(self, reference_doc, auto_repeat_doc): def _get_delivery_date(ref_doc_delivery_date, red_doc_transaction_date, transaction_date): From a15ad804d008613afdf92e9dcd2b8ac415780304 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 9 Jan 2024 19:09:06 +0530 Subject: [PATCH 20/29] fix: add read permission to Buying Settings (backport #39158) (#39257) * fix: add read permission to Buying Settings (cherry picked from commit e05bf9d32a00aac55021faaacbfa914ee9f7275a) # Conflicts: # erpnext/buying/doctype/buying_settings/buying_settings.json * chore: resolve merge conflicts --------- Co-authored-by: s-aga-r Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com> --- .../buying_settings/buying_settings.json | 39 ++++++++++++++++++- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/erpnext/buying/doctype/buying_settings/buying_settings.json b/erpnext/buying/doctype/buying_settings/buying_settings.json index 0af93bfc902..5be70288c56 100644 --- a/erpnext/buying/doctype/buying_settings/buying_settings.json +++ b/erpnext/buying/doctype/buying_settings/buying_settings.json @@ -188,7 +188,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2023-11-24 10:55:51.287327", + "modified": "2024-01-05 15:26:02.320942", "modified_by": "Administrator", "module": "Buying", "name": "Buying Settings", @@ -212,10 +212,45 @@ "role": "Purchase Manager", "share": 1, "write": 1 + }, + { + "email": 1, + "print": 1, + "read": 1, + "role": "Accounts User", + "share": 1 + }, + { + "email": 1, + "print": 1, + "read": 1, + "role": "Accounts Manager", + "share": 1 + }, + { + "email": 1, + "print": 1, + "read": 1, + "role": "Stock Manager", + "share": 1 + }, + { + "email": 1, + "print": 1, + "read": 1, + "role": "Stock User", + "share": 1 + }, + { + "email": 1, + "print": 1, + "read": 1, + "role": "Purchase User", + "share": 1 } ], "sort_field": "modified", "sort_order": "DESC", "states": [], "track_changes": 1 -} \ No newline at end of file +} From a3146c39dd36d8edb7b99cbd57ade2472f475ee2 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 9 Jan 2024 20:37:49 +0530 Subject: [PATCH 21/29] fix: Duplicate Closing Stock Balance (backport #39262) (#39263) fix: Duplicate Closing Stock Balance (cherry picked from commit b15795392bdf8165e7ddfb60451353ebefa177d2) Co-authored-by: s-aga-r --- .../doctype/closing_stock_balance/closing_stock_balance.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/closing_stock_balance/closing_stock_balance.py b/erpnext/stock/doctype/closing_stock_balance/closing_stock_balance.py index 2f7a2dd70b7..e905f673365 100644 --- a/erpnext/stock/doctype/closing_stock_balance/closing_stock_balance.py +++ b/erpnext/stock/doctype/closing_stock_balance/closing_stock_balance.py @@ -44,7 +44,7 @@ class ClosingStockBalance(Document): & ( (table.from_date.between(self.from_date, self.to_date)) | (table.to_date.between(self.from_date, self.to_date)) - | (table.from_date >= self.from_date and table.to_date <= self.to_date) + | (table.from_date >= self.from_date and table.to_date >= self.to_date) ) ) ) From f42e93bf6c43edeec19fc8f23b9ecab8c9c385ae Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 10 Jan 2024 10:53:09 +0530 Subject: [PATCH 22/29] fix: skip rate validation for return `DN Items` with `Moving Average` valuation (backport #39242) (#39265) * fix: skip rate validation for return `DN Items` with `Moving Average` valuation (cherry picked from commit e0ad52b50036e689247f23090a5de89a7fb4abfa) # Conflicts: # erpnext/controllers/sales_and_purchase_return.py * chore: `conflicts` --------- Co-authored-by: s-aga-r --- erpnext/controllers/sales_and_purchase_return.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/erpnext/controllers/sales_and_purchase_return.py b/erpnext/controllers/sales_and_purchase_return.py index efeedc14db6..f1d1e7eaebd 100644 --- a/erpnext/controllers/sales_and_purchase_return.py +++ b/erpnext/controllers/sales_and_purchase_return.py @@ -8,7 +8,7 @@ from frappe.model.meta import get_field_precision from frappe.utils import flt, format_datetime, get_datetime import erpnext -from erpnext.stock.utils import get_incoming_rate +from erpnext.stock.utils import get_incoming_rate, get_valuation_method class StockOverReturnError(frappe.ValidationError): @@ -116,7 +116,12 @@ def validate_returned_items(doc): ref = valid_items.get(d.item_code, frappe._dict()) validate_quantity(doc, d, ref, valid_items, already_returned_items) - if ref.rate and doc.doctype in ("Delivery Note", "Sales Invoice") and flt(d.rate) > ref.rate: + if ( + ref.rate + and flt(d.rate) > ref.rate + and doc.doctype in ("Delivery Note", "Sales Invoice") + and get_valuation_method(ref.item_code) != "Moving Average" + ): frappe.throw( _("Row # {0}: Rate cannot be greater than the rate used in {1} {2}").format( d.idx, doc.doctype, doc.return_against From 41e384326eb11f6dc18097f035007678b1689c5e Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Tue, 9 Jan 2024 12:51:57 +0530 Subject: [PATCH 23/29] fix: Set asset purchase amount based on qty and valuation_rate (cherry picked from commit 135e19d0aab3b1f4ae2361b327c579468d71d6d6) --- .../purchase_invoice/purchase_invoice.py | 26 ++++++++----------- erpnext/assets/doctype/asset/asset.js | 12 ++++++--- erpnext/controllers/buying_controller.py | 9 +++---- .../purchase_receipt/purchase_receipt.py | 13 ++++++---- 4 files changed, 31 insertions(+), 29 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index cc1109b7dc8..4a9153a1ebc 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -925,17 +925,6 @@ class PurchaseInvoice(BuyingController): item=item, ) ) - - # update gross amount of asset bought through this document - assets = frappe.db.get_all( - "Asset", filters={"purchase_invoice": self.name, "item_code": item.item_code} - ) - for asset in assets: - frappe.db.set_value("Asset", asset.name, "gross_purchase_amount", flt(item.valuation_rate)) - frappe.db.set_value( - "Asset", asset.name, "purchase_receipt_amount", flt(item.valuation_rate) - ) - if ( self.auto_accounting_for_stock and self.is_opening == "No" @@ -975,17 +964,24 @@ class PurchaseInvoice(BuyingController): item.item_tax_amount, item.precision("item_tax_amount") ) + if item.is_fixed_asset and item.landed_cost_voucher_amount: + self.update_gross_purchase_amount_for_linked_assets(item) + + def update_gross_purchase_amount_for_linked_assets(self, item): assets = frappe.db.get_all( "Asset", filters={"purchase_invoice": self.name, "item_code": item.item_code}, fields=["name", "asset_quantity"], ) for asset in assets: + purchase_amount = flt(item.valuation_rate) * asset.asset_quantity frappe.db.set_value( - "Asset", asset.name, "gross_purchase_amount", flt(item.valuation_rate) * asset.asset_quantity - ) - frappe.db.set_value( - "Asset", asset.name, "purchase_receipt_amount", flt(item.valuation_rate) * asset.asset_quantity + "Asset", + asset.name, + { + "gross_purchase_amount": purchase_amount, + "purchase_receipt_amount": purchase_amount, + }, ) def make_stock_adjustment_entry( diff --git a/erpnext/assets/doctype/asset/asset.js b/erpnext/assets/doctype/asset/asset.js index 37fbd184fe1..f711577abbe 100644 --- a/erpnext/assets/doctype/asset/asset.js +++ b/erpnext/assets/doctype/asset/asset.js @@ -519,10 +519,16 @@ frappe.ui.form.on('Asset', { indicator: 'red' }); } - frm.set_value('gross_purchase_amount', item.base_net_rate + item.item_tax_amount); - frm.set_value('purchase_receipt_amount', item.base_net_rate + item.item_tax_amount); - item.asset_location && frm.set_value('location', item.asset_location); + var is_grouped_asset = frappe.db.get_value('Item', item.item_code, 'is_grouped_asset'); + var asset_quantity = is_grouped_asset ? item.qty : 1; + var purchase_amount = flt(item.valuation_rate * asset_quantity, precision('gross_purchase_amount')); + + frm.set_value('gross_purchase_amount', purchase_amount); + frm.set_value('purchase_receipt_amount', purchase_amount); + frm.set_value('asset_quantity', asset_quantity); frm.set_value('cost_center', item.cost_center || purchase_doc.cost_center); + if(item.asset_location) { frm.set_value('location', item.asset_location); } + }, set_depreciation_rate: function(frm, row) { diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py index 8e9b2289079..a2b7a65fce0 100644 --- a/erpnext/controllers/buying_controller.py +++ b/erpnext/controllers/buying_controller.py @@ -730,11 +730,8 @@ class BuyingController(SubcontractingController): item_data = frappe.db.get_value( "Item", row.item_code, ["asset_naming_series", "asset_category"], as_dict=1 ) - - if is_grouped_asset: - purchase_amount = flt(row.base_amount + row.item_tax_amount) - else: - purchase_amount = flt(row.base_rate + row.item_tax_amount) + asset_quantity = row.qty if is_grouped_asset else 1 + purchase_amount = flt(row.valuation_rate) * asset_quantity asset = frappe.get_doc( { @@ -750,7 +747,7 @@ class BuyingController(SubcontractingController): "calculate_depreciation": 1, "purchase_receipt_amount": purchase_amount, "gross_purchase_amount": purchase_amount, - "asset_quantity": row.qty if is_grouped_asset else 1, + "asset_quantity": asset_quantity, "purchase_receipt": self.name if self.doctype == "Purchase Receipt" else None, "purchase_invoice": self.name if self.doctype == "Purchase Invoice" else None, "cost_center": row.cost_center, diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index 032e2fec822..d7cefe36473 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -606,7 +606,7 @@ class PurchaseReceipt(BuyingController): ): warehouse_with_no_account.append(d.warehouse or d.rejected_warehouse) - if d.is_fixed_asset: + if d.is_fixed_asset and d.landed_cost_voucher_amount: self.update_assets(d, d.valuation_rate) if warehouse_with_no_account: @@ -738,11 +738,14 @@ class PurchaseReceipt(BuyingController): ) for asset in assets: + purchase_amount = flt(valuation_rate) * asset.asset_quantity frappe.db.set_value( - "Asset", asset.name, "gross_purchase_amount", flt(valuation_rate) * asset.asset_quantity - ) - frappe.db.set_value( - "Asset", asset.name, "purchase_receipt_amount", flt(valuation_rate) * asset.asset_quantity + "Asset", + asset.name, + { + "gross_purchase_amount": purchase_amount, + "purchase_receipt_amount": purchase_amount, + }, ) def update_status(self, status): From 952cee3d6a625561c2d39252c2dcfd3d67a7be1a Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Mon, 8 Jan 2024 12:45:42 +0530 Subject: [PATCH 24/29] fix: Ignore asset qty and status validation while cancelling LCV (cherry picked from commit e9d36242ce3e09ee423f917eede2275bded10d6d) --- .../landed_cost_voucher/landed_cost_voucher.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py index 7f0dc2df9f3..3511cec2fd7 100644 --- a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py +++ b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py @@ -167,7 +167,8 @@ class LandedCostVoucher(Document): for d in self.get("purchase_receipts"): doc = frappe.get_doc(d.receipt_document_type, d.receipt_document) # check if there are {qty} assets created and linked to this receipt document - self.validate_asset_qty_and_status(d.receipt_document_type, doc) + if self.docstatus != 2: + self.validate_asset_qty_and_status(d.receipt_document_type, doc) # set landed cost voucher amount in pr item doc.set_landed_cost_voucher_amount() @@ -208,20 +209,20 @@ class LandedCostVoucher(Document): filters={receipt_document_type: item.receipt_document, "item_code": item.item_code}, fields=["name", "docstatus"], ) - if not docs or len(docs) != item.qty: + if not docs or len(docs) < item.qty: frappe.throw( _( - "There are not enough asset created or linked to {0}. Please create or link {1} Assets with respective document." - ).format(item.receipt_document, item.qty) + "There are only {0} asset created or linked to {1}. Please create or link {2} Assets with respective document." + ).format(len(docs), item.receipt_document, item.qty) ) if docs: for d in docs: if d.docstatus == 1: frappe.throw( _( - "{2} {0} has submitted Assets. Remove Item {1} from table to continue." + "{0} {1} has submitted Assets. Remove Item {2} from table to continue." ).format( - item.receipt_document, item.item_code, item.receipt_document_type + item.receipt_document_type, item.receipt_document, item.item_code ) ) From 8fe346aef887acfe4939549acc69d380a60b80e1 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Wed, 10 Jan 2024 11:34:20 +0530 Subject: [PATCH 25/29] fix: possible typeerror on transaction.js (cherry picked from commit 9f27ac142b6239bb01fb8304056776f1fae24694) --- erpnext/public/js/controllers/transaction.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 0f291e1eba7..48bde9dbc98 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -739,7 +739,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe if (me.frm.doc.price_list_currency == company_currency) { me.frm.set_value('plc_conversion_rate', 1.0); } - if (company_doc.default_letter_head) { + if (company_doc && company_doc.default_letter_head) { if(me.frm.fields_dict.letter_head) { me.frm.set_value("letter_head", company_doc.default_letter_head); } From b52988389750e6d18eb5407ec18bf84b63d783d4 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Wed, 10 Jan 2024 12:01:32 +0530 Subject: [PATCH 26/29] fix: resolved conflict --- erpnext/assets/doctype/asset/asset.py | 84 --------------------------- 1 file changed, 84 deletions(-) diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index 300d95d37ee..3397f7708f5 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -34,86 +34,6 @@ from erpnext.controllers.accounts_controller import AccountsController class Asset(AccountsController): -<<<<<<< HEAD -======= - # begin: auto-generated types - # This code is auto-generated. Do not modify anything in this block. - - from typing import TYPE_CHECKING - - if TYPE_CHECKING: - from frappe.types import DF - - from erpnext.assets.doctype.asset_finance_book.asset_finance_book import AssetFinanceBook - - additional_asset_cost: DF.Currency - amended_from: DF.Link | None - asset_category: DF.Link | None - asset_name: DF.Data - asset_owner: DF.Literal["", "Company", "Supplier", "Customer"] - asset_owner_company: DF.Link | None - asset_quantity: DF.Int - available_for_use_date: DF.Date | None - booked_fixed_asset: DF.Check - calculate_depreciation: DF.Check - capitalized_in: DF.Link | None - company: DF.Link - comprehensive_insurance: DF.Data | None - cost_center: DF.Link | None - custodian: DF.Link | None - customer: DF.Link | None - default_finance_book: DF.Link | None - department: DF.Link | None - depr_entry_posting_status: DF.Literal["", "Successful", "Failed"] - depreciation_method: DF.Literal["", "Straight Line", "Double Declining Balance", "Manual"] - disposal_date: DF.Date | None - finance_books: DF.Table[AssetFinanceBook] - frequency_of_depreciation: DF.Int - gross_purchase_amount: DF.Currency - image: DF.AttachImage | None - insurance_end_date: DF.Date | None - insurance_start_date: DF.Date | None - insured_value: DF.Data | None - insurer: DF.Data | None - is_composite_asset: DF.Check - is_existing_asset: DF.Check - is_fully_depreciated: DF.Check - item_code: DF.Link - item_name: DF.ReadOnly | None - journal_entry_for_scrap: DF.Link | None - location: DF.Link - maintenance_required: DF.Check - naming_series: DF.Literal["ACC-ASS-.YYYY.-"] - next_depreciation_date: DF.Date | None - number_of_depreciations_booked: DF.Int - opening_accumulated_depreciation: DF.Currency - policy_number: DF.Data | None - purchase_date: DF.Date | None - purchase_invoice: DF.Link | None - purchase_receipt: DF.Link | None - purchase_receipt_amount: DF.Currency - split_from: DF.Link | None - status: DF.Literal[ - "Draft", - "Submitted", - "Partially Depreciated", - "Fully Depreciated", - "Sold", - "Scrapped", - "In Maintenance", - "Out of Order", - "Issue", - "Receipt", - "Capitalized", - "Decapitalized", - ] - supplier: DF.Link | None - total_asset_cost: DF.Currency - total_number_of_depreciations: DF.Int - value_after_depreciation: DF.Currency - # end: auto-generated types - ->>>>>>> c34f09c503 (fix: Purchase date and amount is not mandatory for composite asset creation) def validate(self): self.validate_asset_values() self.validate_asset_and_reference() @@ -327,16 +247,12 @@ class Asset(AccountsController): frappe.throw(_("Gross Purchase Amount is mandatory"), frappe.MandatoryError) if is_cwip_accounting_enabled(self.asset_category): -<<<<<<< HEAD - if not self.is_existing_asset and not (self.purchase_receipt or self.purchase_invoice): -======= if ( not self.is_existing_asset and not self.is_composite_asset and not self.purchase_receipt and not self.purchase_invoice ): ->>>>>>> c34f09c503 (fix: Purchase date and amount is not mandatory for composite asset creation) frappe.throw( _("Please create purchase receipt or purchase invoice for the item {0}").format( self.item_code From a2a85586778dfb31f907a37d6a7c3cc616aebc81 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 10 Jan 2024 12:27:13 +0530 Subject: [PATCH 27/29] fix: set parent doctype on chart (backport #39286) (#39287) fix: set parent doctype on chart (#39286) (cherry picked from commit 38c5ecf007751b0e3b152ee5089e7a8e00f17f41) Co-authored-by: Ankush Menat --- .../completed_operation/completed_operation.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/manufacturing/dashboard_chart/completed_operation/completed_operation.json b/erpnext/manufacturing/dashboard_chart/completed_operation/completed_operation.json index d74ae2faf4d..146214af429 100644 --- a/erpnext/manufacturing/dashboard_chart/completed_operation/completed_operation.json +++ b/erpnext/manufacturing/dashboard_chart/completed_operation/completed_operation.json @@ -12,12 +12,13 @@ "is_public": 1, "is_standard": 1, "last_synced_on": "2020-07-21 16:57:09.767009", - "modified": "2020-07-21 16:57:55.719802", + "modified": "2024-01-10 12:21:25.134075", "modified_by": "Administrator", "module": "Manufacturing", "name": "Completed Operation", "number_of_groups": 0, "owner": "Administrator", + "parent_document_type": "Work Order", "time_interval": "Quarterly", "timeseries": 1, "timespan": "Last Year", From ee7474ba203d57f0835d31eb80dfdd8f6608af23 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 10 Jan 2024 12:31:59 +0530 Subject: [PATCH 28/29] fix: remove global _("translation") calls (backport #32828) (#39231) fix: remove global _("translation") calls (#32828) This is not how it works. Translations are dynamic based on language sets during request (using header, user's preferences etc) Calling them on global variables makes no sense. Ref: https://github.com/frappe/frappe/pull/18733 (cherry picked from commit 75983ce80912821dccc1432ca6d23cb60bccea92) Co-authored-by: Ankush Menat --- erpnext/hooks.py | 52 +++++++++++++++++++++++------------------------- 1 file changed, 25 insertions(+), 27 deletions(-) diff --git a/erpnext/hooks.py b/erpnext/hooks.py index 054f9dedabe..97d71a5f2a3 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -1,5 +1,3 @@ -from frappe import _ - app_name = "erpnext" app_title = "ERPNext" app_publisher = "Frappe Technologies Pvt. Ltd." @@ -92,7 +90,7 @@ website_route_rules = [ { "from_route": "/orders/", "to_route": "order", - "defaults": {"doctype": "Sales Order", "parents": [{"label": _("Orders"), "route": "orders"}]}, + "defaults": {"doctype": "Sales Order", "parents": [{"label": "Orders", "route": "orders"}]}, }, {"from_route": "/invoices", "to_route": "Sales Invoice"}, { @@ -100,7 +98,7 @@ website_route_rules = [ "to_route": "order", "defaults": { "doctype": "Sales Invoice", - "parents": [{"label": _("Invoices"), "route": "invoices"}], + "parents": [{"label": "Invoices", "route": "invoices"}], }, }, {"from_route": "/supplier-quotations", "to_route": "Supplier Quotation"}, @@ -109,7 +107,7 @@ website_route_rules = [ "to_route": "order", "defaults": { "doctype": "Supplier Quotation", - "parents": [{"label": _("Supplier Quotation"), "route": "supplier-quotations"}], + "parents": [{"label": "Supplier Quotation", "route": "supplier-quotations"}], }, }, {"from_route": "/purchase-orders", "to_route": "Purchase Order"}, @@ -118,7 +116,7 @@ website_route_rules = [ "to_route": "order", "defaults": { "doctype": "Purchase Order", - "parents": [{"label": _("Purchase Order"), "route": "purchase-orders"}], + "parents": [{"label": "Purchase Order", "route": "purchase-orders"}], }, }, {"from_route": "/purchase-invoices", "to_route": "Purchase Invoice"}, @@ -127,7 +125,7 @@ website_route_rules = [ "to_route": "order", "defaults": { "doctype": "Purchase Invoice", - "parents": [{"label": _("Purchase Invoice"), "route": "purchase-invoices"}], + "parents": [{"label": "Purchase Invoice", "route": "purchase-invoices"}], }, }, {"from_route": "/quotations", "to_route": "Quotation"}, @@ -136,7 +134,7 @@ website_route_rules = [ "to_route": "order", "defaults": { "doctype": "Quotation", - "parents": [{"label": _("Quotations"), "route": "quotations"}], + "parents": [{"label": "Quotations", "route": "quotations"}], }, }, {"from_route": "/shipments", "to_route": "Delivery Note"}, @@ -145,7 +143,7 @@ website_route_rules = [ "to_route": "order", "defaults": { "doctype": "Delivery Note", - "parents": [{"label": _("Shipments"), "route": "shipments"}], + "parents": [{"label": "Shipments", "route": "shipments"}], }, }, {"from_route": "/rfq", "to_route": "Request for Quotation"}, @@ -154,14 +152,14 @@ website_route_rules = [ "to_route": "rfq", "defaults": { "doctype": "Request for Quotation", - "parents": [{"label": _("Request for Quotation"), "route": "rfq"}], + "parents": [{"label": "Request for Quotation", "route": "rfq"}], }, }, {"from_route": "/addresses", "to_route": "Address"}, { "from_route": "/addresses/", "to_route": "addresses", - "defaults": {"doctype": "Address", "parents": [{"label": _("Addresses"), "route": "addresses"}]}, + "defaults": {"doctype": "Address", "parents": [{"label": "Addresses", "route": "addresses"}]}, }, {"from_route": "/boms", "to_route": "BOM"}, {"from_route": "/timesheets", "to_route": "Timesheet"}, @@ -171,78 +169,78 @@ website_route_rules = [ "to_route": "material_request_info", "defaults": { "doctype": "Material Request", - "parents": [{"label": _("Material Request"), "route": "material-requests"}], + "parents": [{"label": "Material Request", "route": "material-requests"}], }, }, {"from_route": "/project", "to_route": "Project"}, ] standard_portal_menu_items = [ - {"title": _("Projects"), "route": "/project", "reference_doctype": "Project"}, + {"title": "Projects", "route": "/project", "reference_doctype": "Project"}, { - "title": _("Request for Quotations"), + "title": "Request for Quotations", "route": "/rfq", "reference_doctype": "Request for Quotation", "role": "Supplier", }, { - "title": _("Supplier Quotation"), + "title": "Supplier Quotation", "route": "/supplier-quotations", "reference_doctype": "Supplier Quotation", "role": "Supplier", }, { - "title": _("Purchase Orders"), + "title": "Purchase Orders", "route": "/purchase-orders", "reference_doctype": "Purchase Order", "role": "Supplier", }, { - "title": _("Purchase Invoices"), + "title": "Purchase Invoices", "route": "/purchase-invoices", "reference_doctype": "Purchase Invoice", "role": "Supplier", }, { - "title": _("Quotations"), + "title": "Quotations", "route": "/quotations", "reference_doctype": "Quotation", "role": "Customer", }, { - "title": _("Orders"), + "title": "Orders", "route": "/orders", "reference_doctype": "Sales Order", "role": "Customer", }, { - "title": _("Invoices"), + "title": "Invoices", "route": "/invoices", "reference_doctype": "Sales Invoice", "role": "Customer", }, { - "title": _("Shipments"), + "title": "Shipments", "route": "/shipments", "reference_doctype": "Delivery Note", "role": "Customer", }, - {"title": _("Issues"), "route": "/issues", "reference_doctype": "Issue", "role": "Customer"}, - {"title": _("Addresses"), "route": "/addresses", "reference_doctype": "Address"}, + {"title": "Issues", "route": "/issues", "reference_doctype": "Issue", "role": "Customer"}, + {"title": "Addresses", "route": "/addresses", "reference_doctype": "Address"}, { - "title": _("Timesheets"), + "title": "Timesheets", "route": "/timesheets", "reference_doctype": "Timesheet", "role": "Customer", }, - {"title": _("Newsletter"), "route": "/newsletters", "reference_doctype": "Newsletter"}, + {"title": "Newsletter", "route": "/newsletters", "reference_doctype": "Newsletter"}, { - "title": _("Material Request"), + "title": "Material Request", "route": "/material-requests", "reference_doctype": "Material Request", "role": "Customer", }, - {"title": _("Appointment Booking"), "route": "/book_appointment"}, + {"title": "Appointment Booking", "route": "/book_appointment"}, ] default_roles = [ From 1cc887a997ee0c2990bff9f14aaa51a24452308a Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 10 Jan 2024 12:33:28 +0530 Subject: [PATCH 29/29] fix: Show timesheet table after fetching data from timesheet (backport #39275) (#39280) fix: Show timesheet table after fetching data from timesheet (cherry picked from commit e1ba5878a3c61d44006dbc5f1f269e6a2542583c) Co-authored-by: Nabin Hait --- erpnext/accounts/doctype/sales_invoice/sales_invoice.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js index 1a04841fd52..b236b447d57 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js @@ -890,8 +890,8 @@ frappe.ui.form.on('Sales Invoice', { frm.events.append_time_log(frm, timesheet, 1.0); } }); - frm.refresh_field("timesheets"); frm.trigger("calculate_timesheet_totals"); + frm.refresh(); }, async get_exchange_rate(frm, from_currency, to_currency) {