From db3d2c31a3a6809a2a1e9faba331223c63959cc5 Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Fri, 19 Nov 2021 05:27:06 +0530 Subject: [PATCH 01/42] fix: Prevent clearing of Depreciation Schedule on adding more than one Finance Book (cherry picked from commit a4043c035d4116e206e486d4eecabe897cf24630) --- erpnext/assets/doctype/asset/asset.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index beb46f37ab1..dc7160eed0d 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -186,11 +186,11 @@ class Asset(AccountsController): if not self.available_for_use_date: return + start = self.clear_depreciation_schedule() + for d in self.get('finance_books'): self.validate_asset_finance_books(d) - start = self.clear_depreciation_schedule() - # value_after_depreciation - current Asset value if self.docstatus == 1 and d.value_after_depreciation: value_after_depreciation = flt(d.value_after_depreciation) From 54cac351cdb1b087374866f018051092ade398e8 Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Fri, 19 Nov 2021 05:35:41 +0530 Subject: [PATCH 02/42] fix: Rename loop variable (cherry picked from commit 059d1f3b74b2bee707387cea518eeb394bfce3d8) # Conflicts: # erpnext/assets/doctype/asset/asset.py --- erpnext/assets/doctype/asset/asset.py | 72 ++++++++++++++++----------- 1 file changed, 44 insertions(+), 28 deletions(-) diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index dc7160eed0d..5b0601cc4c0 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -188,22 +188,28 @@ class Asset(AccountsController): start = self.clear_depreciation_schedule() - for d in self.get('finance_books'): - self.validate_asset_finance_books(d) + for finance_book in self.get('finance_books'): + self.validate_asset_finance_books(finance_book) # value_after_depreciation - current Asset value +<<<<<<< HEAD if self.docstatus == 1 and d.value_after_depreciation: value_after_depreciation = flt(d.value_after_depreciation) +======= + if self.docstatus == 1 and finance_book.value_after_depreciation: + value_after_depreciation = (flt(finance_book.value_after_depreciation) - + flt(self.opening_accumulated_depreciation)) +>>>>>>> 059d1f3b74 (fix: Rename loop variable) else: value_after_depreciation = (flt(self.gross_purchase_amount) - flt(self.opening_accumulated_depreciation)) - d.value_after_depreciation = value_after_depreciation + finance_book.value_after_depreciation = value_after_depreciation - number_of_pending_depreciations = cint(d.total_number_of_depreciations) - \ + number_of_pending_depreciations = cint(finance_book.total_number_of_depreciations) - \ cint(self.number_of_depreciations_booked) - has_pro_rata = self.check_is_pro_rata(d) + has_pro_rata = self.check_is_pro_rata(finance_book) if has_pro_rata: number_of_pending_depreciations += 1 @@ -213,56 +219,66 @@ class Asset(AccountsController): # If depreciation is already completed (for double declining balance) if skip_row: continue - depreciation_amount = get_depreciation_amount(self, value_after_depreciation, d) + depreciation_amount = get_depreciation_amount(self, value_after_depreciation, finance_book) if not has_pro_rata or n < cint(number_of_pending_depreciations) - 1: - schedule_date = add_months(d.depreciation_start_date, - n * cint(d.frequency_of_depreciation)) + schedule_date = add_months(finance_book.depreciation_start_date, + n * cint(finance_book.frequency_of_depreciation)) # schedule date will be a year later from start date # so monthly schedule date is calculated by removing 11 months from it - monthly_schedule_date = add_months(schedule_date, - d.frequency_of_depreciation + 1) + monthly_schedule_date = add_months(schedule_date, - finance_book.frequency_of_depreciation + 1) # if asset is being sold if date_of_sale: - from_date = self.get_from_date(d.finance_book) - depreciation_amount, days, months = self.get_pro_rata_amt(d, depreciation_amount, + from_date = self.get_from_date(finance_book.finance_book) + depreciation_amount, days, months = self.get_pro_rata_amt(finance_book, depreciation_amount, from_date, date_of_sale) if depreciation_amount > 0: self.append("schedules", { "schedule_date": date_of_sale, "depreciation_amount": depreciation_amount, - "depreciation_method": d.depreciation_method, - "finance_book": d.finance_book, - "finance_book_id": d.idx + "depreciation_method": finance_book.depreciation_method, + "finance_book": finance_book.finance_book, + "finance_book_id": finance_book.idx }) break # For first row +<<<<<<< HEAD if has_pro_rata and not self.opening_accumulated_depreciation and n==0: depreciation_amount, days, months = self.get_pro_rata_amt(d, depreciation_amount, self.available_for_use_date, d.depreciation_start_date) +======= + if has_pro_rata and n==0: + depreciation_amount, days, months = self.get_pro_rata_amt(finance_book, depreciation_amount, + self.available_for_use_date, finance_book.depreciation_start_date) +>>>>>>> 059d1f3b74 (fix: Rename loop variable) # For first depr schedule date will be the start date # so monthly schedule date is calculated by removing month difference between use date and start date - monthly_schedule_date = add_months(d.depreciation_start_date, - months + 1) + monthly_schedule_date = add_months(finance_book.depreciation_start_date, - months + 1) # For last row elif has_pro_rata and n == cint(number_of_pending_depreciations) - 1: if not self.flags.increase_in_asset_life: # In case of increase_in_asset_life, the self.to_date is already set on asset_repair submission self.to_date = add_months(self.available_for_use_date, +<<<<<<< HEAD (n + self.number_of_depreciations_booked) * cint(d.frequency_of_depreciation)) +======= + n * cint(finance_book.frequency_of_depreciation)) +>>>>>>> 059d1f3b74 (fix: Rename loop variable) depreciation_amount_without_pro_rata = depreciation_amount - depreciation_amount, days, months = self.get_pro_rata_amt(d, + depreciation_amount, days, months = self.get_pro_rata_amt(finance_book, depreciation_amount, schedule_date, self.to_date) depreciation_amount = self.get_adjusted_depreciation_amount(depreciation_amount_without_pro_rata, - depreciation_amount, d.finance_book) + depreciation_amount, finance_book.finance_book) monthly_schedule_date = add_months(schedule_date, 1) schedule_date = add_days(schedule_date, days) @@ -273,10 +289,10 @@ class Asset(AccountsController): self.precision("gross_purchase_amount")) # Adjust depreciation amount in the last period based on the expected value after useful life - if d.expected_value_after_useful_life and ((n == cint(number_of_pending_depreciations) - 1 - and value_after_depreciation != d.expected_value_after_useful_life) - or value_after_depreciation < d.expected_value_after_useful_life): - depreciation_amount += (value_after_depreciation - d.expected_value_after_useful_life) + if finance_book.expected_value_after_useful_life and ((n == cint(number_of_pending_depreciations) - 1 + and value_after_depreciation != finance_book.expected_value_after_useful_life) + or value_after_depreciation < finance_book.expected_value_after_useful_life): + depreciation_amount += (value_after_depreciation - finance_book.expected_value_after_useful_life) skip_row = True if depreciation_amount > 0: @@ -286,7 +302,7 @@ class Asset(AccountsController): # In pro rata case, for first and last depreciation, month range would be different month_range = months \ if (has_pro_rata and n==0) or (has_pro_rata and n == cint(number_of_pending_depreciations) - 1) \ - else d.frequency_of_depreciation + else finance_book.frequency_of_depreciation for r in range(month_range): if (has_pro_rata and n == 0): @@ -312,17 +328,17 @@ class Asset(AccountsController): self.append("schedules", { "schedule_date": date, "depreciation_amount": amount, - "depreciation_method": d.depreciation_method, - "finance_book": d.finance_book, - "finance_book_id": d.idx + "depreciation_method": finance_book.depreciation_method, + "finance_book": finance_book.finance_book, + "finance_book_id": finance_book.idx }) else: self.append("schedules", { "schedule_date": schedule_date, "depreciation_amount": depreciation_amount, - "depreciation_method": d.depreciation_method, - "finance_book": d.finance_book, - "finance_book_id": d.idx + "depreciation_method": finance_book.depreciation_method, + "finance_book": finance_book.finance_book, + "finance_book_id": finance_book.idx }) # used when depreciation schedule needs to be modified due to increase in asset life From 8a4019e501fc7d0ad93b95b83e701d3d3b3c5c48 Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Mon, 22 Nov 2021 21:23:27 +0530 Subject: [PATCH 03/42] fix: Clear Depreciation Schedule entries that aren't linked with Journal Entries before modifying the schedule (cherry picked from commit 475d8394e413d530ec57d9c7f713fecd706b2271) --- erpnext/assets/doctype/asset/asset.py | 42 ++++++++++++++++++++++----- 1 file changed, 34 insertions(+), 8 deletions(-) diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index 5b0601cc4c0..9883442ffe3 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -215,7 +215,8 @@ class Asset(AccountsController): number_of_pending_depreciations += 1 skip_row = False - for n in range(start, number_of_pending_depreciations): + + for n in range(start[finance_book.idx-1], number_of_pending_depreciations): # If depreciation is already completed (for double declining balance) if skip_row: continue @@ -341,14 +342,39 @@ class Asset(AccountsController): "finance_book_id": finance_book.idx }) - # used when depreciation schedule needs to be modified due to increase in asset life + # depreciation schedules need to be cleared before modification due to increase in asset life/asset sales + # JE: Journal Entry, FB: Finance Book def clear_depreciation_schedule(self): - start = 0 - for n in range(len(self.schedules)): - if not self.schedules[n].journal_entry: - del self.schedules[n:] - start = n - break + start = [] + num_of_depreciations_completed = 0 + depr_schedule = [] + + for schedule in self.get('schedules'): + + # to ensure that start will only be updated once for each FB + if len(start) == (int(schedule.finance_book_id) - 1): + if schedule.journal_entry: + num_of_depreciations_completed += 1 + depr_schedule.append(schedule) + else: + start.append(num_of_depreciations_completed) + num_of_depreciations_completed = 0 + + # to update start when there are JEs linked with all the schedule rows corresponding to an FB + elif len(start) == (int(schedule.finance_book_id) - 2): + start.append(num_of_depreciations_completed) + num_of_depreciations_completed = 0 + + # to update start when all the schedule rows corresponding to the last FB are linked with JEs + if len(start) == (len(self.finance_books) - 1): + start.append(num_of_depreciations_completed) + + # when the Depreciation Schedule is being created for the first time + if start == []: + start = [0] * len(self.finance_books) + else: + self.schedules = depr_schedule + return start def get_from_date(self, finance_book): From 57f41bc30a440187368f17137bcf1a98cfbdd319 Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Tue, 23 Nov 2021 23:26:32 +0530 Subject: [PATCH 04/42] fix: Test if multiple Depreciation Schedules are set up for multiple Finance Books (cherry picked from commit f455de2924f7c7554b9eefc757c354992901136b) --- erpnext/assets/doctype/asset/test_asset.py | 32 ++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py index ba4dbee72da..30a1d771817 100644 --- a/erpnext/assets/doctype/asset/test_asset.py +++ b/erpnext/assets/doctype/asset/test_asset.py @@ -976,6 +976,38 @@ class TestDepreciationBasics(AssetSetup): self.assertEqual(len(asset.schedules), 1) + def test_depreciation_schedules_are_set_up_for_multiple_finance_books(self): + asset = create_asset( + item_code = "Macbook Pro", + available_for_use_date = "2019-12-31", + do_not_save = 1 + ) + + asset.calculate_depreciation = 1 + asset.append("finance_books", { + "depreciation_method": "Straight Line", + "frequency_of_depreciation": 12, + "total_number_of_depreciations": 3, + "expected_value_after_useful_life": 10000, + "depreciation_start_date": "2020-12-31" + }) + asset.append("finance_books", { + "depreciation_method": "Straight Line", + "frequency_of_depreciation": 12, + "total_number_of_depreciations": 6, + "expected_value_after_useful_life": 10000, + "depreciation_start_date": "2020-12-31" + }) + asset.save() + + self.assertEqual(len(asset.schedules), 9) + + for schedule in asset.schedules: + if schedule.idx <= 3: + self.assertEqual(schedule.finance_book_id, 1) + else: + self.assertEqual(schedule.finance_book_id, 2) + def test_depreciation_entry_cancellation(self): asset = create_asset( item_code = "Macbook Pro", From ae875ab021edc574220b0df70ef017fe86d562e4 Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Wed, 24 Nov 2021 02:45:17 +0530 Subject: [PATCH 05/42] fix: Retain depreciation schedule rows that are linked with JEs while clearing the schedule (cherry picked from commit 33a0b1db2ced1fe84386dbd6b1e5eacb535e5098) --- erpnext/assets/doctype/asset/asset.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index 9883442ffe3..36d68b577ee 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -351,6 +351,11 @@ class Asset(AccountsController): for schedule in self.get('schedules'): + # to update start when there are JEs linked with all the schedule rows corresponding to an FB + if len(start) == (int(schedule.finance_book_id) - 2): + start.append(num_of_depreciations_completed) + num_of_depreciations_completed = 0 + # to ensure that start will only be updated once for each FB if len(start) == (int(schedule.finance_book_id) - 1): if schedule.journal_entry: @@ -360,11 +365,6 @@ class Asset(AccountsController): start.append(num_of_depreciations_completed) num_of_depreciations_completed = 0 - # to update start when there are JEs linked with all the schedule rows corresponding to an FB - elif len(start) == (int(schedule.finance_book_id) - 2): - start.append(num_of_depreciations_completed) - num_of_depreciations_completed = 0 - # to update start when all the schedule rows corresponding to the last FB are linked with JEs if len(start) == (len(self.finance_books) - 1): start.append(num_of_depreciations_completed) From 855ae399b5414a5f912c1256cf5f209559e55575 Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Wed, 24 Nov 2021 03:19:36 +0530 Subject: [PATCH 06/42] fix: Test if clear_depreciation_schedule() works for multiple finance books (cherry picked from commit 6ec5a190631b563804748fe43532b70d0f230ff9) --- erpnext/assets/doctype/asset/test_asset.py | 44 ++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py index 30a1d771817..8103db3acc4 100644 --- a/erpnext/assets/doctype/asset/test_asset.py +++ b/erpnext/assets/doctype/asset/test_asset.py @@ -976,6 +976,50 @@ class TestDepreciationBasics(AssetSetup): self.assertEqual(len(asset.schedules), 1) + def test_clear_depreciation_schedule_for_multiple_finance_books(self): + asset = create_asset( + item_code = "Macbook Pro", + available_for_use_date = "2019-12-31", + do_not_save = 1 + ) + + asset.calculate_depreciation = 1 + asset.append("finance_books", { + "depreciation_method": "Straight Line", + "frequency_of_depreciation": 12, + "total_number_of_depreciations": 3, + "expected_value_after_useful_life": 10000, + "depreciation_start_date": "2020-12-31" + }) + asset.append("finance_books", { + "depreciation_method": "Straight Line", + "frequency_of_depreciation": 12, + "total_number_of_depreciations": 6, + "expected_value_after_useful_life": 10000, + "depreciation_start_date": "2020-12-31" + }) + asset.append("finance_books", { + "depreciation_method": "Straight Line", + "frequency_of_depreciation": 12, + "total_number_of_depreciations": 3, + "expected_value_after_useful_life": 10000, + "depreciation_start_date": "2023-12-31" + }) + asset.submit() + + post_depreciation_entries(date="2023-01-01") + asset.load_from_db() + + asset.clear_depreciation_schedule() + + self.assertEqual(len(asset.schedules), 6) + + for schedule in asset.schedules: + if schedule.idx <= 3: + self.assertEqual(schedule.finance_book_id, "1") + else: + self.assertEqual(schedule.finance_book_id, "2") + def test_depreciation_schedules_are_set_up_for_multiple_finance_books(self): asset = create_asset( item_code = "Macbook Pro", From d000782de0353f2130f828d31864d95474b39d88 Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Wed, 24 Nov 2021 05:21:31 +0530 Subject: [PATCH 07/42] fix: Edit dates and frequency of depreciation (cherry picked from commit 4311936b7dd2bf7e3f5e11fe5ce6eeff03a7d49b) --- erpnext/assets/doctype/asset/test_asset.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py index 8103db3acc4..1f30bbfaff5 100644 --- a/erpnext/assets/doctype/asset/test_asset.py +++ b/erpnext/assets/doctype/asset/test_asset.py @@ -986,28 +986,28 @@ class TestDepreciationBasics(AssetSetup): asset.calculate_depreciation = 1 asset.append("finance_books", { "depreciation_method": "Straight Line", - "frequency_of_depreciation": 12, + "frequency_of_depreciation": 1, "total_number_of_depreciations": 3, "expected_value_after_useful_life": 10000, - "depreciation_start_date": "2020-12-31" + "depreciation_start_date": "2020-01-31" }) asset.append("finance_books", { "depreciation_method": "Straight Line", - "frequency_of_depreciation": 12, + "frequency_of_depreciation": 1, "total_number_of_depreciations": 6, "expected_value_after_useful_life": 10000, - "depreciation_start_date": "2020-12-31" + "depreciation_start_date": "2020-01-31" }) asset.append("finance_books", { "depreciation_method": "Straight Line", "frequency_of_depreciation": 12, "total_number_of_depreciations": 3, "expected_value_after_useful_life": 10000, - "depreciation_start_date": "2023-12-31" + "depreciation_start_date": "2020-12-31" }) asset.submit() - post_depreciation_entries(date="2023-01-01") + post_depreciation_entries(date="2020-04-01") asset.load_from_db() asset.clear_depreciation_schedule() From c9a9d0bab8ba50d511d68e75e9428dbb607f7ed4 Mon Sep 17 00:00:00 2001 From: Devin Slauenwhite Date: Wed, 29 Dec 2021 21:47:04 -0500 Subject: [PATCH 08/42] fix(ux): tripple digit cart qty --- erpnext/public/scss/shopping_cart.scss | 1 - 1 file changed, 1 deletion(-) diff --git a/erpnext/public/scss/shopping_cart.scss b/erpnext/public/scss/shopping_cart.scss index e07bcbd28ec..1e01c474c2b 100644 --- a/erpnext/public/scss/shopping_cart.scss +++ b/erpnext/public/scss/shopping_cart.scss @@ -588,7 +588,6 @@ body.product-page { top: -10px; left: -12px; background: var(--red-600); - width: 16px; align-items: center; height: 16px; font-size: 10px; From 852543cbcd368ada8a9e10033d99bb1473ec90d4 Mon Sep 17 00:00:00 2001 From: Ganga Manoj Date: Wed, 12 Jan 2022 14:05:09 +0530 Subject: [PATCH 09/42] fix: Merge conflicts --- erpnext/assets/doctype/asset/asset.py | 20 ++------------------ 1 file changed, 2 insertions(+), 18 deletions(-) diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index 36d68b577ee..264ead6e003 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -192,14 +192,8 @@ class Asset(AccountsController): self.validate_asset_finance_books(finance_book) # value_after_depreciation - current Asset value -<<<<<<< HEAD - if self.docstatus == 1 and d.value_after_depreciation: - value_after_depreciation = flt(d.value_after_depreciation) -======= if self.docstatus == 1 and finance_book.value_after_depreciation: - value_after_depreciation = (flt(finance_book.value_after_depreciation) - - flt(self.opening_accumulated_depreciation)) ->>>>>>> 059d1f3b74 (fix: Rename loop variable) + value_after_depreciation = flt(finance_book.value_after_depreciation) else: value_after_depreciation = (flt(self.gross_purchase_amount) - flt(self.opening_accumulated_depreciation)) @@ -248,15 +242,9 @@ class Asset(AccountsController): break # For first row -<<<<<<< HEAD if has_pro_rata and not self.opening_accumulated_depreciation and n==0: - depreciation_amount, days, months = self.get_pro_rata_amt(d, depreciation_amount, - self.available_for_use_date, d.depreciation_start_date) -======= - if has_pro_rata and n==0: depreciation_amount, days, months = self.get_pro_rata_amt(finance_book, depreciation_amount, self.available_for_use_date, finance_book.depreciation_start_date) ->>>>>>> 059d1f3b74 (fix: Rename loop variable) # For first depr schedule date will be the start date # so monthly schedule date is calculated by removing month difference between use date and start date @@ -267,11 +255,7 @@ class Asset(AccountsController): if not self.flags.increase_in_asset_life: # In case of increase_in_asset_life, the self.to_date is already set on asset_repair submission self.to_date = add_months(self.available_for_use_date, -<<<<<<< HEAD - (n + self.number_of_depreciations_booked) * cint(d.frequency_of_depreciation)) -======= - n * cint(finance_book.frequency_of_depreciation)) ->>>>>>> 059d1f3b74 (fix: Rename loop variable) + (n + self.number_of_depreciations_booked) * cint(finance_book.frequency_of_depreciation)) depreciation_amount_without_pro_rata = depreciation_amount From c8eae9dcef164182932dd4a94bde451a89fee456 Mon Sep 17 00:00:00 2001 From: Anupam Date: Tue, 18 Jan 2022 16:14:45 +0530 Subject: [PATCH 10/42] fix: contact duplication on converting lead to customer --- erpnext/selling/doctype/customer/customer.py | 70 +++++--------------- 1 file changed, 16 insertions(+), 54 deletions(-) diff --git a/erpnext/selling/doctype/customer/customer.py b/erpnext/selling/doctype/customer/customer.py index c86e18ab7aa..fc9bcc9b934 100644 --- a/erpnext/selling/doctype/customer/customer.py +++ b/erpnext/selling/doctype/customer/customer.py @@ -142,7 +142,7 @@ class Customer(TransactionBase): self.update_lead_status() if self.flags.is_new_doc: - self.create_lead_address_contact() + self.link_lead_address_contact() self.update_customer_groups() @@ -176,63 +176,25 @@ class Customer(TransactionBase): if self.lead_name: frappe.db.set_value("Lead", self.lead_name, "status", "Converted") - def create_lead_address_contact(self): + def link_lead_address_contact(self): if self.lead_name: # assign lead address to customer (if already not set) - address_names = frappe.get_all('Dynamic Link', filters={ - "parenttype":"Address", - "link_doctype":"Lead", - "link_name":self.lead_name - }, fields=["parent as name"]) + linked_contacts_and_addresses = frappe.get_all('Dynamic Link', filters=[ + ["parenttype", "in", ["Contact", "Address"]], + ["link_doctype", "=", "Lead"], + ["link_name", "=", self.lead_name] + ], fields=[ + "parent as name", + "parenttype as doctype" + ] + ) - for address_name in address_names: - address = frappe.get_doc('Address', address_name.get('name')) - if not address.has_link('Customer', self.name): - address.append('links', dict(link_doctype='Customer', link_name=self.name)) - address.save(ignore_permissions=self.flags.ignore_permissions) + for row in linked_contacts_and_addresses: + linked_doc = frappe.get_doc(row.doctype, row.name) + if not linked_doc.has_link('Customer', self.name): + linked_doc.append('links', dict(link_doctype='Customer', link_name=self.name)) + linked_doc.save(ignore_permissions=self.flags.ignore_permissions) - lead = frappe.db.get_value("Lead", self.lead_name, ["organization_lead", "lead_name", "email_id", "phone", "mobile_no", "gender", "salutation"], as_dict=True) - - if not lead.lead_name: - frappe.throw(_("Please mention the Lead Name in Lead {0}").format(self.lead_name)) - - if lead.organization_lead: - contact_names = frappe.get_all('Dynamic Link', filters={ - "parenttype":"Contact", - "link_doctype":"Lead", - "link_name":self.lead_name - }, fields=["parent as name"]) - - for contact_name in contact_names: - contact = frappe.get_doc('Contact', contact_name.get('name')) - if not contact.has_link('Customer', self.name): - contact.append('links', dict(link_doctype='Customer', link_name=self.name)) - contact.save(ignore_permissions=self.flags.ignore_permissions) - - else: - lead.lead_name = lead.lead_name.lstrip().split(" ") - lead.first_name = lead.lead_name[0] - lead.last_name = " ".join(lead.lead_name[1:]) - - # create contact from lead - contact = frappe.new_doc('Contact') - contact.first_name = lead.first_name - contact.last_name = lead.last_name - contact.gender = lead.gender - contact.salutation = lead.salutation - contact.email_id = lead.email_id - contact.phone = lead.phone - contact.mobile_no = lead.mobile_no - contact.is_primary_contact = 1 - contact.append('links', dict(link_doctype='Customer', link_name=self.name)) - if lead.email_id: - contact.append('email_ids', dict(email_id=lead.email_id, is_primary=1)) - if lead.mobile_no: - contact.append('phone_nos', dict(phone=lead.mobile_no, is_primary_mobile_no=1)) - contact.flags.ignore_permissions = self.flags.ignore_permissions - contact.autoname() - if not frappe.db.exists("Contact", contact.name): - contact.insert() def validate_name_with_customer_group(self): if frappe.db.exists("Customer Group", self.name): From 79ce36c19f3369754f44baab935081c343ab0ef1 Mon Sep 17 00:00:00 2001 From: Anupam Date: Tue, 18 Jan 2022 16:36:40 +0530 Subject: [PATCH 11/42] fix: sider issue --- erpnext/selling/doctype/customer/customer.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/erpnext/selling/doctype/customer/customer.py b/erpnext/selling/doctype/customer/customer.py index fc9bcc9b934..df3d9e862e5 100644 --- a/erpnext/selling/doctype/customer/customer.py +++ b/erpnext/selling/doctype/customer/customer.py @@ -179,15 +179,15 @@ class Customer(TransactionBase): def link_lead_address_contact(self): if self.lead_name: # assign lead address to customer (if already not set) - linked_contacts_and_addresses = frappe.get_all('Dynamic Link', filters=[ - ["parenttype", "in", ["Contact", "Address"]], - ["link_doctype", "=", "Lead"], - ["link_name", "=", self.lead_name] - ], fields=[ - "parent as name", - "parenttype as doctype" - ] - ) + linked_contacts_and_addresses = frappe.get_all( + "Dynamic Link", + filters=[ + ["parenttype", "in", ["Contact", "Address"]], + ["link_doctype", "=", "Lead"], + ["link_name", "=", self.lead_name], + ], + fields=["parent as name", "parenttype as doctype"], + ) for row in linked_contacts_and_addresses: linked_doc = frappe.get_doc(row.doctype, row.name) From 44d209853054930301ae7c03c1d470c4710cecef Mon Sep 17 00:00:00 2001 From: Anupam Date: Wed, 19 Jan 2022 13:10:23 +0530 Subject: [PATCH 12/42] fix: changed function name --- erpnext/selling/doctype/customer/customer.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/selling/doctype/customer/customer.py b/erpnext/selling/doctype/customer/customer.py index df3d9e862e5..df871491422 100644 --- a/erpnext/selling/doctype/customer/customer.py +++ b/erpnext/selling/doctype/customer/customer.py @@ -142,7 +142,7 @@ class Customer(TransactionBase): self.update_lead_status() if self.flags.is_new_doc: - self.link_lead_address_contact() + self.link_lead_address_and_contact() self.update_customer_groups() @@ -176,9 +176,9 @@ class Customer(TransactionBase): if self.lead_name: frappe.db.set_value("Lead", self.lead_name, "status", "Converted") - def link_lead_address_contact(self): + def link_lead_address_and_contact(self): if self.lead_name: - # assign lead address to customer (if already not set) + # assign lead address and contact to customer (if already not set) linked_contacts_and_addresses = frappe.get_all( "Dynamic Link", filters=[ From 25398d017bca53f14c7ebb6c9f937ef7c1390093 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Thu, 20 Jan 2022 10:51:59 +0530 Subject: [PATCH 13/42] fix: Cleanup empty rows on bank statement import --- .../bank_statement_import.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.py b/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.py index c57e862892c..57434bdd829 100644 --- a/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.py +++ b/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.py @@ -17,6 +17,7 @@ from openpyxl.styles import Font from openpyxl.utils import get_column_letter from six import string_types +INVALID_VALUES = ("", None) class BankStatementImport(DataImport): def __init__(self, *args, **kwargs): @@ -96,6 +97,18 @@ def download_errored_template(data_import_name): data_import = frappe.get_doc("Bank Statement Import", data_import_name) data_import.export_errored_rows() +def parse_data_from_template(raw_data): + data = [] + + for i, row in enumerate(raw_data): + if all(v in INVALID_VALUES for v in row): + # empty row + continue + + data.append(row) + + return data + def start_import(data_import, bank_account, import_file_path, google_sheets_url, bank, template_options): """This method runs in background job""" @@ -105,7 +118,8 @@ def start_import(data_import, bank_account, import_file_path, google_sheets_url, file = import_file_path if import_file_path else google_sheets_url import_file = ImportFile("Bank Transaction", file = file, import_type="Insert New Records") - data = import_file.raw_data + + data = parse_data_from_template(import_file.raw_data) if import_file_path: add_bank_account(data, bank_account) From 77cb7ebd4085fc4a1acf3b6b0022ca1ba45b376e Mon Sep 17 00:00:00 2001 From: marination Date: Thu, 20 Jan 2022 13:36:46 +0530 Subject: [PATCH 14/42] fix: (enhance) BOM Operations Report - Added filters in the Report for BOM ID, Item Code and Workstation - Converted Raw SQL to frappe.qb and added method to get filtered data - Changed fieldtype of 'Time in mins' from Int to Float - Get BOM wise grouped data to keep order and accurate grouping in report (cherry picked from commit a9ff1fc52e1be73fa0d4cbb415607d5d545c545c) --- .../bom_operations_time.js | 35 ++++++++- .../bom_operations_time.py | 78 +++++++++++++------ 2 files changed, 89 insertions(+), 24 deletions(-) diff --git a/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.js b/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.js index 7468e34020c..705da5a3a69 100644 --- a/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.js +++ b/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.js @@ -4,6 +4,39 @@ frappe.query_reports["BOM Operations Time"] = { "filters": [ - + { + "fieldname": "item_code", + "label": __("Item Code"), + "fieldtype": "Link", + "width": "100", + "options": "Item", + "get_query": () =>{ + return { + filters: { "disabled": 0, "is_stock_item": 1 } + } + } + }, + { + "fieldname": "bom_id", + "label": __("BOM ID"), + "fieldtype": "MultiSelectList", + "width": "80", + "options": "BOM", + "get_data": function(txt) { + return frappe.db.get_link_options("BOM", txt); + }, + "get_query": () =>{ + return { + filters: { "docstatus": 1, "is_active": 1 } + } + } + }, + { + "fieldname": "workstation", + "label": __("Workstation"), + "fieldtype": "Link", + "width": "100", + "options": "Workstation" + }, ] }; diff --git a/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.py b/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.py index e7a818abd5d..0bed46ac70b 100644 --- a/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.py +++ b/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.py @@ -4,7 +4,8 @@ import frappe from frappe import _ - +from frappe.model.meta import get_field_precision +from frappe.utils import flt def execute(filters=None): data = get_data(filters) @@ -12,19 +13,15 @@ def execute(filters=None): return columns, data def get_data(filters): - data = [] + bom_wise_data = {} + bom_data, report_data = [], [] - bom_data = [] - for d in frappe.db.sql(""" - SELECT - bom.name, bom.item, bom.item_name, bom.uom, - bomps.operation, bomps.workstation, bomps.time_in_mins - FROM `tabBOM` bom, `tabBOM Operation` bomps - WHERE - bom.docstatus = 1 and bom.is_active = 1 and bom.name = bomps.parent - """, as_dict=1): + bom_operation_data = get_filtered_data(filters) + + for d in bom_operation_data: row = get_args() if d.name not in bom_data: + bom_wise_data[d.name] = [] bom_data.append(d.name) row.update(d) else: @@ -34,14 +31,49 @@ def get_data(filters): "time_in_mins": d.time_in_mins }) - data.append(row) + # maintain BOM wise data for grouping such as: + # {"BOM A": [{Row1}, {Row2}], "BOM B": ...} + bom_wise_data[d.name].append(row) used_as_subassembly_items = get_bom_count(bom_data) - for d in data: - d.used_as_subassembly_items = used_as_subassembly_items.get(d.name, 0) + for d in bom_wise_data: + for row in bom_wise_data[d]: + row.used_as_subassembly_items = used_as_subassembly_items.get(row.name, 0) + report_data.append(row) - return data + return report_data + +def get_filtered_data(filters): + bom = frappe.qb.DocType("BOM") + bom_ops = frappe.qb.DocType("BOM Operation") + + bom_ops_query = ( + frappe.qb.from_(bom) + .join(bom_ops).on(bom.name == bom_ops.parent) + .select( + bom.name, bom.item, bom.item_name, bom.uom, + bom_ops.operation, bom_ops.workstation, bom_ops.time_in_mins + ).where( + (bom.docstatus == 1) + & (bom.is_active == 1) + ) + ) + + if filters.get("item_code"): + bom_ops_query = bom_ops_query.where(bom.item == filters.get("item_code")) + + if filters.get("bom_id"): + bom_ops_query = bom_ops_query.where(bom.name.isin(filters.get("bom_id"))) + + if filters.get("workstation"): + bom_ops_query = bom_ops_query.where( + bom_ops.workstation == filters.get("workstation") + ) + + bom_operation_data = bom_ops_query.run(as_dict=True) + + return bom_operation_data def get_bom_count(bom_data): data = frappe.get_all("BOM Item", @@ -68,13 +100,13 @@ def get_columns(filters): "options": "BOM", "fieldname": "name", "fieldtype": "Link", - "width": 140 + "width": 220 }, { - "label": _("BOM Item Code"), + "label": _("Item Code"), "options": "Item", "fieldname": "item", "fieldtype": "Link", - "width": 140 + "width": 150 }, { "label": _("Item Name"), "fieldname": "item_name", @@ -85,13 +117,13 @@ def get_columns(filters): "options": "UOM", "fieldname": "uom", "fieldtype": "Link", - "width": 140 + "width": 100 }, { "label": _("Operation"), "options": "Operation", "fieldname": "operation", "fieldtype": "Link", - "width": 120 + "width": 140 }, { "label": _("Workstation"), "options": "Workstation", @@ -101,11 +133,11 @@ def get_columns(filters): }, { "label": _("Time (In Mins)"), "fieldname": "time_in_mins", - "fieldtype": "Int", - "width": 140 + "fieldtype": "Float", + "width": 120 }, { "label": _("Sub-assembly BOM Count"), "fieldname": "used_as_subassembly_items", "fieldtype": "Int", - "width": 180 + "width": 200 }] From e963d0e8e0e62b49ecb4afc228afe6ffee958c59 Mon Sep 17 00:00:00 2001 From: marination Date: Thu, 20 Jan 2022 14:19:08 +0530 Subject: [PATCH 15/42] fix: Sider (unused imports) and Linter(add extra empty line) (cherry picked from commit 3c7b7721123bb2284a00f84f335a8ab1708831f5) --- .../report/bom_operations_time/bom_operations_time.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.py b/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.py index 0bed46ac70b..eda9eb9d701 100644 --- a/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.py +++ b/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.py @@ -4,8 +4,7 @@ import frappe from frappe import _ -from frappe.model.meta import get_field_precision -from frappe.utils import flt + def execute(filters=None): data = get_data(filters) From 655d8980dc60a75f59019f475581c8043de63053 Mon Sep 17 00:00:00 2001 From: marination Date: Thu, 20 Jan 2022 14:29:18 +0530 Subject: [PATCH 16/42] fix: Allow only BOM with ops in BOM ID filter & add total row to report (cherry picked from commit af734298c8ac541188de4593a064f962147ea27d) --- .../report/bom_operations_time/bom_operations_time.js | 4 ++-- .../report/bom_operations_time/bom_operations_time.json | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.js b/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.js index 705da5a3a69..0eb22a22f73 100644 --- a/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.js +++ b/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.js @@ -20,14 +20,14 @@ frappe.query_reports["BOM Operations Time"] = { "fieldname": "bom_id", "label": __("BOM ID"), "fieldtype": "MultiSelectList", - "width": "80", + "width": "100", "options": "BOM", "get_data": function(txt) { return frappe.db.get_link_options("BOM", txt); }, "get_query": () =>{ return { - filters: { "docstatus": 1, "is_active": 1 } + filters: { "docstatus": 1, "is_active": 1, "with_operations": 1 } } } }, diff --git a/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.json b/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.json index 665c5b9f79e..8162017ca81 100644 --- a/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.json +++ b/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.json @@ -1,14 +1,16 @@ { - "add_total_row": 0, + "add_total_row": 1, + "columns": [], "creation": "2020-03-03 01:41:20.862521", "disable_prepared_report": 0, "disabled": 0, "docstatus": 0, "doctype": "Report", + "filters": [], "idx": 0, "is_standard": "Yes", "letter_head": "", - "modified": "2020-03-03 01:41:20.862521", + "modified": "2022-01-20 14:21:47.771591", "modified_by": "Administrator", "module": "Manufacturing", "name": "BOM Operations Time", From b9a42807f8e3542e908ea53e451836cf9e3098df Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 20 Jan 2022 15:06:11 +0530 Subject: [PATCH 17/42] fix: show stock UOM for material transfers (#29376) (#29377) (cherry picked from commit 7e407bcc42260e78ed7275946f71e3e7a89b282f) Co-authored-by: Ankush Menat --- .../manufacturing/doctype/production_plan/production_plan.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py index e903e9baf84..106777b82ef 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -949,6 +949,7 @@ def get_materials_from_other_locations(item, warehouses, new_mr_items, company): warehouses, item.get("quantity"), company, ignore_validation=True) required_qty = item.get("quantity") + # get available material by transferring to production warehouse for d in locations: if required_qty <=0: return @@ -959,12 +960,14 @@ def get_materials_from_other_locations(item, warehouses, new_mr_items, company): new_dict.update({ "quantity": quantity, "material_request_type": "Material Transfer", + "uom": new_dict.get("stock_uom"), # internal transfer should be in stock UOM "from_warehouse": d.get("warehouse") }) required_qty -= quantity new_mr_items.append(new_dict) + # raise purchase request for remaining qty if required_qty: stock_uom, purchase_uom = frappe.db.get_value( 'Item', From d190ea4ad3b2ed378222894d169fa49f66ecddc8 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Thu, 20 Jan 2022 14:56:53 +0530 Subject: [PATCH 18/42] ci: disable coverage on v13 branch --- .github/workflows/server-tests.yml | 33 +----------------------------- 1 file changed, 1 insertion(+), 32 deletions(-) diff --git a/.github/workflows/server-tests.yml b/.github/workflows/server-tests.yml index 6d7324d623b..62576996a0f 100644 --- a/.github/workflows/server-tests.yml +++ b/.github/workflows/server-tests.yml @@ -89,39 +89,8 @@ jobs: run: bash ${GITHUB_WORKSPACE}/.github/helper/install.sh - name: Run Tests - run: cd ~/frappe-bench/ && bench --site test_site run-parallel-tests --app erpnext --use-orchestrator --with-coverage + run: cd ~/frappe-bench/ && bench --site test_site run-parallel-tests --app erpnext --use-orchestrator env: TYPE: server CI_BUILD_ID: ${{ github.run_id }} ORCHESTRATOR_URL: http://test-orchestrator.frappe.io - - - name: Upload Coverage Data - run: | - cp ~/frappe-bench/sites/.coverage ${GITHUB_WORKSPACE} - cd ${GITHUB_WORKSPACE} - pip3 install coverage==5.5 - pip3 install coveralls==3.0.1 - coveralls - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - COVERALLS_FLAG_NAME: run-${{ matrix.container }} - COVERALLS_SERVICE_NAME: ${{ github.event_name == 'pull_request' && 'github' || 'github-actions' }} - COVERALLS_PARALLEL: true - - coveralls: - name: Coverage Wrap Up - needs: test - container: python:3-slim - runs-on: ubuntu-18.04 - steps: - - name: Clone - uses: actions/checkout@v2 - - - name: Coveralls Finished - run: | - cd ${GITHUB_WORKSPACE} - pip3 install coverage==5.5 - pip3 install coveralls==3.0.1 - coveralls --finish - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From 8a9a13cb96be74242b01112ea34f0a568b76e3d6 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Thu, 20 Jan 2022 15:03:22 +0530 Subject: [PATCH 19/42] ci: concurrency control on v13 branches --- .github/workflows/patch.yml | 4 ++++ .github/workflows/server-tests.yml | 4 ++++ .github/workflows/ui-tests.yml | 4 ++++ 3 files changed, 12 insertions(+) diff --git a/.github/workflows/patch.yml b/.github/workflows/patch.yml index 8bb44555206..b4421203a15 100644 --- a/.github/workflows/patch.yml +++ b/.github/workflows/patch.yml @@ -8,6 +8,10 @@ on: workflow_dispatch: +concurrency: + group: patch-mariadb-v13-${{ github.event.number }} + cancel-in-progress: true + jobs: test: runs-on: ubuntu-18.04 diff --git a/.github/workflows/server-tests.yml b/.github/workflows/server-tests.yml index 62576996a0f..c62622eecec 100644 --- a/.github/workflows/server-tests.yml +++ b/.github/workflows/server-tests.yml @@ -12,6 +12,10 @@ on: - '**.js' - '**.md' +concurrency: + group: server-mariadb-v13-${{ github.event.number }} + cancel-in-progress: true + jobs: test: runs-on: ubuntu-18.04 diff --git a/.github/workflows/ui-tests.yml b/.github/workflows/ui-tests.yml index 5459e86123d..9f142bd2c2f 100644 --- a/.github/workflows/ui-tests.yml +++ b/.github/workflows/ui-tests.yml @@ -6,6 +6,10 @@ on: - '**.md' workflow_dispatch: +concurrency: + group: ui-v13-${{ github.event.number }} + cancel-in-progress: true + jobs: test: runs-on: ubuntu-18.04 From d32f229483cf4681a0873bfc9a4c5d97abecffca Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Thu, 20 Jan 2022 15:16:48 +0530 Subject: [PATCH 20/42] ci: fail on merge conflict label --- .github/workflows/patch.yml | 6 ++++++ .github/workflows/server-tests.yml | 7 +++++++ 2 files changed, 13 insertions(+) diff --git a/.github/workflows/patch.yml b/.github/workflows/patch.yml index b4421203a15..8d29057b487 100644 --- a/.github/workflows/patch.yml +++ b/.github/workflows/patch.yml @@ -5,6 +5,7 @@ on: paths-ignore: - '**.js' - '**.md' + types: [opened, unlabeled, synchronize, reopened] workflow_dispatch: @@ -29,6 +30,11 @@ jobs: options: --health-cmd="mysqladmin ping" --health-interval=5s --health-timeout=2s --health-retries=3 steps: + - name: Check for merge conficts label + if: ${{ contains(github.event.pull_request.labels.*.name, 'conflicts') }} + run: | + echo "Remove merge conflicts and remove conflict label to run CI" + exit 1 - name: Clone uses: actions/checkout@v2 diff --git a/.github/workflows/server-tests.yml b/.github/workflows/server-tests.yml index c62622eecec..1c9743c5700 100644 --- a/.github/workflows/server-tests.yml +++ b/.github/workflows/server-tests.yml @@ -5,6 +5,7 @@ on: paths-ignore: - '**.js' - '**.md' + types: [opened, unlabeled, synchronize, reopened] workflow_dispatch: push: branches: [ develop ] @@ -39,6 +40,12 @@ jobs: options: --health-cmd="mysqladmin ping" --health-interval=5s --health-timeout=2s --health-retries=3 steps: + - name: Check for merge conficts label + if: ${{ contains(github.event.pull_request.labels.*.name, 'conflicts') }} + run: | + echo "Remove merge conflicts and remove conflict label to run CI" + exit 1 + - name: Clone uses: actions/checkout@v2 From 7d130af12b95483c9046365eafcfcc21c60c397e Mon Sep 17 00:00:00 2001 From: marination Date: Thu, 20 Jan 2022 19:34:36 +0530 Subject: [PATCH 21/42] fix: Cart Logic of Item variant without Website Item --- erpnext/e_commerce/shopping_cart/cart.py | 21 ++++++++++++-- erpnext/e_commerce/variant_selector/utils.py | 29 +++++++++++++++---- .../generators/item/item_configure.js | 2 +- 3 files changed, 42 insertions(+), 10 deletions(-) diff --git a/erpnext/e_commerce/shopping_cart/cart.py b/erpnext/e_commerce/shopping_cart/cart.py index 03aa36fa883..3681c5baf74 100644 --- a/erpnext/e_commerce/shopping_cart/cart.py +++ b/erpnext/e_commerce/shopping_cart/cart.py @@ -42,7 +42,7 @@ def get_cart_quotation(doc=None): if not doc.customer_address and addresses: update_cart_address("billing", addresses[0].name) - + print("doc>>", doc, type(doc)) return { "doc": decorate_quotation_doc(doc), "shipping_addresses": get_shipping_addresses(party), @@ -276,10 +276,25 @@ def guess_territory(): def decorate_quotation_doc(doc): for d in doc.get("items", []): + item_code = d.item_code + fields = ["web_item_name", "thumbnail", "website_image", "description", "route"] + + # Variant Item + if not frappe.db.exists("Website Item", {"item_code": item_code}): + variant_data = frappe.db.get_values( + "Item", + filters={"item_code": item_code}, + fieldname=["variant_of", "item_name"], + as_dict=True + )[0] + item_code = variant_data.variant_of + d.website_item_name = variant_data.item_name + fields = fields[1:] + d.update(frappe.db.get_value( "Website Item", - {"item_code": d.item_code}, - ["web_item_name", "thumbnail", "website_image", "description", "route"], + {"item_code": item_code}, + fields, as_dict=True) ) diff --git a/erpnext/e_commerce/variant_selector/utils.py b/erpnext/e_commerce/variant_selector/utils.py index 61df3adca58..bccc57b7320 100644 --- a/erpnext/e_commerce/variant_selector/utils.py +++ b/erpnext/e_commerce/variant_selector/utils.py @@ -1,7 +1,9 @@ import frappe from frappe.utils import cint +from erpnext.e_commerce.doctype.e_commerce_settings.e_commerce_settings import get_shopping_cart_settings from erpnext.e_commerce.variant_selector.item_variants_cache import ItemVariantsCacheManager +from erpnext.utilities.product import get_price def get_item_codes_by_attributes(attribute_filters, template_item_code=None): @@ -143,14 +145,13 @@ def get_next_attribute_and_values(item_code, selected_attributes): filtered_items_count = len(filtered_items) # get product info if exact match - from erpnext.e_commerce.shopping_cart.product_info import get_product_info_for_website + # from erpnext.e_commerce.shopping_cart.product_info import get_product_info_for_website if exact_match: - data = get_product_info_for_website(exact_match[0]) - product_info = data.product_info + cart_settings = get_shopping_cart_settings() + product_info = get_item_variant_price_dict(exact_match[0], cart_settings) + if product_info: - product_info["allow_items_not_in_stock"] = cint(data.cart_settings.allow_items_not_in_stock) - if not data.cart_settings.show_price: - product_info = None + product_info["allow_items_not_in_stock"] = cint(cart_settings.allow_items_not_in_stock) else: product_info = None @@ -195,3 +196,19 @@ def get_item_attributes(item_code): return attributes +def get_item_variant_price_dict(item_code, cart_settings): + if cart_settings.enabled and cart_settings.show_price: + is_guest = frappe.session.user == "Guest" + # Show Price if logged in. + # If not logged in, check if price is hidden for guest. + if not is_guest or not cart_settings.hide_price_for_guest: + price = get_price( + item_code, + cart_settings.price_list, #TODO + cart_settings.default_customer_group, + cart_settings.company + ) + return {"price": price} + + return None + diff --git a/erpnext/templates/generators/item/item_configure.js b/erpnext/templates/generators/item/item_configure.js index b5f92989ef4..231ae0587ed 100644 --- a/erpnext/templates/generators/item/item_configure.js +++ b/erpnext/templates/generators/item/item_configure.js @@ -214,7 +214,7 @@ class ItemConfigure { ? `