From b0aa4a6e1cbf9d108fa51213db8888b67733537f Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Sun, 10 Oct 2021 11:42:39 +0530 Subject: [PATCH 01/12] fix: patch fails if accounts are frozen --- erpnext/patches.txt | 2 +- .../patches/v13_0/modify_invalid_gain_loss_gl_entries.py | 9 ++++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 22a6313994..5ea666f00f 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -299,7 +299,7 @@ erpnext.patches.v13_0.gst_fields_for_pos_invoice erpnext.patches.v13_0.create_accounting_dimensions_in_pos_doctypes erpnext.patches.v13_0.trim_sales_invoice_custom_field_length erpnext.patches.v13_0.create_custom_field_for_finance_book -erpnext.patches.v13_0.modify_invalid_gain_loss_gl_entries +erpnext.patches.v13_0.modify_invalid_gain_loss_gl_entries #2 erpnext.patches.v13_0.fix_additional_cost_in_mfg_stock_entry erpnext.patches.v13_0.set_status_in_maintenance_schedule_table erpnext.patches.v13_0.add_default_interview_notification_templates \ No newline at end of file diff --git a/erpnext/patches/v13_0/modify_invalid_gain_loss_gl_entries.py b/erpnext/patches/v13_0/modify_invalid_gain_loss_gl_entries.py index fa8a86437d..c2902ce9ef 100644 --- a/erpnext/patches/v13_0/modify_invalid_gain_loss_gl_entries.py +++ b/erpnext/patches/v13_0/modify_invalid_gain_loss_gl_entries.py @@ -17,7 +17,7 @@ def execute(): where ref_exchange_rate = 1 and docstatus = 1 - and ifnull(exchange_gain_loss, '') != '' + and ifnull(exchange_gain_loss, 0) != 0 group by parent """, as_dict=1) @@ -30,7 +30,7 @@ def execute(): where ref_exchange_rate = 1 and docstatus = 1 - and ifnull(exchange_gain_loss, '') != '' + and ifnull(exchange_gain_loss, 0) != 0 group by parent """, as_dict=1) @@ -38,6 +38,8 @@ def execute(): if purchase_invoices + sales_invoices: frappe.log_error(json.dumps(purchase_invoices + sales_invoices, indent=2), title="Patch Log") + acc_frozen_upto = frappe.db.get_value('Accounts Settings', None, 'acc_frozen_upto') + frappe.db.set_value('Accounts Settings', None, 'acc_frozen_upto', None) for invoice in purchase_invoices + sales_invoices: doc = frappe.get_doc(invoice.type, invoice.name) doc.docstatus = 2 @@ -46,4 +48,5 @@ def execute(): if advance.ref_exchange_rate == 1: advance.db_set('exchange_gain_loss', 0, False) doc.docstatus = 1 - doc.make_gl_entries() \ No newline at end of file + doc.make_gl_entries() + frappe.db.set_value('Accounts Settings', None, 'acc_frozen_upto', acc_frozen_upto) \ No newline at end of file From 353ad5f6ffa7e4aaa472f3882d6ef19faa9d3431 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Sun, 10 Oct 2021 11:47:48 +0530 Subject: [PATCH 02/12] feat: handle exceptions --- .../modify_invalid_gain_loss_gl_entries.py | 27 ++++++++++++------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/erpnext/patches/v13_0/modify_invalid_gain_loss_gl_entries.py b/erpnext/patches/v13_0/modify_invalid_gain_loss_gl_entries.py index c2902ce9ef..ddf70aa814 100644 --- a/erpnext/patches/v13_0/modify_invalid_gain_loss_gl_entries.py +++ b/erpnext/patches/v13_0/modify_invalid_gain_loss_gl_entries.py @@ -39,14 +39,21 @@ def execute(): frappe.log_error(json.dumps(purchase_invoices + sales_invoices, indent=2), title="Patch Log") acc_frozen_upto = frappe.db.get_value('Accounts Settings', None, 'acc_frozen_upto') - frappe.db.set_value('Accounts Settings', None, 'acc_frozen_upto', None) + if acc_frozen_upto: + frappe.db.set_value('Accounts Settings', None, 'acc_frozen_upto', None) + for invoice in purchase_invoices + sales_invoices: - doc = frappe.get_doc(invoice.type, invoice.name) - doc.docstatus = 2 - doc.make_gl_entries() - for advance in doc.advances: - if advance.ref_exchange_rate == 1: - advance.db_set('exchange_gain_loss', 0, False) - doc.docstatus = 1 - doc.make_gl_entries() - frappe.db.set_value('Accounts Settings', None, 'acc_frozen_upto', acc_frozen_upto) \ No newline at end of file + try: + doc = frappe.get_doc(invoice.type, invoice.name) + doc.docstatus = 2 + doc.make_gl_entries() + for advance in doc.advances: + if advance.ref_exchange_rate == 1: + advance.db_set('exchange_gain_loss', 0, False) + doc.docstatus = 1 + doc.make_gl_entries() + except Exception: + print(f'Failed to correct gl entries of {invoice.name}') + + if acc_frozen_upto: + frappe.db.set_value('Accounts Settings', None, 'acc_frozen_upto', acc_frozen_upto) \ No newline at end of file From 3337ae120cdf722c157b2048106399e2f4ca0b14 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 11 Oct 2021 13:12:02 +0530 Subject: [PATCH 03/12] fix: Status check for closed loans --- .../doctype/loan_disbursement/loan_disbursement.py | 5 ++++- .../loan_management/doctype/loan_repayment/loan_repayment.py | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.py b/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.py index 6d9d4f490d..99f0d25924 100644 --- a/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.py +++ b/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.py @@ -198,7 +198,7 @@ def get_disbursal_amount(loan, on_current_security_price=0): security_value = get_total_pledged_security_value(loan) if loan_details.is_secured_loan and not on_current_security_price: - security_value = flt(loan_details.maximum_loan_amount) + security_value = get_maximum_amount_as_per_pledged_security(loan) if not security_value and not loan_details.is_secured_loan: security_value = flt(loan_details.loan_amount) @@ -209,3 +209,6 @@ def get_disbursal_amount(loan, on_current_security_price=0): disbursal_amount = loan_details.loan_amount - loan_details.disbursed_amount return disbursal_amount + +def get_maximum_amount_as_per_pledged_security(loan): + return flt(frappe.db.get_value('Loan Security Pledge', {'loan': loan}, 'sum(maximum_loan_value)')) diff --git a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py index 13b7357327..40bb581165 100644 --- a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py +++ b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py @@ -411,7 +411,7 @@ def get_amounts(amounts, against_loan, posting_date): if due_date and not final_due_date: final_due_date = add_days(due_date, loan_type_details.grace_period_in_days) - if against_loan_doc.status in ('Disbursed', 'Loan Closure Requested', 'Closed'): + if against_loan_doc.status in ('Disbursed', 'Closed') or against_loan_doc.disbursed_amount >= against_loan_doc.loan_amount: pending_principal_amount = against_loan_doc.total_payment - against_loan_doc.total_principal_paid \ - against_loan_doc.total_interest_payable - against_loan_doc.written_off_amount else: From d824a90fac68eb76d3f824249e83466201e96b02 Mon Sep 17 00:00:00 2001 From: marination Date: Mon, 11 Oct 2021 23:14:28 +0530 Subject: [PATCH 04/12] fix: Avoid automatic customer creation on website user login --- erpnext/shopping_cart/utils.py | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/erpnext/shopping_cart/utils.py b/erpnext/shopping_cart/utils.py index f412e61f06..6e32b61dce 100644 --- a/erpnext/shopping_cart/utils.py +++ b/erpnext/shopping_cart/utils.py @@ -1,8 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals - import frappe from erpnext.shopping_cart.doctype.shopping_cart_settings.shopping_cart_settings import ( @@ -18,10 +15,17 @@ def show_cart_count(): return False def set_cart_count(login_manager): - role, parties = check_customer_or_supplier() - if role == 'Supplier': return + # since this is run only on hooks login event + # make sure user is already a customer + # before trying to set cart count + user_is_customer = is_customer() + if not user_is_customer: return + if show_cart_count(): from erpnext.shopping_cart.cart import set_cart_count + # set_cart_count will try to fetch existing cart quotation + # or create one if non existent (and create a customer too) + # cart count is calculated from this quotation's items set_cart_count() def clear_cart_count(login_manager): @@ -32,13 +36,13 @@ def update_website_context(context): cart_enabled = is_cart_enabled() context["shopping_cart_enabled"] = cart_enabled -def check_customer_or_supplier(): - if frappe.session.user: +def is_customer(): + if frappe.session.user and frappe.session.user != "Guest": contact_name = frappe.get_value("Contact", {"email_id": frappe.session.user}) if contact_name: contact = frappe.get_doc('Contact', contact_name) for link in contact.links: - if link.link_doctype in ('Customer', 'Supplier'): - return link.link_doctype, link.link_name + if link.link_doctype == 'Customer': + return True - return 'Customer', None + return False From 8355af6dcf6a6e770c354133e1f08012ad9bdfc9 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 12 Oct 2021 12:44:40 +0530 Subject: [PATCH 05/12] fix: Incorrect maximum loan amount update --- .../loan_management/doctype/loan/loan.json | 3 +-- erpnext/loan_management/doctype/loan/loan.py | 25 ++++++++++++------- .../loan_application/loan_application.py | 7 +++--- 3 files changed, 21 insertions(+), 14 deletions(-) diff --git a/erpnext/loan_management/doctype/loan/loan.json b/erpnext/loan_management/doctype/loan/loan.json index c9f23ca4df..5979992bbe 100644 --- a/erpnext/loan_management/doctype/loan/loan.json +++ b/erpnext/loan_management/doctype/loan/loan.json @@ -334,7 +334,6 @@ }, { "depends_on": "eval:doc.is_secured_loan", - "fetch_from": "loan_application.maximum_loan_amount", "fieldname": "maximum_loan_amount", "fieldtype": "Currency", "label": "Maximum Loan Amount", @@ -360,7 +359,7 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2021-04-19 18:10:32.360818", + "modified": "2021-10-12 18:10:32.360818", "modified_by": "Administrator", "module": "Loan Management", "name": "Loan", diff --git a/erpnext/loan_management/doctype/loan/loan.py b/erpnext/loan_management/doctype/loan/loan.py index 7dbd42297e..0f2c3cfdfc 100644 --- a/erpnext/loan_management/doctype/loan/loan.py +++ b/erpnext/loan_management/doctype/loan/loan.py @@ -137,16 +137,23 @@ class Loan(AccountsController): frappe.throw(_("Loan amount is mandatory")) def link_loan_security_pledge(self): - if self.is_secured_loan: - loan_security_pledge = frappe.db.get_value('Loan Security Pledge', {'loan_application': self.loan_application}, - 'name') + if self.is_secured_loan and self.loan_application: + maximum_loan_value = frappe.db.get_value('Loan Security Pledge', + { + 'loan_application': self.loan_application, + 'status': 'Requested' + }, + 'sum(maximum_loan_value)' + ) - if loan_security_pledge: - frappe.db.set_value('Loan Security Pledge', loan_security_pledge, { - 'loan': self.name, - 'status': 'Pledged', - 'pledge_time': now_datetime() - }) + if maximum_loan_value: + frappe.db.sql(""" + UPDATE `tabLoan Security Pledge` + SET loan = %s, pledge_time = %s, status = 'Pledged' + WHERE status = 'Requested' and loan_application = %s + """, (self.name, now_datetime(), self.loan_application)) + + self.db_set('maximum_loan_amount', maximum_loan_value) def unlink_loan_security_pledge(self): pledges = frappe.get_all('Loan Security Pledge', fields=['name'], filters={'loan': self.name}) diff --git a/erpnext/loan_management/doctype/loan_application/loan_application.py b/erpnext/loan_management/doctype/loan_application/loan_application.py index e492920abb..66a3be3513 100644 --- a/erpnext/loan_management/doctype/loan_application/loan_application.py +++ b/erpnext/loan_management/doctype/loan_application/loan_application.py @@ -130,10 +130,11 @@ class LoanApplication(Document): def create_loan(source_name, target_doc=None, submit=0): def update_accounts(source_doc, target_doc, source_parent): account_details = frappe.get_all("Loan Type", - fields=["mode_of_payment", "payment_account","loan_account", "interest_income_account", "penalty_income_account"], - filters = {'name': source_doc.loan_type} - )[0] + fields=["mode_of_payment", "payment_account","loan_account", "interest_income_account", "penalty_income_account"], + filters = {'name': source_doc.loan_type})[0] + if source_doc.is_secured_loan: + target_doc.maximum_loan_amount = 0 target_doc.mode_of_payment = account_details.mode_of_payment target_doc.payment_account = account_details.payment_account From af14ba43de755104bfab67b9e72ba838f30e355a Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 12 Oct 2021 12:58:42 +0530 Subject: [PATCH 06/12] fix: Linting issues --- .../doctype/loan_application/loan_application.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/loan_management/doctype/loan_application/loan_application.py b/erpnext/loan_management/doctype/loan_application/loan_application.py index 66a3be3513..ede0467b0e 100644 --- a/erpnext/loan_management/doctype/loan_application/loan_application.py +++ b/erpnext/loan_management/doctype/loan_application/loan_application.py @@ -131,7 +131,7 @@ def create_loan(source_name, target_doc=None, submit=0): def update_accounts(source_doc, target_doc, source_parent): account_details = frappe.get_all("Loan Type", fields=["mode_of_payment", "payment_account","loan_account", "interest_income_account", "penalty_income_account"], - filters = {'name': source_doc.loan_type})[0] + filters = {'name': source_doc.loan_type})[0] if source_doc.is_secured_loan: target_doc.maximum_loan_amount = 0 From c103f72faddf9b7afd9d58d44703e98c6e687605 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Tue, 12 Oct 2021 13:13:48 +0530 Subject: [PATCH 07/12] fix: rollback on exception --- erpnext/patches/v13_0/modify_invalid_gain_loss_gl_entries.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erpnext/patches/v13_0/modify_invalid_gain_loss_gl_entries.py b/erpnext/patches/v13_0/modify_invalid_gain_loss_gl_entries.py index ddf70aa814..3af7dac342 100644 --- a/erpnext/patches/v13_0/modify_invalid_gain_loss_gl_entries.py +++ b/erpnext/patches/v13_0/modify_invalid_gain_loss_gl_entries.py @@ -52,7 +52,9 @@ def execute(): advance.db_set('exchange_gain_loss', 0, False) doc.docstatus = 1 doc.make_gl_entries() + frappe.db.commit() except Exception: + frappe.db.rollback() print(f'Failed to correct gl entries of {invoice.name}') if acc_frozen_upto: From a780f78f38b247e5679053e15ee5113f9065a68e Mon Sep 17 00:00:00 2001 From: marination Date: Tue, 12 Oct 2021 13:18:28 +0530 Subject: [PATCH 08/12] fix: Sider, Linter - Moved return to next line - Space between function import and body --- erpnext/shopping_cart/utils.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/erpnext/shopping_cart/utils.py b/erpnext/shopping_cart/utils.py index 6e32b61dce..5f0c792381 100644 --- a/erpnext/shopping_cart/utils.py +++ b/erpnext/shopping_cart/utils.py @@ -19,10 +19,12 @@ def set_cart_count(login_manager): # make sure user is already a customer # before trying to set cart count user_is_customer = is_customer() - if not user_is_customer: return + if not user_is_customer: + return if show_cart_count(): from erpnext.shopping_cart.cart import set_cart_count + # set_cart_count will try to fetch existing cart quotation # or create one if non existent (and create a customer too) # cart count is calculated from this quotation's items From 06fa35a9c132c387bdb99d7c6340d0dbe16ad5fe Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Tue, 14 Sep 2021 20:05:16 +0530 Subject: [PATCH 09/12] test: add custom TestCase class and use in stock --- erpnext/stock/doctype/batch/test_batch.py | 6 ++---- .../doctype/delivery_note/test_delivery_note.py | 4 ++-- .../doctype/delivery_trip/test_delivery_trip.py | 6 ++++-- erpnext/stock/doctype/item/test_item.py | 6 +++--- .../item_alternative/test_item_alternative.py | 5 +++-- .../doctype/item_attribute/test_item_attribute.py | 6 +++--- .../stock/doctype/item_price/test_item_price.py | 6 +++--- .../test_landed_cost_voucher.py | 5 ++--- .../material_request/test_material_request.py | 5 ++--- .../doctype/packing_slip/test_packing_slip.py | 2 ++ erpnext/stock/doctype/pick_list/test_pick_list.py | 5 ++--- .../purchase_receipt/test_purchase_receipt.py | 3 ++- .../doctype/putaway_rule/test_putaway_rule.py | 5 ++--- .../quality_inspection/test_quality_inspection.py | 6 +++--- erpnext/stock/doctype/serial_no/test_serial_no.py | 5 ++--- erpnext/stock/doctype/shipment/test_shipment.py | 4 ++-- .../stock_ledger_entry/test_stock_ledger_entry.py | 5 ++--- .../test_stock_reconciliation.py | 6 ++---- .../doctype/stock_settings/test_stock_settings.py | 5 ++++- erpnext/stock/doctype/warehouse/test_warehouse.py | 6 +++--- .../stock_analytics/test_stock_analytics.py | 3 ++- erpnext/tests/utils.py | 15 +++++++++++++++ 22 files changed, 67 insertions(+), 52 deletions(-) diff --git a/erpnext/stock/doctype/batch/test_batch.py b/erpnext/stock/doctype/batch/test_batch.py index 79989307ef..0a663c2a18 100644 --- a/erpnext/stock/doctype/batch/test_batch.py +++ b/erpnext/stock/doctype/batch/test_batch.py @@ -1,8 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals - -import unittest import frappe from frappe.exceptions import ValidationError @@ -11,9 +8,10 @@ from frappe.utils import cint, flt from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice from erpnext.stock.doctype.batch.batch import UnableToSelectBatchError, get_batch_no, get_batch_qty from erpnext.stock.get_item_details import get_item_details +from erpnext.tests.utils import ERPNextTestCase -class TestBatch(unittest.TestCase): +class TestBatch(ERPNextTestCase): def test_item_has_batch_enabled(self): self.assertRaises(ValidationError, frappe.get_doc({ "doctype": "Batch", diff --git a/erpnext/stock/doctype/delivery_note/test_delivery_note.py b/erpnext/stock/doctype/delivery_note/test_delivery_note.py index 7fda94b269..f58b586ab2 100644 --- a/erpnext/stock/doctype/delivery_note/test_delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/test_delivery_note.py @@ -5,7 +5,6 @@ from __future__ import unicode_literals import json -import unittest import frappe from frappe.utils import cstr, flt, nowdate, nowtime @@ -37,9 +36,10 @@ from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import ) from erpnext.stock.doctype.warehouse.test_warehouse import get_warehouse from erpnext.stock.stock_ledger import get_previous_sle +from erpnext.tests.utils import ERPNextTestCase -class TestDeliveryNote(unittest.TestCase): +class TestDeliveryNote(ERPNextTestCase): def test_over_billing_against_dn(self): frappe.db.set_value("Stock Settings", None, "allow_negative_stock", 1) diff --git a/erpnext/stock/doctype/delivery_trip/test_delivery_trip.py b/erpnext/stock/doctype/delivery_trip/test_delivery_trip.py index c9081c908f..c6ff73e633 100644 --- a/erpnext/stock/doctype/delivery_trip/test_delivery_trip.py +++ b/erpnext/stock/doctype/delivery_trip/test_delivery_trip.py @@ -14,11 +14,12 @@ from erpnext.stock.doctype.delivery_trip.delivery_trip import ( make_expense_claim, notify_customers, ) -from erpnext.tests.utils import create_test_contact_and_address +from erpnext.tests.utils import ERPNextTestCase, create_test_contact_and_address -class TestDeliveryTrip(unittest.TestCase): +class TestDeliveryTrip(ERPNextTestCase): def setUp(self): + super().setUp() driver = create_driver() create_vehicle() create_delivery_notification() @@ -32,6 +33,7 @@ class TestDeliveryTrip(unittest.TestCase): frappe.db.sql("delete from `tabVehicle`") frappe.db.sql("delete from `tabEmail Template`") frappe.db.sql("delete from `tabDelivery Trip`") + return super().tearDown() def test_expense_claim_fields_are_fetched_properly(self): expense_claim = make_expense_claim(self.delivery_trip.name) diff --git a/erpnext/stock/doctype/item/test_item.py b/erpnext/stock/doctype/item/test_item.py index e911d35db3..9198272513 100644 --- a/erpnext/stock/doctype/item/test_item.py +++ b/erpnext/stock/doctype/item/test_item.py @@ -4,7 +4,6 @@ from __future__ import unicode_literals import json -import unittest import frappe from frappe.test_runner import make_test_objects @@ -25,7 +24,7 @@ from erpnext.stock.doctype.item.item import ( ) from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry from erpnext.stock.get_item_details import get_item_details -from erpnext.tests.utils import change_settings +from erpnext.tests.utils import ERPNextTestCase, change_settings test_ignore = ["BOM"] test_dependencies = ["Warehouse", "Item Group", "Item Tax Template", "Brand", "Item Attribute"] @@ -53,8 +52,9 @@ def make_item(item_code, properties=None): return item -class TestItem(unittest.TestCase): +class TestItem(ERPNextTestCase): def setUp(self): + super().setUp() frappe.flags.attribute_values = None def get_item(self, idx): diff --git a/erpnext/stock/doctype/item_alternative/test_item_alternative.py b/erpnext/stock/doctype/item_alternative/test_item_alternative.py index 2be8ef740a..af6cc472e3 100644 --- a/erpnext/stock/doctype/item_alternative/test_item_alternative.py +++ b/erpnext/stock/doctype/item_alternative/test_item_alternative.py @@ -4,7 +4,6 @@ from __future__ import unicode_literals import json -import unittest import frappe from frappe.utils import flt @@ -21,10 +20,12 @@ from erpnext.stock.doctype.item.test_item import create_item from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import ( create_stock_reconciliation, ) +from erpnext.tests.utils import ERPNextTestCase -class TestItemAlternative(unittest.TestCase): +class TestItemAlternative(ERPNextTestCase): def setUp(self): + super().setUp() make_items() def test_alternative_item_for_subcontract_rm(self): diff --git a/erpnext/stock/doctype/item_attribute/test_item_attribute.py b/erpnext/stock/doctype/item_attribute/test_item_attribute.py index fc809f443e..2cd711bbb1 100644 --- a/erpnext/stock/doctype/item_attribute/test_item_attribute.py +++ b/erpnext/stock/doctype/item_attribute/test_item_attribute.py @@ -3,17 +3,17 @@ from __future__ import unicode_literals -import unittest - import frappe test_records = frappe.get_test_records('Item Attribute') from erpnext.stock.doctype.item_attribute.item_attribute import ItemAttributeIncrementError +from erpnext.tests.utils import ERPNextTestCase -class TestItemAttribute(unittest.TestCase): +class TestItemAttribute(ERPNextTestCase): def setUp(self): + super().setUp() if frappe.db.exists("Item Attribute", "_Test_Length"): frappe.delete_doc("Item Attribute", "_Test_Length") diff --git a/erpnext/stock/doctype/item_price/test_item_price.py b/erpnext/stock/doctype/item_price/test_item_price.py index 5ed8092166..3a51fbbe17 100644 --- a/erpnext/stock/doctype/item_price/test_item_price.py +++ b/erpnext/stock/doctype/item_price/test_item_price.py @@ -3,17 +3,17 @@ from __future__ import unicode_literals -import unittest - import frappe from frappe.test_runner import make_test_records_for_doctype from erpnext.stock.doctype.item_price.item_price import ItemPriceDuplicateItem from erpnext.stock.get_item_details import get_price_list_rate_for, process_args +from erpnext.tests.utils import ERPNextTestCase -class TestItemPrice(unittest.TestCase): +class TestItemPrice(ERPNextTestCase): def setUp(self): + super().setUp() frappe.db.sql("delete from `tabItem Price`") make_test_records_for_doctype("Item Price", force=True) diff --git a/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py b/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py index 58a72f72dd..339eaaaf7a 100644 --- a/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py +++ b/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py @@ -4,8 +4,6 @@ from __future__ import unicode_literals -import unittest - import frappe from frappe.utils import flt @@ -16,9 +14,10 @@ from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import ( get_gl_entries, make_purchase_receipt, ) +from erpnext.tests.utils import ERPNextTestCase -class TestLandedCostVoucher(unittest.TestCase): +class TestLandedCostVoucher(ERPNextTestCase): def test_landed_cost_voucher(self): frappe.db.set_value("Buying Settings", None, "allow_multiple_items", 1) diff --git a/erpnext/stock/doctype/material_request/test_material_request.py b/erpnext/stock/doctype/material_request/test_material_request.py index 5c2ac2584f..f66a228e35 100644 --- a/erpnext/stock/doctype/material_request/test_material_request.py +++ b/erpnext/stock/doctype/material_request/test_material_request.py @@ -6,8 +6,6 @@ from __future__ import unicode_literals -import unittest - import frappe from frappe.utils import flt, today @@ -18,9 +16,10 @@ from erpnext.stock.doctype.material_request.material_request import ( make_supplier_quotation, raise_work_orders, ) +from erpnext.tests.utils import ERPNextTestCase -class TestMaterialRequest(unittest.TestCase): +class TestMaterialRequest(ERPNextTestCase): def test_make_purchase_order(self): mr = frappe.copy_doc(test_records[0]).insert() diff --git a/erpnext/stock/doctype/packing_slip/test_packing_slip.py b/erpnext/stock/doctype/packing_slip/test_packing_slip.py index 193adfcf1c..c70cba67f2 100644 --- a/erpnext/stock/doctype/packing_slip/test_packing_slip.py +++ b/erpnext/stock/doctype/packing_slip/test_packing_slip.py @@ -6,6 +6,8 @@ from __future__ import unicode_literals import unittest # test_records = frappe.get_test_records('Packing Slip') +from erpnext.tests.utils import ERPNextTestCase + class TestPackingSlip(unittest.TestCase): pass diff --git a/erpnext/stock/doctype/pick_list/test_pick_list.py b/erpnext/stock/doctype/pick_list/test_pick_list.py index aa710ad0e9..fd0b3680df 100644 --- a/erpnext/stock/doctype/pick_list/test_pick_list.py +++ b/erpnext/stock/doctype/pick_list/test_pick_list.py @@ -3,8 +3,6 @@ # See license.txt from __future__ import unicode_literals -import unittest - import frappe test_dependencies = ['Item', 'Sales Invoice', 'Stock Entry', 'Batch'] @@ -15,9 +13,10 @@ from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_pu from erpnext.stock.doctype.stock_reconciliation.stock_reconciliation import ( EmptyStockReconciliationItemsError, ) +from erpnext.tests.utils import ERPNextTestCase -class TestPickList(unittest.TestCase): +class TestPickList(ERPNextTestCase): def test_pick_list_picks_warehouse_for_each_item(self): try: diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index 044856cca9..de17744428 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -17,9 +17,10 @@ from erpnext.stock.doctype.purchase_receipt.purchase_receipt import make_purchas from erpnext.stock.doctype.serial_no.serial_no import SerialNoDuplicateError, get_serial_nos from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse from erpnext.stock.stock_ledger import SerialNoExistsInFutureTransaction +from erpnext.tests.utils import ERPNextTestCase -class TestPurchaseReceipt(unittest.TestCase): +class TestPurchaseReceipt(ERPNextTestCase): def setUp(self): frappe.db.set_value("Buying Settings", None, "allow_multiple_items", 1) diff --git a/erpnext/stock/doctype/putaway_rule/test_putaway_rule.py b/erpnext/stock/doctype/putaway_rule/test_putaway_rule.py index 0aa7610575..c25bca94db 100644 --- a/erpnext/stock/doctype/putaway_rule/test_putaway_rule.py +++ b/erpnext/stock/doctype/putaway_rule/test_putaway_rule.py @@ -3,8 +3,6 @@ # See license.txt from __future__ import unicode_literals -import unittest - import frappe from erpnext.stock.doctype.batch.test_batch import make_new_batch @@ -13,9 +11,10 @@ from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_pu from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse from erpnext.stock.get_item_details import get_conversion_factor +from erpnext.tests.utils import ERPNextTestCase -class TestPutawayRule(unittest.TestCase): +class TestPutawayRule(ERPNextTestCase): def setUp(self): if not frappe.db.exists("Item", "_Rice"): make_item("_Rice", { diff --git a/erpnext/stock/doctype/quality_inspection/test_quality_inspection.py b/erpnext/stock/doctype/quality_inspection/test_quality_inspection.py index f5d076a077..308c62875d 100644 --- a/erpnext/stock/doctype/quality_inspection/test_quality_inspection.py +++ b/erpnext/stock/doctype/quality_inspection/test_quality_inspection.py @@ -1,8 +1,6 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors and Contributors # See license.txt -import unittest - import frappe from frappe.utils import nowdate @@ -15,12 +13,14 @@ from erpnext.controllers.stock_controller import ( from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note from erpnext.stock.doctype.item.test_item import create_item from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry +from erpnext.tests.utils import ERPNextTestCase # test_records = frappe.get_test_records('Quality Inspection') -class TestQualityInspection(unittest.TestCase): +class TestQualityInspection(ERPNextTestCase): def setUp(self): + super().setUp() create_item("_Test Item with QA") frappe.db.set_value( "Item", "_Test Item with QA", "inspection_required_before_delivery", 1 diff --git a/erpnext/stock/doctype/serial_no/test_serial_no.py b/erpnext/stock/doctype/serial_no/test_serial_no.py index 818c163c68..546e21bde0 100644 --- a/erpnext/stock/doctype/serial_no/test_serial_no.py +++ b/erpnext/stock/doctype/serial_no/test_serial_no.py @@ -6,8 +6,6 @@ from __future__ import unicode_literals -import unittest - import frappe from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note @@ -20,9 +18,10 @@ test_dependencies = ["Item"] test_records = frappe.get_test_records('Serial No') from erpnext.stock.doctype.serial_no.serial_no import * +from erpnext.tests.utils import ERPNextTestCase -class TestSerialNo(unittest.TestCase): +class TestSerialNo(ERPNextTestCase): def test_cannot_create_direct(self): frappe.delete_doc_if_exists("Serial No", "_TCSER0001") diff --git a/erpnext/stock/doctype/shipment/test_shipment.py b/erpnext/stock/doctype/shipment/test_shipment.py index 9914cf8015..288d874800 100644 --- a/erpnext/stock/doctype/shipment/test_shipment.py +++ b/erpnext/stock/doctype/shipment/test_shipment.py @@ -3,15 +3,15 @@ # See license.txt from __future__ import unicode_literals -import unittest from datetime import date, timedelta import frappe from erpnext.stock.doctype.delivery_note.delivery_note import make_shipment +from erpnext.tests.utils import ERPNextTestCase -class TestShipment(unittest.TestCase): +class TestShipment(ERPNextTestCase): def test_shipment_from_delivery_note(self): delivery_note = create_test_delivery_note() delivery_note.submit() diff --git a/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py b/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py index 61bae49b0b..ff33c2789b 100644 --- a/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py +++ b/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py @@ -3,8 +3,6 @@ # See license.txt from __future__ import unicode_literals -import unittest - import frappe from frappe.core.page.permission_manager.permission_manager import reset from frappe.utils import add_days, today @@ -21,9 +19,10 @@ from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import create_stock_reconciliation, ) from erpnext.stock.stock_ledger import get_previous_sle +from erpnext.tests.utils import ERPNextTestCase -class TestStockLedgerEntry(unittest.TestCase): +class TestStockLedgerEntry(ERPNextTestCase): def setUp(self): items = create_items() reset('Stock Entry') diff --git a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py index 8647bee40e..b0a15379e6 100644 --- a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py @@ -6,8 +6,6 @@ from __future__ import unicode_literals -import unittest - import frappe from frappe.utils import add_days, flt, nowdate, nowtime, random_string @@ -22,10 +20,10 @@ from erpnext.stock.doctype.stock_reconciliation.stock_reconciliation import ( from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse from erpnext.stock.stock_ledger import get_previous_sle, update_entries_after from erpnext.stock.utils import get_incoming_rate, get_stock_value_on, get_valuation_method -from erpnext.tests.utils import change_settings +from erpnext.tests.utils import ERPNextTestCase, change_settings -class TestStockReconciliation(unittest.TestCase): +class TestStockReconciliation(ERPNextTestCase): @classmethod def setUpClass(self): create_batch_or_serial_no_items() diff --git a/erpnext/stock/doctype/stock_settings/test_stock_settings.py b/erpnext/stock/doctype/stock_settings/test_stock_settings.py index 7e8090499f..bf8ac5dc79 100644 --- a/erpnext/stock/doctype/stock_settings/test_stock_settings.py +++ b/erpnext/stock/doctype/stock_settings/test_stock_settings.py @@ -7,9 +7,12 @@ import unittest import frappe +from erpnext.tests.utils import ERPNextTestCase -class TestStockSettings(unittest.TestCase): + +class TestStockSettings(ERPNextTestCase): def setUp(self): + super().setUp() frappe.db.set_value("Stock Settings", None, "clean_description_html", 0) def test_settings(self): diff --git a/erpnext/stock/doctype/warehouse/test_warehouse.py b/erpnext/stock/doctype/warehouse/test_warehouse.py index 1ca7181f27..98317ec9c5 100644 --- a/erpnext/stock/doctype/warehouse/test_warehouse.py +++ b/erpnext/stock/doctype/warehouse/test_warehouse.py @@ -2,8 +2,6 @@ # License: GNU General Public License v3. See license.txt from __future__ import unicode_literals -import unittest - import frappe from frappe.test_runner import make_test_records from frappe.utils import cint @@ -12,11 +10,13 @@ import erpnext from erpnext.accounts.doctype.account.test_account import create_account, get_inventory_account from erpnext.stock.doctype.item.test_item import create_item from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry +from erpnext.tests.utils import ERPNextTestCase test_records = frappe.get_test_records('Warehouse') -class TestWarehouse(unittest.TestCase): +class TestWarehouse(ERPNextTestCase): def setUp(self): + super().setUp() if not frappe.get_value('Item', '_Test Item'): make_test_records('Item') diff --git a/erpnext/stock/report/stock_analytics/test_stock_analytics.py b/erpnext/stock/report/stock_analytics/test_stock_analytics.py index 21e1205bfc..32df585937 100644 --- a/erpnext/stock/report/stock_analytics/test_stock_analytics.py +++ b/erpnext/stock/report/stock_analytics/test_stock_analytics.py @@ -5,9 +5,10 @@ from frappe import _dict from erpnext.accounts.utils import get_fiscal_year from erpnext.stock.report.stock_analytics.stock_analytics import get_period_date_ranges +from erpnext.tests.utils import ERPNextTestCase -class TestStockAnalyticsReport(unittest.TestCase): +class TestStockAnalyticsReport(ERPNextTestCase): def test_get_period_date_ranges(self): filters = _dict(range="Monthly", from_date="2020-12-28", to_date="2021-02-06") diff --git a/erpnext/tests/utils.py b/erpnext/tests/utils.py index a3cab4b59d..95e56683e1 100644 --- a/erpnext/tests/utils.py +++ b/erpnext/tests/utils.py @@ -2,6 +2,7 @@ # License: GNU General Public License v3. See license.txt import copy +import unittest from contextlib import contextmanager from typing import Any, Dict, NewType, Optional @@ -12,6 +13,20 @@ ReportFilters = Dict[str, Any] ReportName = NewType("ReportName", str) +class ERPNextTestCase(unittest.TestCase): + """A sane default test class for ERPNext tests.""" + + def setUp(self) -> None: + frappe.db.commit() + return super().setUp() + + + def tearDown(self) -> None: + frappe.db.rollback() + return super().tearDown() + + + def create_test_contact_and_address(): frappe.db.sql('delete from tabContact') frappe.db.sql('delete from `tabContact Email`') From acdb26a4bb45787fad3ee56366cd7e6100c85c41 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Tue, 12 Oct 2021 14:45:29 +0530 Subject: [PATCH 10/12] refactor: rollback after full test --- .../test_stock_reconciliation.py | 1 + erpnext/tests/utils.py | 13 +++++++------ 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py index b0a15379e6..e82401d073 100644 --- a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py @@ -26,6 +26,7 @@ from erpnext.tests.utils import ERPNextTestCase, change_settings class TestStockReconciliation(ERPNextTestCase): @classmethod def setUpClass(self): + super().setUpClass() create_batch_or_serial_no_items() frappe.db.set_value("Stock Settings", None, "allow_negative_stock", 1) diff --git a/erpnext/tests/utils.py b/erpnext/tests/utils.py index 95e56683e1..91df5480e3 100644 --- a/erpnext/tests/utils.py +++ b/erpnext/tests/utils.py @@ -16,15 +16,16 @@ ReportName = NewType("ReportName", str) class ERPNextTestCase(unittest.TestCase): """A sane default test class for ERPNext tests.""" - def setUp(self) -> None: + + @classmethod + def setUpClass(cls) -> None: frappe.db.commit() - return super().setUp() + return super().setUpClass() - - def tearDown(self) -> None: + @classmethod + def tearDownClass(cls) -> None: frappe.db.rollback() - return super().tearDown() - + return super().tearDownClass() def create_test_contact_and_address(): From 8d69ec72a64eae0908aa0fb80a7325545db9a0cf Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Tue, 12 Oct 2021 15:26:37 +0530 Subject: [PATCH 11/12] fix: remove transaction commit from tests --- erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py | 2 -- .../report/procurement_tracker/test_procurement_tracker.py | 1 - .../hr/doctype/daily_work_summary/test_daily_work_summary.py | 1 - erpnext/selling/doctype/sales_order/test_sales_order.py | 1 - .../shopping_cart_settings/test_shopping_cart_settings.py | 1 - erpnext/stock/doctype/shipment/test_shipment.py | 2 -- .../stock_reconciliation/test_stock_reconciliation.py | 5 ----- 7 files changed, 13 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index f492a03daf..e11fe13383 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -1087,8 +1087,6 @@ class TestSalesInvoice(unittest.TestCase): actual_qty_1 = get_qty_after_transaction(item_code = "_Test Item", warehouse = "Stores - TCP1") - frappe.db.commit() - self.assertEqual(actual_qty_0 - 5, actual_qty_1) # outgoing_rate diff --git a/erpnext/buying/report/procurement_tracker/test_procurement_tracker.py b/erpnext/buying/report/procurement_tracker/test_procurement_tracker.py index a5b09473a0..fd23795287 100644 --- a/erpnext/buying/report/procurement_tracker/test_procurement_tracker.py +++ b/erpnext/buying/report/procurement_tracker/test_procurement_tracker.py @@ -45,7 +45,6 @@ class TestProcurementTracker(unittest.TestCase): pr = make_purchase_receipt(po.name) pr.get("items")[0].cost_center = "Main - _TPC" pr.submit() - frappe.db.commit() date_obj = datetime.date(datetime.now()) po.load_from_db() diff --git a/erpnext/hr/doctype/daily_work_summary/test_daily_work_summary.py b/erpnext/hr/doctype/daily_work_summary/test_daily_work_summary.py index bed12e31ea..8a23682ad4 100644 --- a/erpnext/hr/doctype/daily_work_summary/test_daily_work_summary.py +++ b/erpnext/hr/doctype/daily_work_summary/test_daily_work_summary.py @@ -74,7 +74,6 @@ class TestDailyWorkSummary(unittest.TestCase): from `tabEmail Queue` as q, `tabEmail Queue Recipient` as r \ where q.name = r.parent""", as_dict=1) - frappe.db.commit() def setup_groups(self, hour=None): # setup email to trigger at this hour diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py index bbfe7c06d8..222e74ee6c 100644 --- a/erpnext/selling/doctype/sales_order/test_sales_order.py +++ b/erpnext/selling/doctype/sales_order/test_sales_order.py @@ -1382,7 +1382,6 @@ def make_sales_order_workflow(): frappe.get_doc(dict(doctype='Role', role_name='Test Junior Approver')).insert(ignore_if_duplicate=True) frappe.get_doc(dict(doctype='Role', role_name='Test Approver')).insert(ignore_if_duplicate=True) - frappe.db.commit() frappe.cache().hdel('roles', frappe.session.user) workflow = frappe.get_doc({ diff --git a/erpnext/shopping_cart/doctype/shopping_cart_settings/test_shopping_cart_settings.py b/erpnext/shopping_cart/doctype/shopping_cart_settings/test_shopping_cart_settings.py index f8a22b0e02..1164a5d394 100644 --- a/erpnext/shopping_cart/doctype/shopping_cart_settings/test_shopping_cart_settings.py +++ b/erpnext/shopping_cart/doctype/shopping_cart_settings/test_shopping_cart_settings.py @@ -44,7 +44,6 @@ class TestShoppingCartSettings(unittest.TestCase): def test_tax_rule_validation(self): frappe.db.sql("update `tabTax Rule` set use_for_shopping_cart = 0") - frappe.db.commit() cart_settings = self.get_cart_settings() cart_settings.enabled = 1 diff --git a/erpnext/stock/doctype/shipment/test_shipment.py b/erpnext/stock/doctype/shipment/test_shipment.py index 288d874800..dcd0b7c17c 100644 --- a/erpnext/stock/doctype/shipment/test_shipment.py +++ b/erpnext/stock/doctype/shipment/test_shipment.py @@ -47,7 +47,6 @@ def create_test_delivery_note(): } ) delivery_note.insert() - frappe.db.commit() return delivery_note @@ -91,7 +90,6 @@ def create_test_shipment(delivery_notes = None): } ) shipment.insert() - frappe.db.commit() return shipment diff --git a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py index e82401d073..415ac5eb26 100644 --- a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py @@ -371,7 +371,6 @@ class TestStockReconciliation(ERPNextTestCase): """ from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note from erpnext.stock.stock_ledger import NegativeStockError - frappe.db.commit() item_code = "Backdated-Reco-Cancellation-Item" warehouse = "_Test Warehouse - _TC" @@ -394,10 +393,6 @@ class TestStockReconciliation(ERPNextTestCase): repost_exists = bool(frappe.db.exists("Repost Item Valuation", {"voucher_no": sr.name})) self.assertFalse(repost_exists, msg="Negative stock validation not working on reco cancellation") - # teardown - frappe.db.rollback() - - def test_valid_batch(self): create_batch_item_with_batch("Testing Batch Item 1", "001") create_batch_item_with_batch("Testing Batch Item 2", "002") From 6f107da16570c5b406e923ecfb4ae99d836905b0 Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Tue, 12 Oct 2021 20:15:55 +0530 Subject: [PATCH 12/12] perf: Add indexes in stock queries and speed up bin updation #27758 perf: Add indexes in stock queries and speed up bin updation --- erpnext/controllers/stock_controller.py | 2 +- erpnext/stock/doctype/bin/bin.py | 109 ++++++++++-------- .../stock_ledger_entry.json | 2 +- .../stock_ledger_entry/stock_ledger_entry.py | 1 + erpnext/stock/stock_ledger.py | 13 +-- erpnext/stock/utils.py | 21 +++- 6 files changed, 91 insertions(+), 57 deletions(-) diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index 78a6e52e4d..4697205d72 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -591,7 +591,7 @@ def future_sle_exists(args, sl_entries=None): data = frappe.db.sql(""" select item_code, warehouse, count(name) as total_row - from `tabStock Ledger Entry` + from `tabStock Ledger Entry` force index (item_warehouse) where ({}) and timestamp(posting_date, posting_time) diff --git a/erpnext/stock/doctype/bin/bin.py b/erpnext/stock/doctype/bin/bin.py index 5fbc2d8dee..4be0415564 100644 --- a/erpnext/stock/doctype/bin/bin.py +++ b/erpnext/stock/doctype/bin/bin.py @@ -14,51 +14,6 @@ class Bin(Document): self.stock_uom = frappe.get_cached_value('Item', self.item_code, 'stock_uom') self.set_projected_qty() - def update_stock(self, args, allow_negative_stock=False, via_landed_cost_voucher=False): - '''Called from erpnext.stock.utils.update_bin''' - self.update_qty(args) - - if args.get("actual_qty") or args.get("voucher_type") == "Stock Reconciliation": - from erpnext.stock.stock_ledger import update_entries_after, update_qty_in_future_sle - - if not args.get("posting_date"): - args["posting_date"] = nowdate() - - if args.get("is_cancelled") and via_landed_cost_voucher: - return - - # Reposts only current voucher SL Entries - # Updates valuation rate, stock value, stock queue for current transaction - update_entries_after({ - "item_code": self.item_code, - "warehouse": self.warehouse, - "posting_date": args.get("posting_date"), - "posting_time": args.get("posting_time"), - "voucher_type": args.get("voucher_type"), - "voucher_no": args.get("voucher_no"), - "sle_id": args.name, - "creation": args.creation - }, allow_negative_stock=allow_negative_stock, via_landed_cost_voucher=via_landed_cost_voucher) - - # update qty in future ale and Validate negative qty - update_qty_in_future_sle(args, allow_negative_stock) - - - def update_qty(self, args): - # update the stock values (for current quantities) - if args.get("voucher_type")=="Stock Reconciliation": - self.actual_qty = args.get("qty_after_transaction") - else: - self.actual_qty = flt(self.actual_qty) + flt(args.get("actual_qty")) - - self.ordered_qty = flt(self.ordered_qty) + flt(args.get("ordered_qty")) - self.reserved_qty = flt(self.reserved_qty) + flt(args.get("reserved_qty")) - self.indented_qty = flt(self.indented_qty) + flt(args.get("indented_qty")) - self.planned_qty = flt(self.planned_qty) + flt(args.get("planned_qty")) - - self.set_projected_qty() - self.db_update() - def set_projected_qty(self): self.projected_qty = (flt(self.actual_qty) + flt(self.ordered_qty) + flt(self.indented_qty) + flt(self.planned_qty) - flt(self.reserved_qty) @@ -143,3 +98,67 @@ class Bin(Document): def on_doctype_update(): frappe.db.add_index("Bin", ["item_code", "warehouse"]) + + +def update_stock(bin_name, args, allow_negative_stock=False, via_landed_cost_voucher=False): + '''Called from erpnext.stock.utils.update_bin''' + update_qty(bin_name, args) + + if args.get("actual_qty") or args.get("voucher_type") == "Stock Reconciliation": + from erpnext.stock.stock_ledger import update_entries_after, update_qty_in_future_sle + + if not args.get("posting_date"): + args["posting_date"] = nowdate() + + if args.get("is_cancelled") and via_landed_cost_voucher: + return + + # Reposts only current voucher SL Entries + # Updates valuation rate, stock value, stock queue for current transaction + update_entries_after({ + "item_code": args.get('item_code'), + "warehouse": args.get('warehouse'), + "posting_date": args.get("posting_date"), + "posting_time": args.get("posting_time"), + "voucher_type": args.get("voucher_type"), + "voucher_no": args.get("voucher_no"), + "sle_id": args.get('name'), + "creation": args.get('creation') + }, allow_negative_stock=allow_negative_stock, via_landed_cost_voucher=via_landed_cost_voucher) + + # update qty in future sle and Validate negative qty + update_qty_in_future_sle(args, allow_negative_stock) + +def get_bin_details(bin_name): + return frappe.db.get_value('Bin', bin_name, ['actual_qty', 'ordered_qty', + 'reserved_qty', 'indented_qty', 'planned_qty', 'reserved_qty_for_production', + 'reserved_qty_for_sub_contract'], as_dict=1) + +def update_qty(bin_name, args): + bin_details = get_bin_details(bin_name) + + # update the stock values (for current quantities) + if args.get("voucher_type")=="Stock Reconciliation": + actual_qty = args.get('qty_after_transaction') + else: + actual_qty = bin_details.actual_qty + flt(args.get("actual_qty")) + + ordered_qty = flt(bin_details.ordered_qty) + flt(args.get("ordered_qty")) + reserved_qty = flt(bin_details.reserved_qty) + flt(args.get("reserved_qty")) + indented_qty = flt(bin_details.indented_qty) + flt(args.get("indented_qty")) + planned_qty = flt(bin_details.planned_qty) + flt(args.get("planned_qty")) + + + # compute projected qty + projected_qty = (flt(actual_qty) + flt(ordered_qty) + + flt(indented_qty) + flt(planned_qty) - flt(reserved_qty) + - flt(bin_details.reserved_qty_for_production) - flt(bin_details.reserved_qty_for_sub_contract)) + + frappe.db.set_value('Bin', bin_name, { + 'actual_qty': actual_qty, + 'ordered_qty': ordered_qty, + 'reserved_qty': reserved_qty, + 'indented_qty': indented_qty, + 'planned_qty': planned_qty, + 'projected_qty': projected_qty + }) \ No newline at end of file diff --git a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.json b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.json index 40ae340bfe..2651407d16 100644 --- a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.json +++ b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.json @@ -317,7 +317,7 @@ "in_create": 1, "index_web_pages_for_search": 1, "links": [], - "modified": "2021-10-08 12:42:51.857631", + "modified": "2021-10-08 13:42:51.857631", "modified_by": "Administrator", "module": "Stock", "name": "Stock Ledger Entry", diff --git a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py index 382fdfa4bf..2cf71accf8 100644 --- a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py +++ b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py @@ -181,3 +181,4 @@ def on_doctype_update(): frappe.db.add_index("Stock Ledger Entry", ["voucher_no", "voucher_type"]) frappe.db.add_index("Stock Ledger Entry", ["batch_no", "item_code", "warehouse"]) + frappe.db.add_index("Stock Ledger Entry", ["warehouse", "item_code"], "item_warehouse") diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index 1b5b792f94..e9d5b6ae09 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -13,8 +13,8 @@ from six import iteritems import erpnext from erpnext.stock.utils import ( - get_bin, get_incoming_outgoing_rate_for_cancel, + get_or_make_bin, get_valuation_method, ) @@ -805,14 +805,13 @@ class update_entries_after(object): def update_bin(self): # update bin for each warehouse for warehouse, data in iteritems(self.data): - bin_doc = get_bin(self.item_code, warehouse) - bin_doc.update({ + bin_record = get_or_make_bin(self.item_code, warehouse) + + frappe.db.set_value('Bin', bin_record, { "valuation_rate": data.valuation_rate, "actual_qty": data.qty_after_transaction, "stock_value": data.stock_value }) - bin_doc.flags.via_stock_ledger_entry = True - bin_doc.save(ignore_permissions=True) def get_previous_sle_of_current_voucher(args, exclude_current_voucher=False): @@ -918,7 +917,7 @@ def get_valuation_rate(item_code, warehouse, voucher_type, voucher_no, company = erpnext.get_default_company() last_valuation_rate = frappe.db.sql("""select valuation_rate - from `tabStock Ledger Entry` + from `tabStock Ledger Entry` force index (item_warehouse) where item_code = %s AND warehouse = %s @@ -929,7 +928,7 @@ def get_valuation_rate(item_code, warehouse, voucher_type, voucher_no, if not last_valuation_rate: # Get valuation rate from last sle for the item against any warehouse last_valuation_rate = frappe.db.sql("""select valuation_rate - from `tabStock Ledger Entry` + from `tabStock Ledger Entry` force index (item_code) where item_code = %s AND valuation_rate > 0 diff --git a/erpnext/stock/utils.py b/erpnext/stock/utils.py index aeb06e987f..c4a0497b74 100644 --- a/erpnext/stock/utils.py +++ b/erpnext/stock/utils.py @@ -180,12 +180,27 @@ def get_bin(item_code, warehouse): bin_obj.flags.ignore_permissions = True return bin_obj +def get_or_make_bin(item_code, warehouse) -> str: + bin_record = frappe.db.get_value('Bin', {'item_code': item_code, 'warehouse': warehouse}) + + if not bin_record: + bin_obj = frappe.get_doc({ + "doctype": "Bin", + "item_code": item_code, + "warehouse": warehouse, + }) + bin_obj.flags.ignore_permissions = 1 + bin_obj.insert() + bin_record = bin_obj.name + + return bin_record + def update_bin(args, allow_negative_stock=False, via_landed_cost_voucher=False): + from erpnext.stock.doctype.bin.bin import update_stock is_stock_item = frappe.get_cached_value('Item', args.get("item_code"), 'is_stock_item') if is_stock_item: - bin = get_bin(args.get("item_code"), args.get("warehouse")) - bin.update_stock(args, allow_negative_stock, via_landed_cost_voucher) - return bin + bin_record = get_or_make_bin(args.get("item_code"), args.get("warehouse")) + update_stock(bin_record, args, allow_negative_stock, via_landed_cost_voucher) else: frappe.msgprint(_("Item {0} ignored since it is not a stock item").format(args.get("item_code")))