From db3d2c31a3a6809a2a1e9faba331223c63959cc5 Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Fri, 19 Nov 2021 05:27:06 +0530 Subject: [PATCH 001/102] 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 002/102] 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 003/102] 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 004/102] 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 005/102] 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 006/102] 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 007/102] 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 008/102] 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 009/102] 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 835448c9e5d60752c232fe53057396dc3e16165c Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 10 Jan 2022 12:50:34 +0530 Subject: [PATCH 010/102] fix(India): NIL Rated, Exempted and non gst invoices in GSTR-1 report (cherry picked from commit 7d85755595149fb27638126f5d05eb79734fc548) --- erpnext/regional/report/gstr_1/gstr_1.js | 3 +- erpnext/regional/report/gstr_1/gstr_1.py | 102 ++++++++++++++++++++++- 2 files changed, 102 insertions(+), 3 deletions(-) diff --git a/erpnext/regional/report/gstr_1/gstr_1.js b/erpnext/regional/report/gstr_1/gstr_1.js index ef2bdb67980..4b98978f130 100644 --- a/erpnext/regional/report/gstr_1/gstr_1.js +++ b/erpnext/regional/report/gstr_1/gstr_1.js @@ -53,7 +53,8 @@ frappe.query_reports["GSTR-1"] = { { "value": "CDNR-REG", "label": __("Credit/Debit Notes (Registered) - 9B") }, { "value": "CDNR-UNREG", "label": __("Credit/Debit Notes (Unregistered) - 9B") }, { "value": "EXPORT", "label": __("Export Invoice - 6A") }, - { "value": "Advances", "label": __("Tax Liability (Advances Received) - 11A(1), 11A(2)") } + { "value": "Advances", "label": __("Tax Liability (Advances Received) - 11A(1), 11A(2)") }, + { "value": "NIL Rated", "label": __("NIL RATED/EXEMPTED Invoices") } ], "default": "B2B" } diff --git a/erpnext/regional/report/gstr_1/gstr_1.py b/erpnext/regional/report/gstr_1/gstr_1.py index b095c7293dd..dd1c892fdd8 100644 --- a/erpnext/regional/report/gstr_1/gstr_1.py +++ b/erpnext/regional/report/gstr_1/gstr_1.py @@ -42,6 +42,7 @@ class Gstr1Report(object): shipping_bill_number, shipping_bill_date, reason_for_issuing_document + company_gstin """ def run(self): @@ -63,6 +64,8 @@ class Gstr1Report(object): self.get_b2c_data() elif self.filters.get("type_of_business") == "Advances": self.get_advance_data() + elif self.filters.get("type_of_business") == "NIL Rated": + self.get_nil_rated_invoices() elif self.invoices: for inv, items_based_on_rate in self.items_based_on_tax_rate.items(): invoice_details = self.invoices.get(inv) @@ -92,6 +95,57 @@ class Gstr1Report(object): row= [key[0], key[1], value[0], value[1]] self.data.append(row) + def get_nil_rated_invoices(self): + nil_exempt_output = [ + { + "description": "Inter-State supplies to registered persons", + "nil_rate": 0.0, + "exempted": 0.0, + "non_gst": 0.0 + }, + { + "description": "Intra-State supplies to registered persons", + "nil_rate": 0.0, + "exempted": 0.0, + "non_gst": 0.0 + }, + { + "description": "Inter-State supplies to unregistered persons", + "nil_rate": 0.0, + "exempted": 0.0, + "non_gst": 0.0 + }, + { + "description": "Intra-State supplies to registered persons", + "nil_rate": 0.0, + "exempted": 0.0, + "non_gst": 0.0 + } + ] + + for invoice, details in self.nil_exempt_non_gst.items(): + invoice_detail = self.invoice.get(invoice) + if invoice_detail.get('gst_category') in ("Registered Regular", "Deemed Export", "SEZ"): + if is_inter_state(invoice_detail): + nil_exempt_output[0]["nil_rated"] += details[0] + nil_exempt_output[0]["exempted"] += details[1] + nil_exempt_output[0]["non_gst"] += details[2] + else: + nil_exempt_output[1]["nil_rated"] += details[0] + nil_exempt_output[1]["exempted"] += details[1] + nil_exempt_output[1]["non_gst"] += details[2] + else: + if is_inter_state(invoice_detail): + nil_exempt_output[2]["nil_rated"] += details[0] + nil_exempt_output[2]["exempted"] += details[1] + nil_exempt_output[2]["non_gst"] += details[2] + else: + nil_exempt_output[3]["nil_rated"] += details[0] + nil_exempt_output[3]["exempted"] += details[1] + nil_exempt_output[3]["non_gst"] += details[2] + + self.data = nil_exempt_output + def get_b2c_data(self): b2cs_output = {} @@ -241,10 +295,11 @@ class Gstr1Report(object): def get_invoice_items(self): self.invoice_items = frappe._dict() self.item_tax_rate = frappe._dict() + self.nil_exempt_non_gst = {} items = frappe.db.sql(""" - select item_code, parent, taxable_value, base_net_amount, item_tax_rate - from `tab%s Item` + select item_code, parent, taxable_value, base_net_amount, item_tax_rate, is_nil_exempt, + is_non_gst from `tab%s Item` where parent in (%s) """ % (self.doctype, ', '.join(['%s']*len(self.invoices))), tuple(self.invoices), as_dict=1) @@ -261,6 +316,16 @@ class Gstr1Report(object): tax_rate_dict = self.item_tax_rate.setdefault(d.parent, {}).setdefault(d.item_code, []) tax_rate_dict.append(rate) + if d.is_nil_exempt: + self.nil_exempt_non_gst.setdefault(d.parent, [0.0, 0.0, 0.0]) + if item_tax_rate: + self.nil_exempt_non_gst[d.parent][0] += d.get('taxable_value', 0) + else: + self.nil_exempt_non_gst[d.parent][1] += d.get('taxable_value', 0) + elif d.is_non_gst: + self.nil_exempt_non_gst.setdefault(d.parent, [0.0, 0.0, 0.0]) + self.nil_exempt_non_gst[d.parent][2] += d.get('taxable_value', 0) + def get_items_based_on_tax_rate(self): self.tax_details = frappe.db.sql(""" select @@ -706,6 +771,33 @@ class Gstr1Report(object): "width": 100 } ] + elif self.filters.get("type_of_business") == "NIL Rated": + self.invoice_columns = [ + { + "fieldname": "descripton", + "label": "Description", + "fieldtype": "Data", + "width": 120 + }, + { + "fieldname": "nil_rated", + "label": "Nil Rated", + "fieldtype": "Currency", + "width": 200 + }, + { + "fieldname": "exempted", + "label": "Exempted", + "fieldtype": "Currency", + "width": 200 + }, + { + "fieldname": "non_gst", + "label": "Non GST", + "fieldtype": "Currency", + "width": 200 + } + ] self.columns = self.invoice_columns + self.tax_columns + self.other_columns @@ -1065,3 +1157,9 @@ def download_json_file(): frappe.response['filecontent'] = data['data'] frappe.response['content_type'] = 'application/json' frappe.response['type'] = 'download' + +def is_inter_state(invoice_detail): + if invoice_detail.place_of_supply.split("-")[0] != invoice_detail.company_gstin[:2]: + return True + else: + return False \ No newline at end of file From 103bf197009e7993b74c8a64d2f74476a86de069 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Thu, 13 Jan 2022 22:39:57 +0530 Subject: [PATCH 011/102] fix: JSON for nil/exempt and non gst (cherry picked from commit 55c445cd375a5a16e69205fc702ff86a4577ce48) --- erpnext/regional/report/gstr_1/gstr_1.py | 84 +++++++++++++++++------- 1 file changed, 61 insertions(+), 23 deletions(-) diff --git a/erpnext/regional/report/gstr_1/gstr_1.py b/erpnext/regional/report/gstr_1/gstr_1.py index dd1c892fdd8..17b11073346 100644 --- a/erpnext/regional/report/gstr_1/gstr_1.py +++ b/erpnext/regional/report/gstr_1/gstr_1.py @@ -41,7 +41,7 @@ class Gstr1Report(object): port_code, shipping_bill_number, shipping_bill_date, - reason_for_issuing_document + reason_for_issuing_document, company_gstin """ @@ -99,32 +99,32 @@ class Gstr1Report(object): nil_exempt_output = [ { "description": "Inter-State supplies to registered persons", - "nil_rate": 0.0, + "nil_rated": 0.0, "exempted": 0.0, "non_gst": 0.0 }, { "description": "Intra-State supplies to registered persons", - "nil_rate": 0.0, + "nil_rated": 0.0, "exempted": 0.0, "non_gst": 0.0 }, { "description": "Inter-State supplies to unregistered persons", - "nil_rate": 0.0, + "nil_rated": 0.0, "exempted": 0.0, "non_gst": 0.0 }, { - "description": "Intra-State supplies to registered persons", - "nil_rate": 0.0, + "description": "Intra-State supplies to unregistered persons", + "nil_rated": 0.0, "exempted": 0.0, "non_gst": 0.0 } ] for invoice, details in self.nil_exempt_non_gst.items(): - invoice_detail = self.invoice.get(invoice) + invoice_detail = self.invoices.get(invoice) if invoice_detail.get('gst_category') in ("Registered Regular", "Deemed Export", "SEZ"): if is_inter_state(invoice_detail): nil_exempt_output[0]["nil_rated"] += details[0] @@ -388,21 +388,24 @@ class Gstr1Report(object): self.items_based_on_tax_rate.setdefault(invoice, {}).setdefault(0, items.keys()) def get_columns(self): - self.tax_columns = [ - { - "fieldname": "rate", - "label": "Rate", - "fieldtype": "Int", - "width": 60 - }, - { - "fieldname": "taxable_value", - "label": "Taxable Value", - "fieldtype": "Currency", - "width": 100 - } - ] self.other_columns = [] + self.tax_columns = [] + + if self.filters.get("type_of_business") != "NIL Rated": + self.tax_columns = [ + { + "fieldname": "rate", + "label": "Rate", + "fieldtype": "Int", + "width": 60 + }, + { + "fieldname": "taxable_value", + "label": "Taxable Value", + "fieldtype": "Currency", + "width": 100 + } + ] if self.filters.get("type_of_business") == "B2B": self.invoice_columns = [ @@ -774,10 +777,10 @@ class Gstr1Report(object): elif self.filters.get("type_of_business") == "NIL Rated": self.invoice_columns = [ { - "fieldname": "descripton", + "fieldname": "description", "label": "Description", "fieldtype": "Data", - "width": 120 + "width": 420 }, { "fieldname": "nil_rated", @@ -861,6 +864,11 @@ def get_json(filters, report_name, data): out = get_advances_json(res, gstin) gst_json["at"] = out + elif filters["type_of_business"] == "NIL Rated": + res = report_data[:-1] + out = get_exempted_json(res) + gst_json["nil"] = out + return { 'report_name': report_name, 'report_type': filters['type_of_business'], @@ -1073,6 +1081,36 @@ def get_cdnr_unreg_json(res, gstin): return out +def get_exempted_json(data): + out = { + "inv": [ + { + "sply_ty": "INTRB2B" + }, + { + "sply_ty": "INTRAB2B" + }, + { + "sply_ty": "INTRB2C" + }, + { + "sply_ty": "INTRAB2C" + } + ] + } + + for i, v in enumerate(data): + if data[i].get('nil_rated'): + out['inv'][i]['nil_amt'] = data[i]['nil_rated'] + + if data[i].get('exempted'): + out['inv'][i]['expt_amt'] = data[i]['exempted'] + + if data[i].get('non_gst'): + out['inv'][i]['ngsup_amt'] = data[i]['non_gst'] + + return out + def get_invoice_type_for_cdnr(row): if row.get('gst_category') == 'SEZ': if row.get('export_type') == 'WPAY': From eb3d28fcc048ae67267703a235306e304e41a2a9 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 3 Jan 2022 19:40:47 +0530 Subject: [PATCH 012/102] fix: Deferred revenue booking for multi currency invoices via JV (cherry picked from commit 98f294a8ae983fe9bf9b83127fccbf1f1a397ba1) --- erpnext/accounts/deferred_revenue.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/deferred_revenue.py b/erpnext/accounts/deferred_revenue.py index 7e270601fb5..460d0844a11 100644 --- a/erpnext/accounts/deferred_revenue.py +++ b/erpnext/accounts/deferred_revenue.py @@ -259,7 +259,7 @@ def book_deferred_income_or_expense(doc, deferred_process, posting_date=None): start_date, end_date, last_gl_entry = get_booking_dates(doc, item, posting_date=posting_date) if not (start_date and end_date): return - account_currency = get_account_currency(item.expense_account) + account_currency = get_account_currency(item.expense_account or item.income_account) if doc.doctype == "Sales Invoice": against, project = doc.customer, doc.project credit_account, debit_account = item.income_account, item.deferred_revenue_account From a08b10d5cf395dea48f77964dc599142d70ac183 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 4 Jan 2022 14:04:18 +0530 Subject: [PATCH 013/102] fix: Add test case for multicurrency invoice (cherry picked from commit 094158f287240fd0807ada6eb89dc6874420aed8) --- erpnext/accounts/deferred_revenue.py | 4 -- .../doctype/journal_entry/journal_entry.py | 24 ++++--- .../sales_invoice/test_sales_invoice.py | 63 +++++++++++++++++++ 3 files changed, 80 insertions(+), 11 deletions(-) diff --git a/erpnext/accounts/deferred_revenue.py b/erpnext/accounts/deferred_revenue.py index 460d0844a11..af6dc577a18 100644 --- a/erpnext/accounts/deferred_revenue.py +++ b/erpnext/accounts/deferred_revenue.py @@ -407,8 +407,6 @@ def book_revenue_via_journal_entry(doc, credit_account, debit_account, against, 'account': credit_account, 'credit': base_amount, 'credit_in_account_currency': amount, - 'party_type': 'Customer' if doc.doctype == 'Sales Invoice' else 'Supplier', - 'party': against, 'account_currency': account_currency, 'reference_name': doc.name, 'reference_type': doc.doctype, @@ -421,8 +419,6 @@ def book_revenue_via_journal_entry(doc, credit_account, debit_account, against, 'account': debit_account, 'debit': base_amount, 'debit_in_account_currency': amount, - 'party_type': 'Customer' if doc.doctype == 'Sales Invoice' else 'Supplier', - 'party': against, 'account_currency': account_currency, 'reference_name': doc.name, 'reference_type': doc.doctype, diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py index 6ec2d4bda03..9c1710217dd 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py @@ -397,13 +397,14 @@ class JournalEntry(AccountsController): debit_or_credit = 'Debit' if d.debit else 'Credit' party_account = get_deferred_booking_accounts(d.reference_type, d.reference_detail_no, debit_or_credit) + against_voucher = ['', against_voucher[1]] else: if d.reference_type == "Sales Invoice": party_account = get_party_account_based_on_invoice_discounting(d.reference_name) or against_voucher[1] else: party_account = against_voucher[1] - if (against_voucher[0] != d.party or party_account != d.account): + if (against_voucher[0] != cstr(d.party) or party_account != d.account): frappe.throw(_("Row {0}: Party / Account does not match with {1} / {2} in {3} {4}") .format(d.idx, field_dict.get(d.reference_type)[0], field_dict.get(d.reference_type)[1], d.reference_type, d.reference_name)) @@ -468,13 +469,22 @@ class JournalEntry(AccountsController): def set_against_account(self): accounts_debited, accounts_credited = [], [] - for d in self.get("accounts"): - if flt(d.debit > 0): accounts_debited.append(d.party or d.account) - if flt(d.credit) > 0: accounts_credited.append(d.party or d.account) + if self.voucher_type in ('Deferred Revenue', 'Deferred Expense'): + for d in self.get('accounts'): + if d.reference_type == 'Sales Invoice': + field = 'customer' + else: + field = 'supplier' - for d in self.get("accounts"): - if flt(d.debit > 0): d.against_account = ", ".join(list(set(accounts_credited))) - if flt(d.credit > 0): d.against_account = ", ".join(list(set(accounts_debited))) + d.against_account = frappe.db.get_value(d.reference_type, d.reference_name, field) + else: + for d in self.get("accounts"): + if flt(d.debit > 0): accounts_debited.append(d.party or d.account) + if flt(d.credit) > 0: accounts_credited.append(d.party or d.account) + + for d in self.get("accounts"): + if flt(d.debit > 0): d.against_account = ", ".join(list(set(accounts_credited))) + if flt(d.credit > 0): d.against_account = ", ".join(list(set(accounts_debited))) def validate_debit_credit_amount(self): for d in self.get('accounts'): diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index e2c1f79bd2e..618acb00aea 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -2455,6 +2455,69 @@ class TestSalesInvoice(unittest.TestCase): frappe.db.set_value('Accounts Settings', None, 'over_billing_allowance', over_billing_allowance) + def test_multi_currency_deferred_revenue_via_journal_entry(self): + deferred_account = create_account(account_name="Deferred Revenue", + parent_account="Current Liabilities - _TC", company="_Test Company") + + acc_settings = frappe.get_doc('Accounts Settings', 'Accounts Settings') + acc_settings.book_deferred_entries_via_journal_entry = 1 + acc_settings.submit_journal_entries = 1 + acc_settings.save() + + item = create_item("_Test Item for Deferred Accounting") + item.enable_deferred_expense = 1 + item.deferred_revenue_account = deferred_account + item.save() + + si = create_sales_invoice(customer='_Test Customer USD', currency='USD', + item=item.name, qty=1, rate=100, conversion_rate=60, do_not_save=True) + + si.set_posting_time = 1 + si.posting_date = '2019-01-01' + si.items[0].enable_deferred_revenue = 1 + si.items[0].service_start_date = "2019-01-01" + si.items[0].service_end_date = "2019-03-30" + si.items[0].deferred_expense_account = deferred_account + si.save() + si.submit() + + pda1 = frappe.get_doc(dict( + doctype='Process Deferred Accounting', + posting_date=nowdate(), + start_date="2019-01-01", + end_date="2019-03-31", + type="Income", + company="_Test Company" + )) + + pda1.insert() + pda1.submit() + + expected_gle = [ + ["Sales - _TC", 0.0, 2089.89, "2019-01-31"], + [deferred_account, 2089.89, 0.0, "2019-01-31"], + ["Sales - _TC", 0.0, 1887.64, "2019-02-28"], + [deferred_account, 1887.64, 0.0, "2019-02-28"], + ["Sales - _TC", 0.0, 2022.47, "2019-03-15"], + [deferred_account, 2022.47, 0.0, "2019-03-15"] + ] + + gl_entries = gl_entries = frappe.db.sql("""select account, debit, credit, posting_date + from `tabGL Entry` + where voucher_type='Journal Entry' and voucher_detail_no=%s and posting_date <= %s + order by posting_date asc, account asc""", (si.items[0].name, si.posting_date), as_dict=1) + + for i, gle in enumerate(gl_entries): + self.assertEqual(expected_gle[i][0], gle.account) + self.assertEqual(expected_gle[i][1], gle.credit) + self.assertEqual(expected_gle[i][2], gle.debit) + self.assertEqual(getdate(expected_gle[i][3]), gle.posting_date) + + acc_settings = frappe.get_doc('Accounts Settings', 'Accounts Settings') + acc_settings.book_deferred_entries_via_journal_entry = 0 + acc_settings.submit_journal_entriessubmit_journal_entries = 0 + acc_settings.save() + def get_sales_invoice_for_e_invoice(): si = make_sales_invoice_for_ewaybill() si.naming_series = 'INV-2020-.#####' From 38a330b55e47cae0ce73d2db364a456e5e9ad76d Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 4 Jan 2022 16:24:24 +0530 Subject: [PATCH 014/102] fix: Test case (cherry picked from commit e311667a250d112552a115a4b551efdc53cf6099) --- erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 618acb00aea..76948652f2b 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -2474,6 +2474,7 @@ class TestSalesInvoice(unittest.TestCase): si.set_posting_time = 1 si.posting_date = '2019-01-01' + si.debit_to = '_Test Receivable USD - _TC' si.items[0].enable_deferred_revenue = 1 si.items[0].service_start_date = "2019-01-01" si.items[0].service_end_date = "2019-03-30" From a4e897d30bf302f6a11c0cd101828501a3ecb1ab Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 7 Jan 2022 19:52:38 +0530 Subject: [PATCH 015/102] fix: Handle frozen books while handling (cherry picked from commit 30a647ff8099105a9fdce02bc98d43969985acb3) --- erpnext/accounts/deferred_revenue.py | 6 ++++++ .../accounts/doctype/sales_invoice/test_sales_invoice.py | 8 ++++++-- erpnext/controllers/accounts_controller.py | 2 -- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/deferred_revenue.py b/erpnext/accounts/deferred_revenue.py index af6dc577a18..4b1535e1000 100644 --- a/erpnext/accounts/deferred_revenue.py +++ b/erpnext/accounts/deferred_revenue.py @@ -255,6 +255,8 @@ def book_deferred_income_or_expense(doc, deferred_process, posting_date=None): enable_check = "enable_deferred_revenue" \ if doc.doctype=="Sales Invoice" else "enable_deferred_expense" + accounts_frozen_upto = frappe.get_cached_value('Accounts Settings', 'None', 'acc_frozen_upto') + def _book_deferred_revenue_or_expense(item, via_journal_entry, submit_journal_entry, book_deferred_entries_based_on): start_date, end_date, last_gl_entry = get_booking_dates(doc, item, posting_date=posting_date) if not (start_date and end_date): return @@ -280,6 +282,10 @@ def book_deferred_income_or_expense(doc, deferred_process, posting_date=None): if not amount: return + # check if books nor frozen till endate: + if getdate(end_date) >= getdate(accounts_frozen_upto): + end_date = get_last_day(add_days(accounts_frozen_upto, 1)) + if via_journal_entry: book_revenue_via_journal_entry(doc, credit_account, debit_account, against, amount, base_amount, end_date, project, account_currency, item.cost_center, item, deferred_process, submit_journal_entry) diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 76948652f2b..82c5ab5fc1f 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -2482,6 +2482,9 @@ class TestSalesInvoice(unittest.TestCase): si.save() si.submit() + acc_settings.acc_frozen_upto = '2019-01-31' + acc_settings.save() + pda1 = frappe.get_doc(dict( doctype='Process Deferred Accounting', posting_date=nowdate(), @@ -2495,8 +2498,8 @@ class TestSalesInvoice(unittest.TestCase): pda1.submit() expected_gle = [ - ["Sales - _TC", 0.0, 2089.89, "2019-01-31"], - [deferred_account, 2089.89, 0.0, "2019-01-31"], + ["Sales - _TC", 0.0, 2089.89, "2019-01-28"], + [deferred_account, 2089.89, 0.0, "2019-01-28"], ["Sales - _TC", 0.0, 1887.64, "2019-02-28"], [deferred_account, 1887.64, 0.0, "2019-02-28"], ["Sales - _TC", 0.0, 2022.47, "2019-03-15"], @@ -2517,6 +2520,7 @@ class TestSalesInvoice(unittest.TestCase): acc_settings = frappe.get_doc('Accounts Settings', 'Accounts Settings') acc_settings.book_deferred_entries_via_journal_entry = 0 acc_settings.submit_journal_entriessubmit_journal_entries = 0 + acc_settings.acc_frozen_upto = None acc_settings.save() def get_sales_invoice_for_e_invoice(): diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 2a95553fbf2..b7b198eac4c 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -191,8 +191,6 @@ class AccountsController(TransactionBase): frappe.throw(_("Row #{0}: Service Start Date cannot be greater than Service End Date").format(d.idx)) elif getdate(self.posting_date) > getdate(d.service_end_date): frappe.throw(_("Row #{0}: Service End Date cannot be before Invoice Posting Date").format(d.idx)) - elif getdate(self.posting_date) > getdate(d.service_start_date): - frappe.throw(_("Row #{0}: Service Start Date cannot be before Invoice Posting Date").format(d.idx)) def validate_invoice_documents_schedule(self): self.validate_payment_schedule_dates() From 8ed138e3733018a2831eb3ef1e4229adaeef4ea1 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 7 Jan 2022 20:57:15 +0530 Subject: [PATCH 016/102] fix: Test case (cherry picked from commit 18c4ddadf1428133961f3558d7df8eefb7475f40) --- erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 82c5ab5fc1f..4bf920ac4e4 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -2520,9 +2520,10 @@ class TestSalesInvoice(unittest.TestCase): acc_settings = frappe.get_doc('Accounts Settings', 'Accounts Settings') acc_settings.book_deferred_entries_via_journal_entry = 0 acc_settings.submit_journal_entriessubmit_journal_entries = 0 - acc_settings.acc_frozen_upto = None acc_settings.save() + frappe.db.set_value('Accounts Settings', None, 'acc_frozen_upto', add_days(getdate(), 1)) + def get_sales_invoice_for_e_invoice(): si = make_sales_invoice_for_ewaybill() si.naming_series = 'INV-2020-.#####' From eff48299e4310ad02cb494771b5ab84cb6474927 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Thu, 13 Jan 2022 19:44:26 +0530 Subject: [PATCH 017/102] fix: Test case (cherry picked from commit 24bb1e8987c222139884076e4134fe52e3d214f7) --- .../doctype/sales_invoice/test_sales_invoice.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 4bf920ac4e4..d76e655d071 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -2459,7 +2459,7 @@ class TestSalesInvoice(unittest.TestCase): deferred_account = create_account(account_name="Deferred Revenue", parent_account="Current Liabilities - _TC", company="_Test Company") - acc_settings = frappe.get_doc('Accounts Settings', 'Accounts Settings') + acc_settings = frappe.get_single('Accounts Settings') acc_settings.book_deferred_entries_via_journal_entry = 1 acc_settings.submit_journal_entries = 1 acc_settings.save() @@ -2474,7 +2474,7 @@ class TestSalesInvoice(unittest.TestCase): si.set_posting_time = 1 si.posting_date = '2019-01-01' - si.debit_to = '_Test Receivable USD - _TC' + # si.debit_to = '_Test Receivable USD - _TC' si.items[0].enable_deferred_revenue = 1 si.items[0].service_start_date = "2019-01-01" si.items[0].service_end_date = "2019-03-30" @@ -2482,8 +2482,7 @@ class TestSalesInvoice(unittest.TestCase): si.save() si.submit() - acc_settings.acc_frozen_upto = '2019-01-31' - acc_settings.save() + frappe.db.set_value('Accounts Settings', None, 'acc_frozen_upto', getdate('2019-01-31')) pda1 = frappe.get_doc(dict( doctype='Process Deferred Accounting', @@ -2517,12 +2516,12 @@ class TestSalesInvoice(unittest.TestCase): self.assertEqual(expected_gle[i][2], gle.debit) self.assertEqual(getdate(expected_gle[i][3]), gle.posting_date) - acc_settings = frappe.get_doc('Accounts Settings', 'Accounts Settings') + acc_settings = frappe.get_single('Accounts Settings') acc_settings.book_deferred_entries_via_journal_entry = 0 acc_settings.submit_journal_entriessubmit_journal_entries = 0 acc_settings.save() - frappe.db.set_value('Accounts Settings', None, 'acc_frozen_upto', add_days(getdate(), 1)) + frappe.db.set_value('Accounts Settings', None, 'acc_frozen_upto', getdate('2019-01-31')) def get_sales_invoice_for_e_invoice(): si = make_sales_invoice_for_ewaybill() From 045a505a38f1320736aea8343c287bde4a10e215 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Thu, 13 Jan 2022 23:05:27 +0530 Subject: [PATCH 018/102] fix: Remove comment (cherry picked from commit 08f26de3df28eabb93064217f3a67ff9b0ce01b9) --- erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index d76e655d071..7a26611b58d 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -2474,7 +2474,7 @@ class TestSalesInvoice(unittest.TestCase): si.set_posting_time = 1 si.posting_date = '2019-01-01' - # si.debit_to = '_Test Receivable USD - _TC' + si.debit_to = '_Test Receivable USD - _TC' si.items[0].enable_deferred_revenue = 1 si.items[0].service_start_date = "2019-01-01" si.items[0].service_end_date = "2019-03-30" @@ -2521,7 +2521,7 @@ class TestSalesInvoice(unittest.TestCase): acc_settings.submit_journal_entriessubmit_journal_entries = 0 acc_settings.save() - frappe.db.set_value('Accounts Settings', None, 'acc_frozen_upto', getdate('2019-01-31')) + frappe.db.set_value('Accounts Settings', None, 'acc_frozen_upto', None) def get_sales_invoice_for_e_invoice(): si = make_sales_invoice_for_ewaybill() From ef3e4ee61feffc03b3270c0a18198298ac5917cc Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sat, 15 Jan 2022 18:27:40 +0530 Subject: [PATCH 019/102] fix: Remove unwanted test (cherry picked from commit fd922f1617303d8bf26529b9a9e9d3e919cba011) --- .../sales_invoice/test_sales_invoice.py | 41 ------------------- 1 file changed, 41 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 7a26611b58d..12e00dcac6f 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -1789,47 +1789,6 @@ class TestSalesInvoice(unittest.TestCase): check_gl_entries(self, si.name, expected_gle, "2019-01-30") - def test_deferred_revenue_post_account_freeze_upto_by_admin(self): - frappe.set_user("Administrator") - - frappe.db.set_value('Accounts Settings', None, 'acc_frozen_upto', None) - frappe.db.set_value('Accounts Settings', None, 'frozen_accounts_modifier', None) - - deferred_account = create_account(account_name="Deferred Revenue", - parent_account="Current Liabilities - _TC", company="_Test Company") - - item = create_item("_Test Item for Deferred Accounting") - item.enable_deferred_revenue = 1 - item.deferred_revenue_account = deferred_account - item.no_of_months = 12 - item.save() - - si = create_sales_invoice(item=item.name, posting_date="2019-01-10", do_not_save=True) - si.items[0].enable_deferred_revenue = 1 - si.items[0].service_start_date = "2019-01-10" - si.items[0].service_end_date = "2019-03-15" - si.items[0].deferred_revenue_account = deferred_account - si.save() - si.submit() - - frappe.db.set_value('Accounts Settings', None, 'acc_frozen_upto', getdate('2019-01-31')) - frappe.db.set_value('Accounts Settings', None, 'frozen_accounts_modifier', 'System Manager') - - pda1 = frappe.get_doc(dict( - doctype='Process Deferred Accounting', - posting_date=nowdate(), - start_date="2019-01-01", - end_date="2019-03-31", - type="Income", - company="_Test Company" - )) - - pda1.insert() - self.assertRaises(frappe.ValidationError, pda1.submit) - - frappe.db.set_value('Accounts Settings', None, 'acc_frozen_upto', None) - frappe.db.set_value('Accounts Settings', None, 'frozen_accounts_modifier', None) - def test_fixed_deferred_revenue(self): deferred_account = create_account(account_name="Deferred Revenue", parent_account="Current Liabilities - _TC", company="_Test Company") From ecbd885aad5de796380a790ea991b8b7c10dd8d2 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Sun, 16 Jan 2022 12:33:55 +0530 Subject: [PATCH 020/102] fix: exclude existing serial numbers while auto creating new #29292 (#29300) fix: exclude existing serial numbers while auto creating new (cherry picked from commit 2b681f04fd47b0f73685c8680dcaa7203d671be3) Co-authored-by: Dany Robert --- erpnext/stock/doctype/serial_no/serial_no.py | 8 +++++++- .../stock/doctype/serial_no/test_serial_no.py | 19 +++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/serial_no/serial_no.py b/erpnext/stock/doctype/serial_no/serial_no.py index b2321acf9e8..3b325b80295 100644 --- a/erpnext/stock/doctype/serial_no/serial_no.py +++ b/erpnext/stock/doctype/serial_no/serial_no.py @@ -421,10 +421,16 @@ def update_serial_nos(sle, item_det): def get_auto_serial_nos(serial_no_series, qty): serial_nos = [] for i in range(cint(qty)): - serial_nos.append(make_autoname(serial_no_series, "Serial No")) + serial_nos.append(get_new_serial_number(serial_no_series)) return "\n".join(serial_nos) +def get_new_serial_number(series): + sr_no = make_autoname(series, "Serial No") + if frappe.db.exists("Serial No", sr_no): + sr_no = get_new_serial_number(series) + return sr_no + def auto_make_serial_nos(args): serial_nos = get_serial_nos(args.get('serial_no')) created_numbers = [] diff --git a/erpnext/stock/doctype/serial_no/test_serial_no.py b/erpnext/stock/doctype/serial_no/test_serial_no.py index 99000d12016..9cdc0f74355 100644 --- a/erpnext/stock/doctype/serial_no/test_serial_no.py +++ b/erpnext/stock/doctype/serial_no/test_serial_no.py @@ -8,6 +8,7 @@ import frappe from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note +from erpnext.stock.doctype.item.test_item import make_item from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos from erpnext.stock.doctype.stock_entry.test_stock_entry import make_serialized_item @@ -176,6 +177,24 @@ class TestSerialNo(ERPNextTestCase): self.assertEqual(sn_doc.warehouse, "_Test Warehouse - _TC") self.assertEqual(sn_doc.purchase_document_no, se.name) + def test_auto_creation_of_serial_no(self): + """ + Test if auto created Serial No excludes existing serial numbers + """ + item_code = make_item("_Test Auto Serial Item ", { + "has_serial_no": 1, + "serial_no_series": "XYZ.###" + }).item_code + + # Reserve XYZ005 + pr_1 = make_purchase_receipt(item_code=item_code, qty=1, serial_no="XYZ005") + # XYZ005 is already used and will throw an error if used again + pr_2 = make_purchase_receipt(item_code=item_code, qty=10) + + self.assertEqual(get_serial_nos(pr_1.get("items")[0].serial_no)[0], "XYZ005") + for serial_no in get_serial_nos(pr_2.get("items")[0].serial_no): + self.assertNotEqual(serial_no, "XYZ005") + def test_serial_no_sanitation(self): "Test if Serial No input is sanitised before entering the DB." item_code = "_Test Serialized Item" From 42d3b0c0634e8e48c9535d7a4c43244f9503e0d1 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Sun, 16 Jan 2022 14:05:09 +0530 Subject: [PATCH 021/102] fix: only add stock queue if FIFO (#29302) (#29305) (cherry picked from commit b0cf6195e9efe01150c759483c8ec1c465c746a6) Co-authored-by: Ankush Menat --- erpnext/stock/stock_ledger.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index 456cfe3d76f..2ce7253b8cc 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -441,8 +441,9 @@ class update_entries_after(object): # assert self.wh_data.valuation_rate = sle.valuation_rate self.wh_data.qty_after_transaction = sle.qty_after_transaction - self.wh_data.stock_queue = [[self.wh_data.qty_after_transaction, self.wh_data.valuation_rate]] self.wh_data.stock_value = flt(self.wh_data.qty_after_transaction) * flt(self.wh_data.valuation_rate) + if self.valuation_method != "Moving Average": + self.wh_data.stock_queue = [[self.wh_data.qty_after_transaction, self.wh_data.valuation_rate]] else: if self.valuation_method == "Moving Average": self.get_moving_average_values(sle) From 5db8af8ceef4c1df0472fd247036a2116344d2b7 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Sun, 16 Jan 2022 20:59:19 +0530 Subject: [PATCH 022/102] fix(patch): sle.serial_no = "\n" causes incorrect queue (#29306) (#29309) This happens due to old data. (cherry picked from commit 66bf21f14337a9c83b1ab423a7fafe70781ef21b) Co-authored-by: Ankush Menat --- erpnext/patches.txt | 2 +- erpnext/patches/v13_0/trim_whitespace_from_serial_nos.py | 4 +++- erpnext/stock/stock_ledger.py | 5 ++++- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 18319f70c47..8c9b365fa7e 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -305,7 +305,7 @@ erpnext.patches.v13_0.shopify_deprecation_warning erpnext.patches.v13_0.add_custom_field_for_south_africa #2 erpnext.patches.v13_0.rename_discharge_ordered_date_in_ip_record erpnext.patches.v13_0.remove_bad_selling_defaults -erpnext.patches.v13_0.trim_whitespace_from_serial_nos +erpnext.patches.v13_0.trim_whitespace_from_serial_nos # 16-01-2022 erpnext.patches.v13_0.migrate_stripe_api erpnext.patches.v13_0.reset_clearance_date_for_intracompany_payment_entries execute:frappe.reload_doc("erpnext_integrations", "doctype", "TaxJar Settings") diff --git a/erpnext/patches/v13_0/trim_whitespace_from_serial_nos.py b/erpnext/patches/v13_0/trim_whitespace_from_serial_nos.py index 8a9633d8964..4ec22e9d0e1 100644 --- a/erpnext/patches/v13_0/trim_whitespace_from_serial_nos.py +++ b/erpnext/patches/v13_0/trim_whitespace_from_serial_nos.py @@ -9,13 +9,15 @@ def execute(): from `tabStock Ledger Entry` where is_cancelled = 0 - and (serial_no like %s or serial_no like %s or serial_no like %s or serial_no like %s) + and ( serial_no like %s or serial_no like %s or serial_no like %s or serial_no like %s + or serial_no = %s ) """, ( " %", # leading whitespace "% ", # trailing whitespace "%\n %", # leading whitespace on newline "% \n%", # trailing whitespace on newline + "\n", # just new line ), as_dict=True, ) diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index 2ce7253b8cc..85170553560 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -105,6 +105,7 @@ def get_args_for_future_sle(row): def validate_serial_no(sle): from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos + for sn in get_serial_nos(sle.serial_no): args = copy.deepcopy(sle) args.serial_no = sn @@ -415,6 +416,8 @@ class update_entries_after(object): return sorted(entries_to_fix, key=lambda k: k['timestamp']) def process_sle(self, sle): + from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos + # previous sle data for this warehouse self.wh_data = self.data[sle.warehouse] @@ -429,7 +432,7 @@ class update_entries_after(object): if not self.args.get("sle_id"): self.get_dynamic_incoming_outgoing_rate(sle) - if sle.serial_no: + if get_serial_nos(sle.serial_no): self.get_serialized_values(sle) self.wh_data.qty_after_transaction += flt(sle.actual_qty) if sle.voucher_type == "Stock Reconciliation": From de9a0d6a7fead8d7156d750b65e7ea64e7d9971d Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Sun, 16 Jan 2022 21:44:07 +0530 Subject: [PATCH 023/102] fix: ignore cancelled SLEs (#29303) (#29307) (cherry picked from commit 82ea95873049deff46930ea0a792df7609fdf39f) Co-authored-by: Ankush Menat --- erpnext/stock/doctype/batch/batch.py | 1 + .../batch_item_expiry_status/batch_item_expiry_status.py | 3 ++- erpnext/stock/report/cogs_by_item_group/cogs_by_item_group.py | 2 +- .../itemwise_recommended_reorder_level.py | 1 + erpnext/stock/stock_ledger.py | 3 +++ 5 files changed, 8 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/doctype/batch/batch.py b/erpnext/stock/doctype/batch/batch.py index 2fcdba59e35..c0cb30b5edb 100644 --- a/erpnext/stock/doctype/batch/batch.py +++ b/erpnext/stock/doctype/batch/batch.py @@ -293,6 +293,7 @@ def get_batches(item_code, warehouse, qty=1, throw=False, serial_no=None): join `tabStock Ledger Entry` ignore index (item_code, warehouse) on (`tabBatch`.batch_id = `tabStock Ledger Entry`.batch_no ) where `tabStock Ledger Entry`.item_code = %s and `tabStock Ledger Entry`.warehouse = %s + and `tabStock Ledger Entry`.is_cancelled = 0 and (`tabBatch`.expiry_date >= CURDATE() or `tabBatch`.expiry_date IS NULL) {0} group by batch_id order by `tabBatch`.expiry_date ASC, `tabBatch`.creation ASC diff --git a/erpnext/stock/report/batch_item_expiry_status/batch_item_expiry_status.py b/erpnext/stock/report/batch_item_expiry_status/batch_item_expiry_status.py index 44e13869ddb..87097c72fa4 100644 --- a/erpnext/stock/report/batch_item_expiry_status/batch_item_expiry_status.py +++ b/erpnext/stock/report/batch_item_expiry_status/batch_item_expiry_status.py @@ -55,7 +55,8 @@ def get_stock_ledger_entries(filters): return frappe.db.sql("""select item_code, batch_no, warehouse, posting_date, actual_qty from `tabStock Ledger Entry` - where docstatus < 2 and ifnull(batch_no, '') != '' %s order by item_code, warehouse""" % + where is_cancelled = 0 + and docstatus < 2 and ifnull(batch_no, '') != '' %s order by item_code, warehouse""" % conditions, as_dict=1) def get_item_warehouse_batch_map(filters, float_precision): diff --git a/erpnext/stock/report/cogs_by_item_group/cogs_by_item_group.py b/erpnext/stock/report/cogs_by_item_group/cogs_by_item_group.py index 5f6184d6f35..058af77aa21 100644 --- a/erpnext/stock/report/cogs_by_item_group/cogs_by_item_group.py +++ b/erpnext/stock/report/cogs_by_item_group/cogs_by_item_group.py @@ -91,7 +91,7 @@ def get_stock_value_difference_list(filtered_entries: FilteredEntries) -> SVDLis voucher_nos = [fe.get('voucher_no') for fe in filtered_entries] svd_list = frappe.get_list( 'Stock Ledger Entry', fields=['item_code','stock_value_difference'], - filters=[('voucher_no', 'in', voucher_nos)] + filters=[('voucher_no', 'in', voucher_nos), ("is_cancelled", "=", 0)] ) assign_item_groups_to_svd_list(svd_list) return svd_list diff --git a/erpnext/stock/report/itemwise_recommended_reorder_level/itemwise_recommended_reorder_level.py b/erpnext/stock/report/itemwise_recommended_reorder_level/itemwise_recommended_reorder_level.py index 3f490653e14..cfa1e474c7b 100644 --- a/erpnext/stock/report/itemwise_recommended_reorder_level/itemwise_recommended_reorder_level.py +++ b/erpnext/stock/report/itemwise_recommended_reorder_level/itemwise_recommended_reorder_level.py @@ -76,6 +76,7 @@ def get_consumed_items(condition): on sle.voucher_no = se.name where actual_qty < 0 + and is_cancelled = 0 and voucher_type not in ('Delivery Note', 'Sales Invoice') %s group by item_code""" % condition, as_dict=1) diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index 85170553560..3008e822a89 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -643,6 +643,7 @@ class update_entries_after(object): where company = %s and actual_qty > 0 + and is_cancelled = 0 and (serial_no = %s or serial_no like %s or serial_no like %s @@ -946,6 +947,7 @@ def get_valuation_rate(item_code, warehouse, voucher_type, voucher_no, item_code = %s AND warehouse = %s AND valuation_rate >= 0 + AND is_cancelled = 0 AND NOT (voucher_no = %s AND voucher_type = %s) order by posting_date desc, posting_time desc, name desc limit 1""", (item_code, warehouse, voucher_no, voucher_type)) @@ -956,6 +958,7 @@ def get_valuation_rate(item_code, warehouse, voucher_type, voucher_no, where item_code = %s AND valuation_rate > 0 + AND is_cancelled = 0 AND NOT(voucher_no = %s AND voucher_type = %s) order by posting_date desc, posting_time desc, name desc limit 1""", (item_code, voucher_no, voucher_type)) From 83ca2422f6d6260259d4dc31c427984ea3620360 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 17 Jan 2022 12:08:21 +0530 Subject: [PATCH 024/102] fix: show work order progress bar even it is closed (#29312) (#29313) (cherry picked from commit 4d480358289af007618a12e4b74c963d0c1524cb) Co-authored-by: Anupam Kumar --- erpnext/manufacturing/doctype/work_order/work_order.js | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/erpnext/manufacturing/doctype/work_order/work_order.js b/erpnext/manufacturing/doctype/work_order/work_order.js index e47103599e9..3f2f39e73af 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.js +++ b/erpnext/manufacturing/doctype/work_order/work_order.js @@ -131,16 +131,14 @@ frappe.ui.form.on("Work Order", { erpnext.work_order.set_custom_buttons(frm); frm.set_intro(""); - if (frm.doc.docstatus === 0 && !frm.doc.__islocal) { + if (frm.doc.docstatus === 0 && !frm.is_new()) { frm.set_intro(__("Submit this Work Order for further processing.")); + } else { + frm.trigger("show_progress_for_items"); + frm.trigger("show_progress_for_operations"); } if (frm.doc.status != "Closed") { - if (frm.doc.docstatus===1) { - frm.trigger('show_progress_for_items'); - frm.trigger('show_progress_for_operations'); - } - if (frm.doc.docstatus === 1 && frm.doc.operations && frm.doc.operations.length) { From 3b0f80b8657a571c72652f42ad945646c9c3e257 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Fri, 7 Jan 2022 11:10:23 +0530 Subject: [PATCH 025/102] fix: dont update sle values from get_gl_entries (cherry picked from commit 8f5772463c7f1188110caccae0906d0e98a4b050) --- erpnext/controllers/stock_controller.py | 33 +------------------------ 1 file changed, 1 insertion(+), 32 deletions(-) diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index 7073e32f536..f22669b2555 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -17,7 +17,7 @@ from erpnext.accounts.general_ledger import ( from erpnext.accounts.utils import get_fiscal_year from erpnext.controllers.accounts_controller import AccountsController from erpnext.stock import get_warehouse_account_map -from erpnext.stock.stock_ledger import get_items_to_be_repost, get_valuation_rate +from erpnext.stock.stock_ledger import get_items_to_be_repost class QualityInspectionRequiredError(frappe.ValidationError): pass @@ -111,17 +111,6 @@ class StockController(AccountsController): self.check_expense_account(item_row) - # If the item does not have the allow zero valuation rate flag set - # and ( valuation rate not mentioned in an incoming entry - # or incoming entry not found while delivering the item), - # try to pick valuation rate from previous sle or Item master and update in SLE - # Otherwise, throw an exception - - if not sle.stock_value_difference and self.doctype != "Stock Reconciliation" \ - and not item_row.get("allow_zero_valuation_rate"): - - sle = self.update_stock_ledger_entries(sle) - # expense account/ target_warehouse / source_warehouse if item_row.get('target_warehouse'): warehouse = item_row.get('target_warehouse') @@ -164,26 +153,6 @@ class StockController(AccountsController): return frappe.flags.debit_field_precision - def update_stock_ledger_entries(self, sle): - sle.valuation_rate = get_valuation_rate(sle.item_code, sle.warehouse, - self.doctype, self.name, currency=self.company_currency, company=self.company) - - sle.stock_value = flt(sle.qty_after_transaction) * flt(sle.valuation_rate) - sle.stock_value_difference = flt(sle.actual_qty) * flt(sle.valuation_rate) - - if sle.name: - frappe.db.sql(""" - update - `tabStock Ledger Entry` - set - stock_value = %(stock_value)s, - valuation_rate = %(valuation_rate)s, - stock_value_difference = %(stock_value_difference)s - where - name = %(name)s""", (sle)) - - return sle - def get_voucher_details(self, default_expense_account, default_cost_center, sle_map): if self.doctype == "Stock Reconciliation": reconciliation_purpose = frappe.db.get_value(self.doctype, self.name, "purpose") From a0c2c94cff2c99d632c632b62ebd2a1d7d7d9440 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Sun, 16 Jan 2022 13:24:40 +0530 Subject: [PATCH 026/102] test: make sure zero incoming rate is maintained while consuming (cherry picked from commit 0272397e54513b5a8c18f4e817d3ddf708efc191) --- .../doctype/stock_entry/test_stock_entry.py | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/erpnext/stock/doctype/stock_entry/test_stock_entry.py b/erpnext/stock/doctype/stock_entry/test_stock_entry.py index af7acc36353..3d936c50f7a 100644 --- a/erpnext/stock/doctype/stock_entry/test_stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/test_stock_entry.py @@ -852,6 +852,34 @@ class TestStockEntry(ERPNextTestCase): self.assertEqual(se.get("items")[0].allow_zero_valuation_rate, 1) self.assertEqual(se.get("items")[0].amount, 0) + def test_zero_incoming_rate(self): + """ Make sure incoming rate of 0 is allowed while consuming. + + qty | rate | valuation rate + 1 | 100 | 100 + 1 | 0 | 50 + -1 | 100 | 0 + -1 | 0 <--- assert this + """ + item_code = "_TestZeroVal" + warehouse = "_Test Warehouse - _TC" + create_item('_TestZeroVal') + _receipt = make_stock_entry(item_code=item_code, qty=1, to_warehouse=warehouse, rate=100) + receipt2 = make_stock_entry(item_code=item_code, qty=1, to_warehouse=warehouse, rate=0, do_not_save=True) + receipt2.items[0].allow_zero_valuation_rate = 1 + receipt2.save() + receipt2.submit() + + issue = make_stock_entry(item_code=item_code, qty=1, from_warehouse=warehouse) + + value_diff = frappe.db.get_value("Stock Ledger Entry", {"voucher_no": issue.name, "voucher_type": "Stock Entry"}, "stock_value_difference") + self.assertEqual(value_diff, -100) + + issue2 = make_stock_entry(item_code=item_code, qty=1, from_warehouse=warehouse) + value_diff = frappe.db.get_value("Stock Ledger Entry", {"voucher_no": issue2.name, "voucher_type": "Stock Entry"}, "stock_value_difference") + self.assertEqual(value_diff, 0) + + def test_gle_for_opening_stock_entry(self): mr = make_stock_entry(item_code="_Test Item", target="Stores - TCP1", company="_Test Company with perpetual inventory", qty=50, basic_rate=100, From ea9756da8fbdd270e8f10da5f48bbb5e21215da0 Mon Sep 17 00:00:00 2001 From: ruthra Date: Thu, 6 Jan 2022 11:34:47 +0530 Subject: [PATCH 027/102] fix: get project from PO into payment entry (cherry picked from commit ca17c7226ce343e63cf929023babee425e3da3c5) --- erpnext/accounts/doctype/payment_entry/payment_entry.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index c8c214500c9..e3c9a18dccd 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -3,6 +3,7 @@ import json +from functools import reduce import frappe from frappe import ValidationError, _, scrub, throw @@ -1523,6 +1524,8 @@ def get_payment_entry(dt, dn, party_amount=None, bank_account=None, bank_amount= pe.paid_amount = paid_amount pe.received_amount = received_amount pe.letter_head = doc.get("letter_head") + if dt == 'Purchase Order': + pe.project = reduce(lambda prev,cur: prev or cur, [x.project for x in doc.get('items')], None) # get first non-empty project from items if pe.party_type in ["Customer", "Supplier"]: bank_account = get_party_bank_account(pe.party_type, pe.party) From 7f5415177e88d1968c59fc23ed2684aae2af7538 Mon Sep 17 00:00:00 2001 From: ruthra Date: Mon, 10 Jan 2022 12:58:58 +0530 Subject: [PATCH 028/102] refactor: get project from basic transactions - sales order, sales invoice, purchase order and purchase order - if project not found in transaction, get from items (cherry picked from commit 8026f86548d0eccc56b1f66e72f47719e7a85969) --- erpnext/accounts/doctype/payment_entry/payment_entry.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index e3c9a18dccd..96ebc5e0658 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -1524,8 +1524,10 @@ def get_payment_entry(dt, dn, party_amount=None, bank_account=None, bank_amount= pe.paid_amount = paid_amount pe.received_amount = received_amount pe.letter_head = doc.get("letter_head") - if dt == 'Purchase Order': - pe.project = reduce(lambda prev,cur: prev or cur, [x.project for x in doc.get('items')], None) # get first non-empty project from items + + if dt in ['Purchase Order', 'Sales Order', 'Sales Invoice', 'Purchase Invoice']: + pe.project = (doc.get('project') or + reduce(lambda prev,cur: prev or cur, [x.get('project') for x in doc.get('items')], None)) # get first non-empty project from items if pe.party_type in ["Customer", "Supplier"]: bank_account = get_party_bank_account(pe.party_type, pe.party) From 5da7e28d20b4bdc3b01a26235b593a199b3dc5db Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 17 Jan 2022 15:01:17 +0530 Subject: [PATCH 029/102] fix: Linting issues (cherry picked from commit 09172002e729a2bbee2362f3132d0cbd688c6b20) --- erpnext/accounts/doctype/payment_entry/payment_entry.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 96ebc5e0658..80f61d4f02e 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -1527,7 +1527,7 @@ def get_payment_entry(dt, dn, party_amount=None, bank_account=None, bank_amount= if dt in ['Purchase Order', 'Sales Order', 'Sales Invoice', 'Purchase Invoice']: pe.project = (doc.get('project') or - reduce(lambda prev,cur: prev or cur, [x.get('project') for x in doc.get('items')], None)) # get first non-empty project from items + reduce(lambda prev,cur: prev or cur, [x.get('project') for x in doc.get('items')], None)) # get first non-empty project from items if pe.party_type in ["Customer", "Supplier"]: bank_account = get_party_bank_account(pe.party_type, pe.party) From c2d58ba796ca053fa4f2660c4a405a6c23d540b8 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 17 Jan 2022 16:48:04 +0530 Subject: [PATCH 030/102] feat(UX): Option to exclude holidays while marking monthly attendance (backport #29185) (#29321) (cherry picked from commit 6aac8de53e5ec57fea3dbdb21d03a6dbc0eade62) Co-authored-by: Rucha Mahabal --- erpnext/hr/doctype/attendance/attendance.py | 11 ++++-- .../hr/doctype/attendance/attendance_list.js | 36 +++++++++++++++++-- 2 files changed, 42 insertions(+), 5 deletions(-) diff --git a/erpnext/hr/doctype/attendance/attendance.py b/erpnext/hr/doctype/attendance/attendance.py index 7dcfac249f4..b1eaaf8b587 100644 --- a/erpnext/hr/doctype/attendance/attendance.py +++ b/erpnext/hr/doctype/attendance/attendance.py @@ -5,9 +5,9 @@ import frappe from frappe import _ from frappe.model.document import Document -from frappe.utils import cstr, formatdate, get_datetime, getdate, nowdate +from frappe.utils import cint, cstr, formatdate, get_datetime, getdate, nowdate -from erpnext.hr.utils import validate_active_employee +from erpnext.hr.utils import get_holiday_dates_for_employee, validate_active_employee class Attendance(Document): @@ -171,7 +171,7 @@ def get_month_map(): }) @frappe.whitelist() -def get_unmarked_days(employee, month): +def get_unmarked_days(employee, month, exclude_holidays=0): import calendar month_map = get_month_map() @@ -191,6 +191,11 @@ def get_unmarked_days(employee, month): ]) marked_days = [get_datetime(record.attendance_date) for record in records] + if cint(exclude_holidays): + holiday_dates = get_holiday_dates_for_employee(employee, month_start, month_end) + holidays = [get_datetime(record) for record in holiday_dates] + marked_days.extend(holidays) + unmarked_days = [] for date in dates_of_month: diff --git a/erpnext/hr/doctype/attendance/attendance_list.js b/erpnext/hr/doctype/attendance/attendance_list.js index 6b3c29a76b4..3a5c5915396 100644 --- a/erpnext/hr/doctype/attendance/attendance_list.js +++ b/erpnext/hr/doctype/attendance/attendance_list.js @@ -28,6 +28,7 @@ frappe.listview_settings['Attendance'] = { onchange: function() { dialog.set_df_property("unmarked_days", "hidden", 1); dialog.set_df_property("status", "hidden", 1); + dialog.set_df_property("exclude_holidays", "hidden", 1); dialog.set_df_property("month", "value", ''); dialog.set_df_property("unmarked_days", "options", []); dialog.no_unmarked_days_left = false; @@ -42,9 +43,14 @@ frappe.listview_settings['Attendance'] = { onchange: function() { if (dialog.fields_dict.employee.value && dialog.fields_dict.month.value) { dialog.set_df_property("status", "hidden", 0); + dialog.set_df_property("exclude_holidays", "hidden", 0); dialog.set_df_property("unmarked_days", "options", []); dialog.no_unmarked_days_left = false; - me.get_multi_select_options(dialog.fields_dict.employee.value, dialog.fields_dict.month.value).then(options => { + me.get_multi_select_options( + dialog.fields_dict.employee.value, + dialog.fields_dict.month.value, + dialog.fields_dict.exclude_holidays.get_value() + ).then(options => { if (options.length > 0) { dialog.set_df_property("unmarked_days", "hidden", 0); dialog.set_df_property("unmarked_days", "options", options); @@ -64,6 +70,31 @@ frappe.listview_settings['Attendance'] = { reqd: 1, }, + { + label: __("Exclude Holidays"), + fieldtype: "Check", + fieldname: "exclude_holidays", + hidden: 1, + onchange: function() { + if (dialog.fields_dict.employee.value && dialog.fields_dict.month.value) { + dialog.set_df_property("status", "hidden", 0); + dialog.set_df_property("unmarked_days", "options", []); + dialog.no_unmarked_days_left = false; + me.get_multi_select_options( + dialog.fields_dict.employee.value, + dialog.fields_dict.month.value, + dialog.fields_dict.exclude_holidays.get_value() + ).then(options => { + if (options.length > 0) { + dialog.set_df_property("unmarked_days", "hidden", 0); + dialog.set_df_property("unmarked_days", "options", options); + } else { + dialog.no_unmarked_days_left = true; + } + }); + } + } + }, { label: __("Unmarked Attendance for days"), fieldname: "unmarked_days", @@ -105,7 +136,7 @@ frappe.listview_settings['Attendance'] = { }); }, - get_multi_select_options: function(employee, month) { + get_multi_select_options: function(employee, month, exclude_holidays) { return new Promise(resolve => { frappe.call({ method: 'erpnext.hr.doctype.attendance.attendance.get_unmarked_days', @@ -113,6 +144,7 @@ frappe.listview_settings['Attendance'] = { args: { employee: employee, month: month, + exclude_holidays: exclude_holidays } }).then(r => { var options = []; From a3a99207711698094302f8f3d7de11a2c808d09e Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Tue, 21 Dec 2021 16:49:41 +0530 Subject: [PATCH 031/102] fix: incorrect incoming rate computation for sr no (cherry picked from commit b9642a1036d7b6fa9a49a08b64145bfc2f56916f) --- erpnext/stock/stock_ledger.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index 3008e822a89..f43d82d8b59 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -599,9 +599,9 @@ class update_entries_after(object): incoming_rate = self.wh_data.valuation_rate stock_value_change = 0 - if incoming_rate: + if actual_qty > 0: stock_value_change = actual_qty * incoming_rate - elif actual_qty < 0: + else: # In case of delivery/stock issue, get average purchase rate # of serial nos of current entry if not sle.is_cancelled: From 9a3a67962884eeace554e9b798de0748d33ba277 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Wed, 22 Dec 2021 12:38:51 +0530 Subject: [PATCH 032/102] fix: get incoming rate only if not return (cherry picked from commit fcc885bc16403c80f27e6bfb970b156fd2b636b6) --- erpnext/controllers/selling_controller.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py index cc773b75963..4ff851d7f94 100644 --- a/erpnext/controllers/selling_controller.py +++ b/erpnext/controllers/selling_controller.py @@ -385,7 +385,7 @@ class SellingController(StockController): # Get incoming rate based on original item cost based on valuation method qty = flt(d.get('stock_qty') or d.get('actual_qty')) - if not d.incoming_rate: + if not (self.get("is_return") and d.incoming_rate): d.incoming_rate = get_incoming_rate({ "item_code": d.item_code, "warehouse": d.warehouse, From 0411bcbd5c33af3e2bc9ef6212a5c8eb8a67e0fb Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Sun, 16 Jan 2022 17:45:27 +0530 Subject: [PATCH 033/102] test: serial no valuation test case (cherry picked from commit 5d27a7672e73e514e82fd79b960b16479403e696) --- .../stock/doctype/serial_no/test_serial_no.py | 32 +++++++++++++++++-- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/erpnext/stock/doctype/serial_no/test_serial_no.py b/erpnext/stock/doctype/serial_no/test_serial_no.py index 9cdc0f74355..f8cea717251 100644 --- a/erpnext/stock/doctype/serial_no/test_serial_no.py +++ b/erpnext/stock/doctype/serial_no/test_serial_no.py @@ -11,6 +11,7 @@ from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delive from erpnext.stock.doctype.item.test_item import make_item from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos +from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry from erpnext.stock.doctype.stock_entry.test_stock_entry import make_serialized_item from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse @@ -22,6 +23,10 @@ from erpnext.tests.utils import ERPNextTestCase class TestSerialNo(ERPNextTestCase): + + def tearDown(self): + frappe.db.rollback() + def test_cannot_create_direct(self): frappe.delete_doc_if_exists("Serial No", "_TCSER0001") @@ -211,7 +216,28 @@ class TestSerialNo(ERPNextTestCase): self.assertEqual(se.get("items")[0].serial_no, "_TS1\n_TS2\n_TS3\n_TS4 - 2021") - frappe.db.rollback() + def test_correct_serial_no_incoming_rate(self): + """ Check correct consumption rate based on serial no record. + """ + item_code = "_Test Serialized Item" + warehouse = "_Test Warehouse - _TC" + serial_nos = ["LOWVALUATION", "HIGHVALUATION"] + + in1 = make_stock_entry(item_code=item_code, to_warehouse=warehouse, qty=1, rate=42, + serial_no=serial_nos[0]) + in2 = make_stock_entry(item_code=item_code, to_warehouse=warehouse, qty=1, rate=113, + serial_no=serial_nos[1]) + + out = create_delivery_note(item_code=item_code, qty=1, serial_no=serial_nos[0], do_not_submit=True) + + # change serial no + out.items[0].serial_no = serial_nos[1] + out.save() + out.submit() + + value_diff = frappe.db.get_value("Stock Ledger Entry", + {"voucher_no": out.name, "voucher_type": "Delivery Note"}, + "stock_value_difference" + ) + self.assertEqual(value_diff, -113) - def tearDown(self): - frappe.db.rollback() From 5e3f5d8bcce20566099406586dc5e7929d39fd68 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 17 Jan 2022 22:15:12 +0530 Subject: [PATCH 034/102] fix: purchase to Stock UOM conversion on Production Plan #28570 (#29325) fix: purchase to Stock UOM conversion on Production Plan (cherry picked from commit 9cd26fbb6a0e5977c35774dc641728beb7b0d655) Co-authored-by: Maxwell Morais --- .../doctype/production_plan/production_plan.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py index a63ed999e44..a78bd1f9e66 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -970,7 +970,25 @@ def get_materials_from_other_locations(item, warehouses, new_mr_items, company): new_mr_items.append(new_dict) if required_qty: + stock_uom, purchase_uom = frappe.db.get_value( + 'Item', + item['item_code'], + ['stock_uom', 'purchase_uom'] + ) + + if purchase_uom != stock_uom and purchase_uom == item['uom']: + conversion_factor = get_uom_conversion_factor(item['item_code'], item['uom']) + if not (conversion_factor or frappe.flags.show_qty_in_stock_uom): + frappe.throw(_("UOM Conversion factor ({0} -> {1}) not found for item: {2}") + .format(purchase_uom, stock_uom, item['item_code'])) + + required_qty = required_qty / conversion_factor + + if frappe.db.get_value("UOM", purchase_uom, "must_be_whole_number"): + required_qty = ceil(required_qty) + item["quantity"] = required_qty + new_mr_items.append(item) @frappe.whitelist() From d5d7f67ac24089b8453f1d40773ece84f3f568a3 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 17 Jan 2022 22:16:41 +0530 Subject: [PATCH 035/102] fix: cover case when all material needs to be bought (#29326) (#29327) When material request is to be made for purchase qty should be converted to purchase UOM (cherry picked from commit 48f3d535561efd4d2f3d7b0e38c00d23c68cc517) Co-authored-by: Ankush Menat --- .../doctype/production_plan/production_plan.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py index a78bd1f9e66..e903e9baf84 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -948,10 +948,6 @@ def get_materials_from_other_locations(item, warehouses, new_mr_items, company): locations = get_available_item_locations(item.get("item_code"), warehouses, item.get("quantity"), company, ignore_validation=True) - if not locations: - new_mr_items.append(item) - return - required_qty = item.get("quantity") for d in locations: if required_qty <=0: return @@ -971,8 +967,8 @@ def get_materials_from_other_locations(item, warehouses, new_mr_items, company): if required_qty: stock_uom, purchase_uom = frappe.db.get_value( - 'Item', - item['item_code'], + 'Item', + item['item_code'], ['stock_uom', 'purchase_uom'] ) From d7f16c4924642ff60b223eaf2fdfdd374b266c05 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 18 Jan 2022 11:17:32 +0530 Subject: [PATCH 036/102] refactor: convert CTE validation to python (#29324) Some old servers running on mariadb <10.2 can't work with CTEs hence reverting to basic python code Co-authored-by: Ankush Menat --- erpnext/stock/stock_ledger.py | 41 ++++++++++++++++++++--------------- 1 file changed, 23 insertions(+), 18 deletions(-) diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index f43d82d8b59..b47097553ba 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -7,7 +7,7 @@ import json import frappe from frappe import _ from frappe.model.meta import get_field_precision -from frappe.utils import cint, cstr, flt, get_link_to_form, getdate, now, nowdate +from frappe.utils import cint, cstr, flt, get_datetime, get_link_to_form, getdate, now, nowdate from six import iteritems import erpnext @@ -1139,26 +1139,31 @@ def get_future_sle_with_negative_qty(args): def get_future_sle_with_negative_batch_qty(args): - return frappe.db.sql(""" - with batch_ledger as ( - select - posting_date, posting_time, voucher_type, voucher_no, - sum(actual_qty) over (order by posting_date, posting_time, creation) as cumulative_total - from `tabStock Ledger Entry` - where - item_code = %(item_code)s - and warehouse = %(warehouse)s - and batch_no=%(batch_no)s - and is_cancelled = 0 - order by posting_date, posting_time, creation - ) - select * from batch_ledger + batch_ledger = frappe.db.sql(""" + select + posting_date, posting_time, voucher_type, voucher_no, actual_qty + from `tabStock Ledger Entry` where - cumulative_total < 0.0 - and timestamp(posting_date, posting_time) >= timestamp(%(posting_date)s, %(posting_time)s) - limit 1 + item_code = %(item_code)s + and warehouse = %(warehouse)s + and batch_no=%(batch_no)s + and is_cancelled = 0 + order by timestamp(posting_date, posting_time), creation """, args, as_dict=1) + cumulative_total = 0.0 + current_posting_datetime = get_datetime(str(args.posting_date) + " " + str(args.posting_time)) + for entry in batch_ledger: + cumulative_total += entry.actual_qty + if cumulative_total > -1e-6: + continue + + if (get_datetime(str(entry.posting_date) + " " + str(entry.posting_time)) + >= current_posting_datetime): + + entry.cumulative_total = cumulative_total + return [entry] + def _round_off_if_near_zero(number: float, precision: int = 6) -> float: """ Rounds off the number to zero only if number is close to zero for decimal From 69fded250c9dfb3e82f670a405ef5416c799b185 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 18 Jan 2022 13:33:29 +0530 Subject: [PATCH 037/102] fix: enable allow negative stock by default in reposts (#29331) (#29333) Negative stock validation is done BEFORE reposting as such it's not required to be performed again and most likely this fails on old intermediate data where user has no clue why it failed. (cherry picked from commit fabe0bce15b3bb1941316072304b244161de3a3e) Co-authored-by: Ankush Menat --- .../repost_item_valuation/repost_item_valuation.json | 11 ++++++----- .../repost_item_valuation/repost_item_valuation.py | 3 +-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.json b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.json index cd7e63b18b2..0ba97d59a14 100644 --- a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.json +++ b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.json @@ -1,7 +1,7 @@ { "actions": [], "autoname": "REPOST-ITEM-VAL-.######", - "creation": "2020-10-22 22:27:07.742161", + "creation": "2022-01-11 15:03:38.273179", "doctype": "DocType", "editable_grid": 1, "engine": "InnoDB", @@ -129,7 +129,7 @@ "reqd": 1 }, { - "default": "0", + "default": "1", "fieldname": "allow_negative_stock", "fieldtype": "Check", "label": "Allow Negative Stock" @@ -177,7 +177,7 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2021-11-24 02:18:10.524560", + "modified": "2022-01-18 10:57:33.450907", "modified_by": "Administrator", "module": "Stock", "name": "Repost Item Valuation", @@ -227,5 +227,6 @@ } ], "sort_field": "modified", - "sort_order": "DESC" -} + "sort_order": "DESC", + "states": [] +} \ No newline at end of file diff --git a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py index 5ad8f443203..3b76301b4cc 100644 --- a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py +++ b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py @@ -27,8 +27,7 @@ class RepostItemValuation(Document): self.item_code = None self.warehouse = None - self.allow_negative_stock = self.allow_negative_stock or \ - cint(frappe.db.get_single_value("Stock Settings", "allow_negative_stock")) + self.allow_negative_stock = 1 def set_company(self): if self.based_on == "Transaction": From c8eae9dcef164182932dd4a94bde451a89fee456 Mon Sep 17 00:00:00 2001 From: Anupam Date: Tue, 18 Jan 2022 16:14:45 +0530 Subject: [PATCH 038/102] 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 039/102] 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 8775154fa0dbeb9561156ce5d95eba48a6d75b61 Mon Sep 17 00:00:00 2001 From: Noah Jacob Date: Mon, 13 Dec 2021 16:50:55 +0530 Subject: [PATCH 040/102] refactor: update_serial_no function and code cleanup (cherry picked from commit 4f52b86d7e577741f65932601c6ff2066bba1553) # Conflicts: # erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.js --- .../maintenance_schedule.py | 26 +++++---- .../maintenance_visit/maintenance_visit.js | 55 +++++++++---------- 2 files changed, 41 insertions(+), 40 deletions(-) diff --git a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py index 2ffae1a4f2a..9c35f4f2457 100644 --- a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py +++ b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py @@ -1,7 +1,6 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt - import frappe from frappe import _, throw from frappe.utils import add_days, cint, cstr, date_diff, formatdate, getdate @@ -306,13 +305,18 @@ class MaintenanceSchedule(TransactionBase): return schedule.name @frappe.whitelist() -def update_serial_nos(s_id): - serial_nos = frappe.db.get_value('Maintenance Schedule Detail', s_id, 'serial_no') +def update_serial_nos(item_code, schedule=None): + serial_nos = [] + if schedule: + serial_nos = frappe.db.get_value('Maintenance Schedule Item', { + 'parent': schedule, + 'item_code': item_code + }, 'serial_no') + if serial_nos: serial_nos = get_serial_nos(serial_nos) - return serial_nos - else: - return False + + return serial_nos @frappe.whitelist() def make_maintenance_visit(source_name, target_doc=None, item_name=None, s_id=None): @@ -320,12 +324,9 @@ def make_maintenance_visit(source_name, target_doc=None, item_name=None, s_id=No def update_status_and_detail(source, target, parent): target.maintenance_type = "Scheduled" - target.maintenance_schedule = source.name target.maintenance_schedule_detail = s_id - def update_sales_and_serial(source, target, parent): - sales_person = frappe.db.get_value('Maintenance Schedule Detail', s_id, 'sales_person') - target.service_person = sales_person + 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] @@ -346,7 +347,10 @@ def make_maintenance_visit(source_name, target_doc=None, item_name=None, s_id=No "Maintenance Schedule Item": { "doctype": "Maintenance Visit Purpose", "condition": lambda doc: doc.item_name == item_name, - "postprocess": update_sales_and_serial + "field_map": { + "sales_person": "service_person" + }, + "postprocess": update_serial } }, target_doc) diff --git a/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.js b/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.js index 78289679744..b3661b7bf38 100644 --- a/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.js +++ b/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.js @@ -2,52 +2,50 @@ // License: GNU General Public License v3. See license.txt frappe.provide("erpnext.maintenance"); -var serial_nos = []; frappe.ui.form.on('Maintenance Visit', { - refresh: function (frm) { - //filters for serial_no based on item_code - frm.set_query('serial_no', 'purposes', function (frm, cdt, cdn) { - let item = locals[cdt][cdn]; - if (serial_nos) { - return { - filters: { - 'item_code': item.item_code, - 'name': ["in", serial_nos] - } - }; - } else { - return { - filters: { - 'item_code': item.item_code - } - }; - } - }); - }, setup: function (frm) { frm.set_query('contact_person', erpnext.queries.contact_query); frm.set_query('customer_address', erpnext.queries.address_query); frm.set_query('customer', erpnext.queries.customer); }, - onload: function (frm, cdt, cdn) { - let item = locals[cdt][cdn]; + onload: function (frm) { + // filters for serial no based on item code if (frm.doc.maintenance_type === "Scheduled") { - const schedule_id = item.purposes[0].prevdoc_detail_docname || frm.doc.maintenance_schedule_detail; + let item_code = frm.doc.purposes[0].item_code; frappe.call({ method: "erpnext.maintenance.doctype.maintenance_schedule.maintenance_schedule.update_serial_nos", args: { - s_id: schedule_id - }, - callback: function (r) { - serial_nos = r.message; + schedule: frm.doc.maintenance_schedule, + item_code: item_code } + }).then((r) => { + let serial_nos = r.message; + frm.set_query('serial_no', 'purposes', () => { + if (serial_nos.length > 0) { + return { + filters: { + 'item_code': item_code, + 'name': ["in", serial_nos] + } + }; + } + return { + filters: { + 'item_code': item_code + } + }; + }); }); } if (!frm.doc.status) { frm.set_value({ status: 'Draft' }); } if (frm.doc.__islocal) { +<<<<<<< HEAD frm.doc.maintenance_type == 'Unscheduled' && frm.clear_table("purposes"); +======= + frm.clear_table("purposes"); +>>>>>>> 4f52b86d7e (refactor: update_serial_no function and code cleanup) frm.set_value({ mntc_date: frappe.datetime.get_today() }); } }, @@ -60,7 +58,6 @@ frappe.ui.form.on('Maintenance Visit', { contact_person: function (frm) { erpnext.utils.get_contact_details(frm); } - }) // TODO commonify this code From dc0d974144c4aa97c0c846abdf5afd3f7a8282de Mon Sep 17 00:00:00 2001 From: Noah Jacob Date: Wed, 15 Dec 2021 15:27:41 +0530 Subject: [PATCH 041/102] fix(patch): updates maintenance schedule field for old docs (cherry picked from commit c872aa43f871dbe8fb71b5584fefab52b42a2eda) # Conflicts: # erpnext/patches.txt --- erpnext/patches.txt | 5 +++++ ...ate_maintenance_schedule_field_in_visit.py | 22 +++++++++++++++++++ 2 files changed, 27 insertions(+) create mode 100644 erpnext/patches/v13_0/update_maintenance_schedule_field_in_visit.py diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 8c9b365fa7e..13f0891b89e 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -336,8 +336,13 @@ erpnext.patches.v12_0.update_production_plan_status erpnext.patches.v13_0.item_naming_series_not_mandatory erpnext.patches.v13_0.update_category_in_ltds_certificate erpnext.patches.v13_0.create_ksa_vat_custom_fields +<<<<<<< HEAD erpnext.patches.v13_0.rename_ksa_qr_field erpnext.patches.v13_0.disable_ksa_print_format_for_others # 16-12-2021 erpnext.patches.v13_0.update_tax_category_for_rcm erpnext.patches.v13_0.convert_to_website_item_in_item_card_group_template erpnext.patches.v13_0.agriculture_deprecation_warning +======= +erpnext.patches.v13_0.update_maintenance_schedule_field_in_visit +erpnext.patches.v14_0.migrate_crm_settings +>>>>>>> c872aa43f8 (fix(patch): updates maintenance schedule field for old docs) diff --git a/erpnext/patches/v13_0/update_maintenance_schedule_field_in_visit.py b/erpnext/patches/v13_0/update_maintenance_schedule_field_in_visit.py new file mode 100644 index 00000000000..450c00e4212 --- /dev/null +++ b/erpnext/patches/v13_0/update_maintenance_schedule_field_in_visit.py @@ -0,0 +1,22 @@ + +import frappe + + +def execute(): + # Updates the Maintenance Schedule link to fetch serial nos + from frappe.query_builder.functions import Coalesce + mvp = frappe.qb.DocType('Maintenance Visit Purpose') + mv = frappe.qb.DocType('Maintenance Visit') + + frappe.qb.update( + mv + ).join( + mvp + ).on(mvp.parent == mv.name).set( + mv.maintenance_schedule, + Coalesce(mvp.prevdoc_docname, '') + ).where( + (mv.maintenance_type == "Scheduled") + & (mvp.prevdoc_docname.notnull()) + & (mv.docstatus < 2) + ).run(as_dict=1) From f541b5c780b535e324d9e79911233c8eb89acce1 Mon Sep 17 00:00:00 2001 From: Noah Jacob Date: Fri, 17 Dec 2021 11:57:17 +0530 Subject: [PATCH 042/102] test: added test for serial no. filters (cherry picked from commit 9109660a69056424a96bb9ba6d85ccf5214a45c6) --- .../test_maintenance_schedule.py | 43 ++++++++++++++++++- 1 file changed, 41 insertions(+), 2 deletions(-) diff --git a/erpnext/maintenance/doctype/maintenance_schedule/test_maintenance_schedule.py b/erpnext/maintenance/doctype/maintenance_schedule/test_maintenance_schedule.py index 501712613a8..7539505842d 100644 --- a/erpnext/maintenance/doctype/maintenance_schedule/test_maintenance_schedule.py +++ b/erpnext/maintenance/doctype/maintenance_schedule/test_maintenance_schedule.py @@ -8,7 +8,10 @@ from frappe.utils.data import add_days, formatdate, today from erpnext.maintenance.doctype.maintenance_schedule.maintenance_schedule import ( make_maintenance_visit, + update_serial_nos, ) +from erpnext.stock.doctype.item.test_item import create_item +from erpnext.stock.doctype.stock_entry.test_stock_entry import make_serialized_item # test_records = frappe.get_test_records('Maintenance Schedule') @@ -80,6 +83,41 @@ class TestMaintenanceSchedule(unittest.TestCase): #checks if visit status is back updated in schedule self.assertTrue(ms.schedules[1].completion_status, "Partially Completed") + def test_serial_no_filters(self): + # Without serial no. set in schedule -> returns None + item_code = "_Test Serial Item" + make_serial_item_with_serial(item_code) + ms = make_maintenance_schedule(item_code=item_code) + ms.submit() + + s_item = ms.schedules[0] + mv = make_maintenance_visit(source_name=ms.name, item_name=item_code, s_id=s_item.name) + mvi = mv.purposes[0] + serial_nos = update_serial_nos(mvi.item_name, ms.name) + self.assertEqual(serial_nos, None) + + # With serial no. set in schedule -> returns serial nos. + make_serial_item_with_serial(item_code) + ms = make_maintenance_schedule(item_code=item_code, serial_no="TEST001, TEST002") + ms.submit() + + s_item = ms.schedules[0] + mv = make_maintenance_visit(source_name=ms.name, item_name=item_code, s_id=s_item.name) + mvi = mv.purposes[0] + serial_nos = update_serial_nos(mvi.item_name, ms.name) + self.assertEqual(serial_nos, ["TEST001", "TEST002"]) + + frappe.db.rollback() + +def make_serial_item_with_serial(item_code): + serial_item_doc = create_item(item_code, is_stock_item=1) + if not serial_item_doc.has_serial_no or not serial_item_doc.serial_no_series: + serial_item_doc.has_serial_no = 1 + serial_item_doc.serial_no_series = "TEST.###" + serial_item_doc.save(ignore_permissions=True) + if frappe.db.exists('Serial No', {"status": "Active", "item_code": item_code}): + make_serialized_item(item_code=item_code) + def get_events(ms): return frappe.get_all("Event Participants", filters={ "reference_doctype": ms.doctype, @@ -87,17 +125,18 @@ def get_events(ms): "parenttype": "Event" }) -def make_maintenance_schedule(): +def make_maintenance_schedule(**args): ms = frappe.new_doc("Maintenance Schedule") ms.company = "_Test Company" ms.customer = "_Test Customer" ms.transaction_date = today() ms.append("items", { - "item_code": "_Test Item", + "item_code": args.get("item_code") or "_Test Item", "start_date": today(), "periodicity": "Weekly", "no_of_visits": 4, + "serial_no": args.get("serial_no"), "sales_person": "Sales Team", }) ms.insert(ignore_permissions=True) From 0bb0a9d0328ea8b9739b13f2261dd17da0671148 Mon Sep 17 00:00:00 2001 From: Noah Jacob Date: Fri, 17 Dec 2021 13:32:30 +0530 Subject: [PATCH 043/102] fix(test): fixed test case not creating serials (cherry picked from commit 90d32006f42a95dba37c51924c5f62be7436dd6f) --- .../doctype/maintenance_schedule/test_maintenance_schedule.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/maintenance/doctype/maintenance_schedule/test_maintenance_schedule.py b/erpnext/maintenance/doctype/maintenance_schedule/test_maintenance_schedule.py index 7539505842d..378d3238d75 100644 --- a/erpnext/maintenance/doctype/maintenance_schedule/test_maintenance_schedule.py +++ b/erpnext/maintenance/doctype/maintenance_schedule/test_maintenance_schedule.py @@ -115,7 +115,8 @@ def make_serial_item_with_serial(item_code): serial_item_doc.has_serial_no = 1 serial_item_doc.serial_no_series = "TEST.###" serial_item_doc.save(ignore_permissions=True) - if frappe.db.exists('Serial No', {"status": "Active", "item_code": item_code}): + active_serials = frappe.db.get_all('Serial No', {"status": "Active", "item_code": item_code}) + if len(active_serials) < 2: make_serialized_item(item_code=item_code) def get_events(ms): From 93325c72f0e99af5cb2c4a4dd763ee701c597aa1 Mon Sep 17 00:00:00 2001 From: Noah Jacob Date: Fri, 17 Dec 2021 13:46:46 +0530 Subject: [PATCH 044/102] refactor: moved purpose table mandatory check to server (cherry picked from commit b98c61f2a7247d9f27251b8e740664a77a8b51ef) # Conflicts: # erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.js --- .../doctype/maintenance_visit/maintenance_visit.js | 3 +++ .../doctype/maintenance_visit/maintenance_visit.json | 6 +++--- .../doctype/maintenance_visit/maintenance_visit.py | 5 +++++ 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.js b/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.js index b3661b7bf38..d08786410c7 100644 --- a/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.js +++ b/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.js @@ -41,11 +41,14 @@ frappe.ui.form.on('Maintenance Visit', { frm.set_value({ status: 'Draft' }); } if (frm.doc.__islocal) { +<<<<<<< HEAD <<<<<<< HEAD frm.doc.maintenance_type == 'Unscheduled' && frm.clear_table("purposes"); ======= frm.clear_table("purposes"); >>>>>>> 4f52b86d7e (refactor: update_serial_no function and code cleanup) +======= +>>>>>>> b98c61f2a7 (refactor: moved purpose table mandatory check to server) frm.set_value({ mntc_date: frappe.datetime.get_today() }); } }, diff --git a/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.json b/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.json index ec32239518f..4a6aa0a34bf 100644 --- a/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.json +++ b/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.json @@ -179,8 +179,7 @@ "label": "Purposes", "oldfieldname": "maintenance_visit_details", "oldfieldtype": "Table", - "options": "Maintenance Visit Purpose", - "reqd": 1 + "options": "Maintenance Visit Purpose" }, { "fieldname": "more_info", @@ -294,10 +293,11 @@ "idx": 1, "is_submittable": 1, "links": [], - "modified": "2021-05-27 16:06:17.352572", + "modified": "2021-12-17 03:10:27.608112", "modified_by": "Administrator", "module": "Maintenance", "name": "Maintenance Visit", + "naming_rule": "By \"Naming Series\" field", "owner": "Administrator", "permissions": [ { diff --git a/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.py b/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.py index 5a87b162af6..d5d87536da5 100644 --- a/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.py +++ b/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.py @@ -18,6 +18,10 @@ class MaintenanceVisit(TransactionBase): if d.serial_no and not frappe.db.exists("Serial No", d.serial_no): frappe.throw(_("Serial No {0} does not exist").format(d.serial_no)) + def validate_purpose_table(self): + if not self.purposes: + 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') @@ -29,6 +33,7 @@ class MaintenanceVisit(TransactionBase): def validate(self): self.validate_serial_no() self.validate_maintenance_date() + self.validate_purpose_table() def update_completion_status(self): if self.maintenance_schedule_detail: From add36363ab4d91f5b821e0ecbcdb032903d6de31 Mon Sep 17 00:00:00 2001 From: Noah Jacob Date: Mon, 17 Jan 2022 18:25:42 +0530 Subject: [PATCH 045/102] fix: Serial No. filters for unscheduled visits (cherry picked from commit dec751377302e504285e834afed6a64dd0f010bf) --- .../maintenance_schedule/maintenance_schedule.py | 2 +- .../maintenance_schedule/test_maintenance_schedule.py | 6 +++--- .../doctype/maintenance_visit/maintenance_visit.js | 11 ++++++++++- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py index 9c35f4f2457..07d928c221f 100644 --- a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py +++ b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py @@ -305,7 +305,7 @@ class MaintenanceSchedule(TransactionBase): return schedule.name @frappe.whitelist() -def update_serial_nos(item_code, schedule=None): +def get_serial_nos_from_schedule(item_code, schedule=None): serial_nos = [] if schedule: serial_nos = frappe.db.get_value('Maintenance Schedule Item', { diff --git a/erpnext/maintenance/doctype/maintenance_schedule/test_maintenance_schedule.py b/erpnext/maintenance/doctype/maintenance_schedule/test_maintenance_schedule.py index 378d3238d75..4d3c3f48f4a 100644 --- a/erpnext/maintenance/doctype/maintenance_schedule/test_maintenance_schedule.py +++ b/erpnext/maintenance/doctype/maintenance_schedule/test_maintenance_schedule.py @@ -7,8 +7,8 @@ import frappe from frappe.utils.data import add_days, formatdate, today from erpnext.maintenance.doctype.maintenance_schedule.maintenance_schedule import ( + get_serial_nos_from_schedule, make_maintenance_visit, - update_serial_nos, ) from erpnext.stock.doctype.item.test_item import create_item from erpnext.stock.doctype.stock_entry.test_stock_entry import make_serialized_item @@ -93,7 +93,7 @@ class TestMaintenanceSchedule(unittest.TestCase): s_item = ms.schedules[0] mv = make_maintenance_visit(source_name=ms.name, item_name=item_code, s_id=s_item.name) mvi = mv.purposes[0] - serial_nos = update_serial_nos(mvi.item_name, ms.name) + serial_nos = get_serial_nos_from_schedule(mvi.item_name, ms.name) self.assertEqual(serial_nos, None) # With serial no. set in schedule -> returns serial nos. @@ -104,7 +104,7 @@ class TestMaintenanceSchedule(unittest.TestCase): s_item = ms.schedules[0] mv = make_maintenance_visit(source_name=ms.name, item_name=item_code, s_id=s_item.name) mvi = mv.purposes[0] - serial_nos = update_serial_nos(mvi.item_name, ms.name) + serial_nos = get_serial_nos_from_schedule(mvi.item_name, ms.name) self.assertEqual(serial_nos, ["TEST001", "TEST002"]) frappe.db.rollback() diff --git a/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.js b/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.js index d08786410c7..746fb765e10 100644 --- a/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.js +++ b/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.js @@ -13,7 +13,7 @@ frappe.ui.form.on('Maintenance Visit', { if (frm.doc.maintenance_type === "Scheduled") { let item_code = frm.doc.purposes[0].item_code; frappe.call({ - method: "erpnext.maintenance.doctype.maintenance_schedule.maintenance_schedule.update_serial_nos", + method: "erpnext.maintenance.doctype.maintenance_schedule.maintenance_schedule.get_serial_nos_from_schedule", args: { schedule: frm.doc.maintenance_schedule, item_code: item_code @@ -36,6 +36,15 @@ frappe.ui.form.on('Maintenance Visit', { }; }); }); + } else { + frm.set_query('serial_no', 'purposes', (frm, cdt, cdn) => { + let row = locals[cdt][cdn]; + return { + filters: { + 'item_code': row.item_code + } + }; + }); } if (!frm.doc.status) { frm.set_value({ status: 'Draft' }); From 3023757a602108e8f006066025a7c918d50e29d5 Mon Sep 17 00:00:00 2001 From: marination Date: Wed, 19 Jan 2022 13:06:52 +0530 Subject: [PATCH 046/102] fix: Conflicts --- .../doctype/maintenance_visit/maintenance_visit.js | 8 -------- erpnext/patches.txt | 4 ---- 2 files changed, 12 deletions(-) diff --git a/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.js b/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.js index 746fb765e10..f4a0d4d399c 100644 --- a/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.js +++ b/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.js @@ -50,14 +50,6 @@ frappe.ui.form.on('Maintenance Visit', { frm.set_value({ status: 'Draft' }); } if (frm.doc.__islocal) { -<<<<<<< HEAD -<<<<<<< HEAD - frm.doc.maintenance_type == 'Unscheduled' && frm.clear_table("purposes"); -======= - frm.clear_table("purposes"); ->>>>>>> 4f52b86d7e (refactor: update_serial_no function and code cleanup) -======= ->>>>>>> b98c61f2a7 (refactor: moved purpose table mandatory check to server) frm.set_value({ mntc_date: frappe.datetime.get_today() }); } }, diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 13f0891b89e..7110a7e308e 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -336,13 +336,9 @@ erpnext.patches.v12_0.update_production_plan_status erpnext.patches.v13_0.item_naming_series_not_mandatory erpnext.patches.v13_0.update_category_in_ltds_certificate erpnext.patches.v13_0.create_ksa_vat_custom_fields -<<<<<<< HEAD erpnext.patches.v13_0.rename_ksa_qr_field erpnext.patches.v13_0.disable_ksa_print_format_for_others # 16-12-2021 erpnext.patches.v13_0.update_tax_category_for_rcm erpnext.patches.v13_0.convert_to_website_item_in_item_card_group_template erpnext.patches.v13_0.agriculture_deprecation_warning -======= erpnext.patches.v13_0.update_maintenance_schedule_field_in_visit -erpnext.patches.v14_0.migrate_crm_settings ->>>>>>> c872aa43f8 (fix(patch): updates maintenance schedule field for old docs) From 44d209853054930301ae7c03c1d470c4710cecef Mon Sep 17 00:00:00 2001 From: Anupam Date: Wed, 19 Jan 2022 13:10:23 +0530 Subject: [PATCH 047/102] 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 93d665b38a840037476366682873820fc4ca221a Mon Sep 17 00:00:00 2001 From: marination Date: Wed, 19 Jan 2022 13:53:18 +0530 Subject: [PATCH 048/102] fix: Patch (reload doc) --- .../patches/v13_0/update_maintenance_schedule_field_in_visit.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erpnext/patches/v13_0/update_maintenance_schedule_field_in_visit.py b/erpnext/patches/v13_0/update_maintenance_schedule_field_in_visit.py index 450c00e4212..43096991943 100644 --- a/erpnext/patches/v13_0/update_maintenance_schedule_field_in_visit.py +++ b/erpnext/patches/v13_0/update_maintenance_schedule_field_in_visit.py @@ -3,6 +3,8 @@ import frappe def execute(): + frappe.reload_doc("maintenance", "doctype", "maintenance_visit") + # Updates the Maintenance Schedule link to fetch serial nos from frappe.query_builder.functions import Coalesce mvp = frappe.qb.DocType('Maintenance Visit Purpose') From f75d36dcb4f96ad9b9b6759fa93ef199b0f95bac Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 19 Jan 2022 19:30:21 +0530 Subject: [PATCH 049/102] fix: from time and to time not updated in drag and drop action #29114 (#29361) fix: from time and to time not updated in drag and drop action (cherry picked from commit 8b5827ed6db1041526b6440ca8e4fde19c646e1e) Co-authored-by: Mohammed Yusuf Shaikh <49878143+mohammedyusufshaikh@users.noreply.github.com> --- erpnext/education/api.py | 4 ++-- .../doctype/course_schedule/course_schedule.py | 12 +++++++++++- .../course_schedule/course_schedule_calendar.js | 7 +++---- .../doctype/course_schedule/test_course_schedule.py | 6 ++++++ 4 files changed, 22 insertions(+), 7 deletions(-) diff --git a/erpnext/education/api.py b/erpnext/education/api.py index d9013b08161..636b948a1cc 100644 --- a/erpnext/education/api.py +++ b/erpnext/education/api.py @@ -201,8 +201,8 @@ def get_course_schedule_events(start, end, filters=None): conditions = get_event_conditions("Course Schedule", filters) data = frappe.db.sql("""select name, course, color, - timestamp(schedule_date, from_time) as from_datetime, - timestamp(schedule_date, to_time) as to_datetime, + timestamp(schedule_date, from_time) as from_time, + timestamp(schedule_date, to_time) as to_time, room, student_group, 0 as 'allDay' from `tabCourse Schedule` where ( schedule_date between %(start)s and %(end)s ) diff --git a/erpnext/education/doctype/course_schedule/course_schedule.py b/erpnext/education/doctype/course_schedule/course_schedule.py index 335b6d28d0c..335cec43527 100644 --- a/erpnext/education/doctype/course_schedule/course_schedule.py +++ b/erpnext/education/doctype/course_schedule/course_schedule.py @@ -3,6 +3,8 @@ # For license information, please see license.txt +from datetime import datetime + import frappe from frappe import _ from frappe.model.document import Document @@ -30,6 +32,14 @@ class CourseSchedule(Document): if self.from_time > self.to_time: frappe.throw(_("From Time cannot be greater than To Time.")) + """Handles specicfic case to update schedule date in calendar """ + if isinstance(self.from_time, str): + try: + datetime_obj = datetime.strptime(self.from_time, '%Y-%m-%d %H:%M:%S') + self.schedule_date = datetime_obj + except ValueError: + pass + def validate_overlap(self): """Validates overlap for Student Group, Instructor, Room""" @@ -47,4 +57,4 @@ class CourseSchedule(Document): validate_overlap_for(self, "Assessment Plan", "student_group") validate_overlap_for(self, "Assessment Plan", "room") - validate_overlap_for(self, "Assessment Plan", "supervisor", self.instructor) + validate_overlap_for(self, "Assessment Plan", "supervisor", self.instructor) \ No newline at end of file diff --git a/erpnext/education/doctype/course_schedule/course_schedule_calendar.js b/erpnext/education/doctype/course_schedule/course_schedule_calendar.js index 803527e5480..cacd539b224 100644 --- a/erpnext/education/doctype/course_schedule/course_schedule_calendar.js +++ b/erpnext/education/doctype/course_schedule/course_schedule_calendar.js @@ -1,11 +1,10 @@ frappe.views.calendar["Course Schedule"] = { field_map: { - // from_datetime and to_datetime don't exist as docfields but are used in onload - "start": "from_datetime", - "end": "to_datetime", + "start": "from_time", + "end": "to_time", "id": "name", "title": "course", - "allDay": "allDay" + "allDay": "allDay", }, gantt: false, order_by: "schedule_date", diff --git a/erpnext/education/doctype/course_schedule/test_course_schedule.py b/erpnext/education/doctype/course_schedule/test_course_schedule.py index a7324195557..56149affcea 100644 --- a/erpnext/education/doctype/course_schedule/test_course_schedule.py +++ b/erpnext/education/doctype/course_schedule/test_course_schedule.py @@ -6,6 +6,7 @@ import unittest import frappe from frappe.utils import to_timedelta, today +from frappe.utils.data import add_to_date from erpnext.education.utils import OverlapError @@ -39,6 +40,11 @@ class TestCourseSchedule(unittest.TestCase): make_course_schedule_test_record(from_time= cs1.from_time, to_time= cs1.to_time, student_group="Course-TC102-2014-2015 (_Test Academic Term)", instructor="_Test Instructor 2", room=frappe.get_all("Room")[1].name) + def test_update_schedule_date(self): + doc = make_course_schedule_test_record(schedule_date= add_to_date(today(), days=1)) + doc.schedule_date = add_to_date(doc.schedule_date, days=1) + doc.save() + def make_course_schedule_test_record(**args): args = frappe._dict(args) From 25398d017bca53f14c7ebb6c9f937ef7c1390093 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Thu, 20 Jan 2022 10:51:59 +0530 Subject: [PATCH 050/102] 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 051/102] 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 052/102] 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 053/102] 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 054/102] 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 055/102] 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 056/102] 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 057/102] 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 058/102] 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 { ? `