From 40bb2cba933bfe07cc24df37c65495d66c9da4f2 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 27 Oct 2022 10:55:04 +0530 Subject: [PATCH 01/17] fix: GP incorrect buying amount if no upd on SI and Delivery Note (cherry picked from commit e4d16c31da7e62cf4361d76761f952154afdec5d) # Conflicts: # erpnext/accounts/report/gross_profit/gross_profit.py --- .../report/gross_profit/gross_profit.py | 74 +++++++++++++++---- 1 file changed, 61 insertions(+), 13 deletions(-) diff --git a/erpnext/accounts/report/gross_profit/gross_profit.py b/erpnext/accounts/report/gross_profit/gross_profit.py index 7f6e2b99c89..2890ba929d1 100644 --- a/erpnext/accounts/report/gross_profit/gross_profit.py +++ b/erpnext/accounts/report/gross_profit/gross_profit.py @@ -3,8 +3,14 @@ import frappe +<<<<<<< HEAD from frappe import _, scrub from frappe.utils import cint, flt +======= +from frappe import _, qb, scrub +from frappe.query_builder import Order +from frappe.utils import cint, flt, formatdate +>>>>>>> e4d16c31da (fix: GP incorrect buying amount if no upd on SI and Delivery Note) from erpnext.controllers.queries import get_match_cond from erpnext.stock.utils import get_incoming_rate @@ -367,6 +373,7 @@ class GrossProfitGenerator(object): self.average_buying_rate = {} self.filters = frappe._dict(filters) self.load_invoice_items() + self.get_delivery_notes() if filters.group_by == "Invoice": self.group_items_by_invoice() @@ -535,6 +542,21 @@ class GrossProfitGenerator(object): return flt(buying_amount, self.currency_precision) + def calculate_buying_amount_from_sle(self, row, my_sle, parenttype, parent, item_row, item_code): + for i, sle in enumerate(my_sle): + # find the stock valution rate from stock ledger entry + if ( + sle.voucher_type == parenttype + and parent == sle.voucher_no + and sle.voucher_detail_no == item_row + ): + previous_stock_value = len(my_sle) > i + 1 and flt(my_sle[i + 1].stock_value) or 0.0 + + if previous_stock_value: + return abs(previous_stock_value - flt(sle.stock_value)) * flt(row.qty) / abs(flt(sle.qty)) + else: + return flt(row.qty) * self.get_average_buying_rate(row, item_code) + def get_buying_amount(self, row, item_code): # IMP NOTE # stock_ledger_entries should already be filtered by item_code and warehouse and @@ -551,19 +573,22 @@ class GrossProfitGenerator(object): if row.dn_detail: parenttype, parent = "Delivery Note", row.delivery_note - for i, sle in enumerate(my_sle): - # find the stock valution rate from stock ledger entry - if ( - sle.voucher_type == parenttype - and parent == sle.voucher_no - and sle.voucher_detail_no == row.item_row - ): - previous_stock_value = len(my_sle) > i + 1 and flt(my_sle[i + 1].stock_value) or 0.0 - - if previous_stock_value: - return abs(previous_stock_value - flt(sle.stock_value)) * flt(row.qty) / abs(flt(sle.qty)) - else: - return flt(row.qty) * self.get_average_buying_rate(row, item_code) + return self.calculate_buying_amount_from_sle( + row, my_sle, parenttype, parent, row.item_row, item_code + ) + elif self.delivery_notes.get((row.parent, row.item_code), None): + # check if Invoice has delivery notes + dn = self.delivery_notes.get((row.parent, row.item_code)) + parenttype, parent, item_row, warehouse = ( + "Delivery Note", + dn["delivery_note"], + dn["item_row"], + dn["warehouse"], + ) + my_sle = self.sle.get((item_code, warehouse)) + return self.calculate_buying_amount_from_sle( + row, my_sle, parenttype, parent, item_row, item_code + ) else: return flt(row.qty) * self.get_average_buying_rate(row, item_code) @@ -667,6 +692,29 @@ class GrossProfitGenerator(object): as_dict=1, ) + def get_delivery_notes(self): + self.delivery_notes = frappe._dict({}) + if self.si_list: + invoices = [x.parent for x in self.si_list] + dni = qb.DocType("Delivery Note Item") + delivery_notes = ( + qb.from_(dni) + .select( + dni.against_sales_invoice.as_("sales_invoice"), + dni.item_code, + dni.warehouse, + dni.parent.as_("delivery_note"), + dni.name.as_("item_row"), + ) + .where((dni.docstatus == 1) & (dni.against_sales_invoice.isin(invoices))) + .groupby(dni.against_sales_invoice, dni.item_code) + .orderby(dni.creation, order=Order.desc) + .run(as_dict=True) + ) + + for entry in delivery_notes: + self.delivery_notes[(entry.sales_invoice, entry.item_code)] = entry + def group_items_by_invoice(self): """ Turns list of Sales Invoice Items to a tree of Sales Invoices with their Items as children. From 09ef8eeaeaaa1707f90e28a0dbc5e532f33f1a60 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 3 Nov 2022 17:04:50 +0530 Subject: [PATCH 02/17] test: buying amount of invoices 1. Invoice with unset `update_stock`, with and without Delivery Notes (cherry picked from commit 2c8b0b17a7650b9a2c2234ad706fd86199232c3a) --- .../report/gross_profit/test_gross_profit.py | 209 ++++++++++++++++++ 1 file changed, 209 insertions(+) create mode 100644 erpnext/accounts/report/gross_profit/test_gross_profit.py diff --git a/erpnext/accounts/report/gross_profit/test_gross_profit.py b/erpnext/accounts/report/gross_profit/test_gross_profit.py new file mode 100644 index 00000000000..0ea6b5c8a44 --- /dev/null +++ b/erpnext/accounts/report/gross_profit/test_gross_profit.py @@ -0,0 +1,209 @@ +import frappe +from frappe import qb +from frappe.tests.utils import FrappeTestCase +from frappe.utils import add_days, flt, nowdate + +from erpnext.accounts.doctype.sales_invoice.sales_invoice import make_delivery_note +from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice +from erpnext.accounts.report.gross_profit.gross_profit import execute +from erpnext.stock.doctype.item.test_item import create_item +from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry + + +class TestGrossProfit(FrappeTestCase): + def setUp(self): + self.create_company() + self.create_item() + self.create_customer() + self.create_sales_invoice() + self.clear_old_entries() + + def tearDown(self): + frappe.db.rollback() + + def create_company(self): + company_name = "_Test Gross Profit" + abbr = "_GP" + if frappe.db.exists("Company", company_name): + company = frappe.get_doc("Company", company_name) + else: + company = frappe.get_doc( + { + "doctype": "Company", + "company_name": company_name, + "country": "India", + "default_currency": "INR", + "create_chart_of_accounts_based_on": "Standard Template", + "chart_of_accounts": "Standard", + } + ) + company = company.save() + + self.company = company.name + self.cost_center = company.cost_center + self.warehouse = "Stores - " + abbr + self.income_account = "Sales - " + abbr + self.expense_account = "Cost of Goods Sold - " + abbr + self.debit_to = "Debtors - " + abbr + self.creditors = "Creditors - " + abbr + + def create_item(self): + item = create_item( + item_code="_Test GP Item", is_stock_item=1, company=self.company, warehouse=self.warehouse + ) + self.item = item if isinstance(item, str) else item.item_code + + def create_customer(self): + name = "_Test GP Customer" + if frappe.db.exists("Customer", name): + self.customer = name + else: + customer = frappe.new_doc("Customer") + customer.customer_name = name + customer.type = "Individual" + customer.save() + self.customer = customer.name + + def create_sales_invoice( + self, qty=1, rate=100, posting_date=nowdate(), do_not_save=False, do_not_submit=False + ): + """ + Helper function to populate default values in sales invoice + """ + sinv = create_sales_invoice( + qty=qty, + rate=rate, + company=self.company, + customer=self.customer, + item_code=self.item, + item_name=self.item, + cost_center=self.cost_center, + warehouse=self.warehouse, + debit_to=self.debit_to, + parent_cost_center=self.cost_center, + update_stock=0, + currency="INR", + is_pos=0, + is_return=0, + return_against=None, + income_account=self.income_account, + expense_account=self.expense_account, + do_not_save=do_not_save, + do_not_submit=do_not_submit, + ) + return sinv + + def clear_old_entries(self): + doctype_list = [ + "Sales Invoice", + "GL Entry", + "Payment Ledger Entry", + "Stock Entry", + "Stock Ledger Entry", + "Delivery Note", + ] + for doctype in doctype_list: + qb.from_(qb.DocType(doctype)).delete().where(qb.DocType(doctype).company == self.company).run() + + def test_invoice_without_only_delivery_note(self): + """ + Test buying amount for Invoice without `update_stock` flag set but has Delivery Note + """ + se = make_stock_entry( + company=self.company, + item_code=self.item, + target=self.warehouse, + qty=1, + basic_rate=100, + do_not_submit=True, + ) + item = se.items[0] + se.append( + "items", + { + "item_code": item.item_code, + "s_warehouse": item.s_warehouse, + "t_warehouse": item.t_warehouse, + "qty": 1, + "basic_rate": 200, + "conversion_factor": item.conversion_factor or 1.0, + "transfer_qty": flt(item.qty) * (flt(item.conversion_factor) or 1.0), + "serial_no": item.serial_no, + "batch_no": item.batch_no, + "cost_center": item.cost_center, + "expense_account": item.expense_account, + }, + ) + se = se.save().submit() + + sinv = create_sales_invoice( + qty=1, + rate=100, + company=self.company, + customer=self.customer, + item_code=self.item, + item_name=self.item, + cost_center=self.cost_center, + warehouse=self.warehouse, + debit_to=self.debit_to, + parent_cost_center=self.cost_center, + update_stock=0, + currency="INR", + income_account=self.income_account, + expense_account=self.expense_account, + ) + + filters = frappe._dict( + company=self.company, from_date=nowdate(), to_date=nowdate(), group_by="Invoice" + ) + + columns, data = execute(filters=filters) + + # Without Delivery Note, buying rate should be 150 + expected_entry_without_dn = { + "parent_invoice": sinv.name, + "currency": "INR", + "sales_invoice": self.item, + "customer": self.customer, + "posting_date": frappe.utils.datetime.date.fromisoformat(nowdate()), + "item_code": self.item, + "item_name": self.item, + "warehouse": "Stores - _GP", + "qty": 1.0, + "avg._selling_rate": 100.0, + "valuation_rate": 150.0, + "selling_amount": 100.0, + "buying_amount": 150.0, + "gross_profit": -50.0, + "gross_profit_%": -50.0, + } + gp_entry = [x for x in data if x.parent_invoice == sinv.name] + self.assertDictContainsSubset(expected_entry_without_dn, gp_entry[0]) + + # make delivery note + dn = make_delivery_note(sinv.name) + dn.items[0].qty = 1 + dn = dn.save().submit() + + columns, data = execute(filters=filters) + + # Without Delivery Note, buying rate should be 100 + expected_entry_with_dn = { + "parent_invoice": sinv.name, + "currency": "INR", + "sales_invoice": self.item, + "customer": self.customer, + "posting_date": frappe.utils.datetime.date.fromisoformat(nowdate()), + "item_code": self.item, + "item_name": self.item, + "warehouse": "Stores - _GP", + "qty": 1.0, + "avg._selling_rate": 100.0, + "valuation_rate": 100.0, + "selling_amount": 100.0, + "buying_amount": 100.0, + "gross_profit": 0.0, + "gross_profit_%": 0.0, + } + gp_entry = [x for x in data if x.parent_invoice == sinv.name] + self.assertDictContainsSubset(expected_entry_with_dn, gp_entry[0]) From 1ee5c2ff87a3569c565a55127828a4d7ad5cf4f3 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sun, 13 Nov 2022 19:49:07 +0530 Subject: [PATCH 03/17] chore: Resolve conflicts --- erpnext/accounts/report/gross_profit/gross_profit.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/erpnext/accounts/report/gross_profit/gross_profit.py b/erpnext/accounts/report/gross_profit/gross_profit.py index 2890ba929d1..3c0c8bcb19a 100644 --- a/erpnext/accounts/report/gross_profit/gross_profit.py +++ b/erpnext/accounts/report/gross_profit/gross_profit.py @@ -3,14 +3,9 @@ import frappe -<<<<<<< HEAD -from frappe import _, scrub -from frappe.utils import cint, flt -======= from frappe import _, qb, scrub from frappe.query_builder import Order from frappe.utils import cint, flt, formatdate ->>>>>>> e4d16c31da (fix: GP incorrect buying amount if no upd on SI and Delivery Note) from erpnext.controllers.queries import get_match_cond from erpnext.stock.utils import get_incoming_rate From 3aba14f71adaae18f2c11fe4ef187ca6fa6e011a Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 15 Nov 2022 18:50:32 +0530 Subject: [PATCH 04/17] fix: cast POS query inputs to integers (backport #32975) (#32978) * fix: cast POS query inputs to integers (#32975) fix: cast POS query inputs to integers (cherry picked from commit c013db6ea18b7c8f88b645d3a71dd785018da8be) # Conflicts: # erpnext/selling/page/point_of_sale/point_of_sale.py * chore: conflicts Co-authored-by: Ankush Menat --- erpnext/selling/page/point_of_sale/point_of_sale.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/erpnext/selling/page/point_of_sale/point_of_sale.py b/erpnext/selling/page/point_of_sale/point_of_sale.py index 8bce1f60725..a4d20e2e7ae 100644 --- a/erpnext/selling/page/point_of_sale/point_of_sale.py +++ b/erpnext/selling/page/point_of_sale/point_of_sale.py @@ -5,6 +5,7 @@ import json import frappe +from frappe.utils import cint from frappe.utils.nestedset import get_root_of from erpnext.accounts.doctype.pos_invoice.pos_invoice import get_stock_availability @@ -105,11 +106,11 @@ def get_items(start, page_length, price_list, item_group, pos_profile, search_te ORDER BY item.name asc LIMIT - {start}, {page_length}""".format( - start=start, - page_length=page_length, - lft=lft, - rgt=rgt, + {page_length} offset {start}""".format( + start=cint(start), + page_length=cint(page_length), + lft=cint(lft), + rgt=cint(rgt), condition=condition, bin_join_selection=bin_join_selection, bin_join_condition=bin_join_condition, From 78317440644974f8494cd672355fa49e441d49ab Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 16 Nov 2022 16:24:28 +0530 Subject: [PATCH 05/17] ci: fix flake8 URL (cherry picked from commit e81bec5fc95f6469d34c5f848be3fd628d4e320a) --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index dc3011f050f..73aae33e936 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,8 +16,8 @@ repos: - id: check-merge-conflict - id: check-ast - - repo: https://gitlab.com/pycqa/flake8 - rev: 3.9.2 + - repo: https://github.com/PyCQA/flake8 + rev: 5.0.4 hooks: - id: flake8 additional_dependencies: [ From 4ad3e28147c6b5b92c2e446740cf99b427b5add3 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 16 Nov 2022 11:57:17 +0530 Subject: [PATCH 06/17] fix: Opening journal entry templates (cherry picked from commit 33b61aef5acd10cf8d690e7aef342916f7d44e6f) --- .../journal_entry_template.js | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/erpnext/accounts/doctype/journal_entry_template/journal_entry_template.js b/erpnext/accounts/doctype/journal_entry_template/journal_entry_template.js index cf5fbe12afe..88f1c9069c8 100644 --- a/erpnext/accounts/doctype/journal_entry_template/journal_entry_template.js +++ b/erpnext/accounts/doctype/journal_entry_template/journal_entry_template.js @@ -45,21 +45,6 @@ frappe.ui.form.on("Journal Entry Template", { frm.trigger("clear_child"); switch(frm.doc.voucher_type){ - case "Opening Entry": - frm.set_value("is_opening", "Yes"); - frappe.call({ - type:"GET", - method: "erpnext.accounts.doctype.journal_entry.journal_entry.get_opening_accounts", - args: { - "company": frm.doc.company - }, - callback: function(r) { - if(r.message) { - add_accounts(frm.doc, r.message); - } - } - }); - break; case "Bank Entry": case "Cash Entry": frappe.call({ From 9d5c4ffadfb71572e084d06ea2f5333861f135a1 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Thu, 17 Nov 2022 11:58:05 +0530 Subject: [PATCH 07/17] chore: hardcode doctype --- erpnext/assets/doctype/location/location.py | 4 ++-- erpnext/hr/doctype/department/department.py | 4 ++-- .../doctype/quality_procedure/quality_procedure.py | 2 +- erpnext/setup/doctype/company/company.py | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/erpnext/assets/doctype/location/location.py b/erpnext/assets/doctype/location/location.py index 0d87bb2bf4d..5bff3dd8c99 100644 --- a/erpnext/assets/doctype/location/location.py +++ b/erpnext/assets/doctype/location/location.py @@ -200,11 +200,11 @@ def get_children(doctype, parent=None, location=None, is_root=False): name as value, is_group as expandable from - `tab{doctype}` comp + `tabLocation` comp where ifnull(parent_location, "")={parent} """.format( - doctype=doctype, parent=frappe.db.escape(parent) + parent=frappe.db.escape(parent) ), as_dict=1, ) diff --git a/erpnext/hr/doctype/department/department.py b/erpnext/hr/doctype/department/department.py index a9806c529f6..d4834add02b 100644 --- a/erpnext/hr/doctype/department/department.py +++ b/erpnext/hr/doctype/department/department.py @@ -70,11 +70,11 @@ def get_children(doctype, parent=None, company=None, is_root=False): select name as value, is_group as expandable - from `tab{doctype}` + from `tabDepartment` where {condition} order by name""".format( - doctype=doctype, condition=condition + condition=condition ), var_dict, as_dict=1, diff --git a/erpnext/quality_management/doctype/quality_procedure/quality_procedure.py b/erpnext/quality_management/doctype/quality_procedure/quality_procedure.py index 72f9e6d6e44..e8604080fbf 100644 --- a/erpnext/quality_management/doctype/quality_procedure/quality_procedure.py +++ b/erpnext/quality_management/doctype/quality_procedure/quality_procedure.py @@ -79,7 +79,7 @@ def get_children(doctype, parent=None, parent_quality_procedure=None, is_root=Fa ] else: return frappe.get_all( - doctype, + "Quality Procedure", fields=["name as value", "is_group as expandable"], filters=dict(parent_quality_procedure=parent), order_by="name asc", diff --git a/erpnext/setup/doctype/company/company.py b/erpnext/setup/doctype/company/company.py index ee39d3a4ac5..51bae6091bd 100644 --- a/erpnext/setup/doctype/company/company.py +++ b/erpnext/setup/doctype/company/company.py @@ -611,11 +611,11 @@ def get_children(doctype, parent=None, company=None, is_root=False): name as value, is_group as expandable from - `tab{doctype}` comp + `tabCompany` comp where ifnull(parent_company, "")={parent} """.format( - doctype=doctype, parent=frappe.db.escape(parent) + parent=frappe.db.escape(parent) ), as_dict=1, ) From 30b5d1c4006f613102fcb6f32920e09302331a6f Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Thu, 17 Nov 2022 12:36:44 +0530 Subject: [PATCH 08/17] fix: make `is_internal_supplier` read-only (cherry picked from commit 5efbc2cbf8dd9ac0b0bc84194d640a0443c81c56) # Conflicts: # erpnext/buying/doctype/purchase_order/purchase_order.json --- erpnext/buying/doctype/purchase_order/purchase_order.json | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.json b/erpnext/buying/doctype/purchase_order/purchase_order.json index c5bfcd57738..d5d91fec6aa 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.json +++ b/erpnext/buying/doctype/purchase_order/purchase_order.json @@ -1118,7 +1118,8 @@ "fetch_from": "supplier.is_internal_supplier", "fieldname": "is_internal_supplier", "fieldtype": "Check", - "label": "Is Internal Supplier" + "label": "Is Internal Supplier", + "read_only": 1 }, { "fetch_from": "supplier.represents_company", @@ -1169,7 +1170,11 @@ "idx": 105, "is_submittable": 1, "links": [], +<<<<<<< HEAD "modified": "2022-09-16 17:45:04.954055", +======= + "modified": "2022-11-17 12:34:36.033363", +>>>>>>> 5efbc2cbf8 (fix: make `is_internal_supplier` read-only) "modified_by": "Administrator", "module": "Buying", "name": "Purchase Order", From 9191c54b90a2525ce77132b3d3cd705d7a3ce692 Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Thu, 17 Nov 2022 14:19:01 +0530 Subject: [PATCH 09/17] chore: conflicts --- erpnext/buying/doctype/purchase_order/purchase_order.json | 4 ---- 1 file changed, 4 deletions(-) diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.json b/erpnext/buying/doctype/purchase_order/purchase_order.json index d5d91fec6aa..2144ae00366 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.json +++ b/erpnext/buying/doctype/purchase_order/purchase_order.json @@ -1170,11 +1170,7 @@ "idx": 105, "is_submittable": 1, "links": [], -<<<<<<< HEAD - "modified": "2022-09-16 17:45:04.954055", -======= "modified": "2022-11-17 12:34:36.033363", ->>>>>>> 5efbc2cbf8 (fix: make `is_internal_supplier` read-only) "modified_by": "Administrator", "module": "Buying", "name": "Purchase Order", From 357ae939d1a1f320d204f8a1bd79c589b2089de5 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 17 Nov 2022 14:24:45 +0530 Subject: [PATCH 10/17] refactor: search queries (backport #33004) (#33008) * refactor: search queries (#33004) - guard clauses for readability - use values or format (cherry picked from commit 34e4903ed7936c35176d6031a16d1a27654dcb40) # Conflicts: # erpnext/stock/doctype/material_request/material_request.py # erpnext/stock/doctype/quality_inspection/quality_inspection.py * chore: conflicts [skip ci] Co-authored-by: Ankush Menat --- .../request_for_quotation.py | 19 +-- .../material_request/material_request.py | 12 +- .../quality_inspection/quality_inspection.py | 119 +++++++++--------- 3 files changed, 78 insertions(+), 72 deletions(-) diff --git a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py index 45a2ad43e7c..309d321ba81 100644 --- a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py +++ b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py @@ -479,7 +479,7 @@ def get_rfq_containing_supplier(doctype, txt, searchfield, start, page_len, filt conditions += "and rfq.transaction_date = '{0}'".format(filters.get("transaction_date")) rfq_data = frappe.db.sql( - """ + f""" select distinct rfq.name, rfq.transaction_date, rfq.company @@ -487,15 +487,18 @@ def get_rfq_containing_supplier(doctype, txt, searchfield, start, page_len, filt `tabRequest for Quotation` rfq, `tabRequest for Quotation Supplier` rfq_supplier where rfq.name = rfq_supplier.parent - and rfq_supplier.supplier = '{0}' + and rfq_supplier.supplier = %(supplier)s and rfq.docstatus = 1 - and rfq.company = '{1}' - {2} + and rfq.company = %(company)s + {conditions} order by rfq.transaction_date ASC - limit %(page_len)s offset %(start)s """.format( - filters.get("supplier"), filters.get("company"), conditions - ), - {"page_len": page_len, "start": start}, + limit %(page_len)s offset %(start)s """, + { + "page_len": page_len, + "start": start, + "company": filters.get("company"), + "supplier": filters.get("supplier"), + }, as_dict=1, ) diff --git a/erpnext/stock/doctype/material_request/material_request.py b/erpnext/stock/doctype/material_request/material_request.py index 3474ca0db68..c1201ef8f9a 100644 --- a/erpnext/stock/doctype/material_request/material_request.py +++ b/erpnext/stock/doctype/material_request/material_request.py @@ -10,7 +10,7 @@ import json import frappe from frappe import _, msgprint from frappe.model.mapper import get_mapped_doc -from frappe.utils import cstr, flt, get_link_to_form, getdate, new_line_sep, nowdate +from frappe.utils import cint, cstr, flt, get_link_to_form, getdate, new_line_sep, nowdate from six import string_types from erpnext.buying.utils import check_on_hold_or_closed_status, validate_for_items @@ -504,13 +504,13 @@ def get_material_requests_based_on_supplier(doctype, txt, searchfield, start, pa and mr.per_ordered < 99.99 and mr.docstatus = 1 and mr.status != 'Stopped' - and mr.company = '{1}' - {2} + and mr.company = %s + {1} order by mr_item.item_code ASC - limit {3} offset {4} """.format( - ", ".join(["%s"] * len(supplier_items)), filters.get("company"), conditions, page_len, start + limit {2} offset {3} """.format( + ", ".join(["%s"] * len(supplier_items)), conditions, cint(page_len), cint(start) ), - tuple(supplier_items), + tuple(supplier_items) + (filters.get("company"),), as_dict=1, ) diff --git a/erpnext/stock/doctype/quality_inspection/quality_inspection.py b/erpnext/stock/doctype/quality_inspection/quality_inspection.py index b9237bc4912..9321c2c166b 100644 --- a/erpnext/stock/doctype/quality_inspection/quality_inspection.py +++ b/erpnext/stock/doctype/quality_inspection/quality_inspection.py @@ -6,7 +6,7 @@ import frappe from frappe import _ from frappe.model.document import Document from frappe.model.mapper import get_mapped_doc -from frappe.utils import cint, flt +from frappe.utils import cint, cstr, flt from erpnext.stock.doctype.quality_inspection_template.quality_inspection_template import ( get_template_details, @@ -219,68 +219,71 @@ class QualityInspection(Document): @frappe.whitelist() @frappe.validate_and_sanitize_search_inputs def item_query(doctype, txt, searchfield, start, page_len, filters): - if filters.get("from"): - from frappe.desk.reportview import get_match_cond + from frappe.desk.reportview import get_match_cond - mcond = get_match_cond(filters["from"]) - cond, qi_condition = "", "and (quality_inspection is null or quality_inspection = '')" + from_doctype = cstr(filters.get("doctype")) + if not from_doctype or not frappe.db.exists("DocType", from_doctype): + return [] - if filters.get("parent"): - if ( - filters.get("from") in ["Purchase Invoice Item", "Purchase Receipt Item"] - and filters.get("inspection_type") != "In Process" - ): - cond = """and item_code in (select name from `tabItem` where - inspection_required_before_purchase = 1)""" - elif ( - filters.get("from") in ["Sales Invoice Item", "Delivery Note Item"] - and filters.get("inspection_type") != "In Process" - ): - cond = """and item_code in (select name from `tabItem` where - inspection_required_before_delivery = 1)""" - elif filters.get("from") == "Stock Entry Detail": - cond = """and s_warehouse is null""" + mcond = get_match_cond(from_doctype) + cond, qi_condition = "", "and (quality_inspection is null or quality_inspection = '')" - if filters.get("from") in ["Supplier Quotation Item"]: - qi_condition = "" + if filters.get("parent"): + if ( + from_doctype in ["Purchase Invoice Item", "Purchase Receipt Item"] + and filters.get("inspection_type") != "In Process" + ): + cond = """and item_code in (select name from `tabItem` where + inspection_required_before_purchase = 1)""" + elif ( + from_doctype in ["Sales Invoice Item", "Delivery Note Item"] + and filters.get("inspection_type") != "In Process" + ): + cond = """and item_code in (select name from `tabItem` where + inspection_required_before_delivery = 1)""" + elif from_doctype == "Stock Entry Detail": + cond = """and s_warehouse is null""" - return frappe.db.sql( - """ - SELECT item_code - FROM `tab{doc}` - WHERE parent=%(parent)s and docstatus < 2 and item_code like %(txt)s - {qi_condition} {cond} {mcond} - ORDER BY item_code limit {start}, {page_len} - """.format( - doc=filters.get("from"), - cond=cond, - mcond=mcond, - start=start, - page_len=page_len, - qi_condition=qi_condition, - ), - {"parent": filters.get("parent"), "txt": "%%%s%%" % txt}, - ) + if from_doctype in ["Supplier Quotation Item"]: + qi_condition = "" - elif filters.get("reference_name"): - return frappe.db.sql( - """ - SELECT production_item - FROM `tab{doc}` - WHERE name = %(reference_name)s and docstatus < 2 and production_item like %(txt)s - {qi_condition} {cond} {mcond} - ORDER BY production_item - LIMIT {start}, {page_len} - """.format( - doc=filters.get("from"), - cond=cond, - mcond=mcond, - start=start, - page_len=page_len, - qi_condition=qi_condition, - ), - {"reference_name": filters.get("reference_name"), "txt": "%%%s%%" % txt}, - ) + return frappe.db.sql( + """ + SELECT item_code + FROM `tab{doc}` + WHERE parent=%(parent)s and docstatus < 2 and item_code like %(txt)s + {qi_condition} {cond} {mcond} + ORDER BY item_code limit {page_len} offset {start} + """.format( + doc=from_doctype, + cond=cond, + mcond=mcond, + start=cint(start), + page_len=cint(page_len), + qi_condition=qi_condition, + ), + {"parent": filters.get("parent"), "txt": "%%%s%%" % txt}, + ) + + elif filters.get("reference_name"): + return frappe.db.sql( + """ + SELECT production_item + FROM `tab{doc}` + WHERE name = %(reference_name)s and docstatus < 2 and production_item like %(txt)s + {qi_condition} {cond} {mcond} + ORDER BY production_item + limit {page_len} offset {start} + """.format( + doc=from_doctype, + cond=cond, + mcond=mcond, + start=cint(start), + page_len=cint(page_len), + qi_condition=qi_condition, + ), + {"reference_name": filters.get("reference_name"), "txt": "%%%s%%" % txt}, + ) @frappe.whitelist() From ab31eb4ee7622780a910b2edac3447c0bec4a21b Mon Sep 17 00:00:00 2001 From: Sabu Siyad Date: Thu, 17 Nov 2022 12:56:42 +0530 Subject: [PATCH 11/17] fix(pos): item selector image border radius Signed-off-by: Sabu Siyad (cherry picked from commit 2f4940cc269ab6402dae649ebd9e2f9dfc18d1e7) --- erpnext/public/scss/point-of-sale.scss | 6 ++++++ erpnext/selling/page/point_of_sale/pos_item_selector.js | 4 ++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/erpnext/public/scss/point-of-sale.scss b/erpnext/public/scss/point-of-sale.scss index 7a3854cc611..7b7530b1501 100644 --- a/erpnext/public/scss/point-of-sale.scss +++ b/erpnext/public/scss/point-of-sale.scss @@ -159,6 +159,12 @@ } } + .item-img { + @extend .image; + border-radius: 8px 8px 0 0; + object-fit: cover; + } + > .item-detail { display: flex; flex-direction: column; diff --git a/erpnext/selling/page/point_of_sale/pos_item_selector.js b/erpnext/selling/page/point_of_sale/pos_item_selector.js index 7a90fb044f3..b5eb0489f9d 100644 --- a/erpnext/selling/page/point_of_sale/pos_item_selector.js +++ b/erpnext/selling/page/point_of_sale/pos_item_selector.js @@ -103,9 +103,9 @@ erpnext.PointOfSale.ItemSelector = class {
${frappe.get_abbr(item.item_name)} + >
`; } else { return `
From 0070b5ef9afecfc0ab586acf809d0c50b9f77e03 Mon Sep 17 00:00:00 2001 From: Sabu Siyad Date: Thu, 17 Nov 2022 17:46:49 +0530 Subject: [PATCH 12/17] fix: use `list()` on self mutating iteration https://github.com/frappe/erpnext/issues/30325 Signed-off-by: Sabu Siyad (cherry picked from commit 546c809cbeead2b556b3b09b632f3d73a500900e) --- erpnext/erpnext_integrations/taxjar_integration.py | 2 +- erpnext/stock/report/stock_ledger/stock_ledger.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/erpnext_integrations/taxjar_integration.py b/erpnext/erpnext_integrations/taxjar_integration.py index b8893aa7732..2d9093b6e92 100644 --- a/erpnext/erpnext_integrations/taxjar_integration.py +++ b/erpnext/erpnext_integrations/taxjar_integration.py @@ -302,7 +302,7 @@ def check_for_nexus(doc, tax_dict): item.tax_collectable = flt(0) item.taxable_amount = flt(0) - for tax in doc.taxes: + for tax in list(doc.taxes): if tax.account_head == TAX_ACCOUNT_HEAD: doc.taxes.remove(tax) return diff --git a/erpnext/stock/report/stock_ledger/stock_ledger.py b/erpnext/stock/report/stock_ledger/stock_ledger.py index b1fdaacac9d..7ca771f0a73 100644 --- a/erpnext/stock/report/stock_ledger/stock_ledger.py +++ b/erpnext/stock/report/stock_ledger/stock_ledger.py @@ -355,7 +355,7 @@ def get_opening_balance(filters, columns, sl_entries): ) # check if any SLEs are actually Opening Stock Reconciliation - for sle in sl_entries: + for sle in list(sl_entries): if ( sle.get("voucher_type") == "Stock Reconciliation" and sle.get("date").split()[0] == filters.from_date From ecdd8493ea9136e93d9dedfd53ea6825ff3add9a Mon Sep 17 00:00:00 2001 From: gavin Date: Fri, 18 Nov 2022 17:17:54 +0530 Subject: [PATCH 13/17] fix(realtime): Restrict updates to only last modified or current user (#33034) (cherry picked from commit dd2493a541f0f8f8a231c4ba32970898b1bab224) --- .../opening_invoice_creation_tool.js | 1 - .../opening_invoice_creation_tool.py | 2 +- .../accounts/doctype/pos_closing_entry/pos_closing_entry.js | 2 +- .../doctype/pos_invoice_merge_log/pos_invoice_merge_log.py | 4 ++-- .../doctype/quickbooks_migrator/quickbooks_migrator.py | 2 +- .../doctype/tally_migration/tally_migration.py | 1 + .../import_supplier_invoice/import_supplier_invoice.py | 4 +++- 7 files changed, 9 insertions(+), 7 deletions(-) diff --git a/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.js b/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.js index 7eb5c4234d1..e9612c36027 100644 --- a/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.js +++ b/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.js @@ -20,7 +20,6 @@ frappe.ui.form.on('Opening Invoice Creation Tool', { frm.dashboard.reset(); frm.doc.import_in_progress = true; } - if (data.user != frappe.session.user) return; if (data.count == data.total) { setTimeout((title) => { frm.doc.import_in_progress = false; diff --git a/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py b/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py index 0f0ab68dcb8..510b69c96d5 100644 --- a/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py +++ b/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py @@ -271,10 +271,10 @@ def publish(index, total, doctype): dict( title=_("Opening Invoice Creation In Progress"), message=_("Creating {} out of {} {}").format(index + 1, total, doctype), - user=frappe.session.user, count=index + 1, total=total, ), + user=frappe.session.user, ) diff --git a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.js b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.js index 1d596c1bfbb..e6d9fe2b54d 100644 --- a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.js +++ b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.js @@ -25,7 +25,7 @@ frappe.ui.form.on('POS Closing Entry', { frappe.realtime.on('closing_process_complete', async function(data) { await frm.reload_doc(); - if (frm.doc.status == 'Failed' && frm.doc.error_message && data.user == frappe.session.user) { + if (frm.doc.status == 'Failed' && frm.doc.error_message) { frappe.msgprint({ title: __('POS Closing Failed'), message: frm.doc.error_message, diff --git a/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py b/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py index f6002163f0f..fc356f2378d 100644 --- a/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py +++ b/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py @@ -432,7 +432,7 @@ def create_merge_logs(invoice_by_customer, closing_entry=None): finally: frappe.db.commit() - frappe.publish_realtime("closing_process_complete", {"user": frappe.session.user}) + frappe.publish_realtime("closing_process_complete", user=frappe.session.user) def cancel_merge_logs(merge_logs, closing_entry=None): @@ -459,7 +459,7 @@ def cancel_merge_logs(merge_logs, closing_entry=None): finally: frappe.db.commit() - frappe.publish_realtime("closing_process_complete", {"user": frappe.session.user}) + frappe.publish_realtime("closing_process_complete", user=frappe.session.user) def enqueue_job(job, **kwargs): diff --git a/erpnext/erpnext_integrations/doctype/quickbooks_migrator/quickbooks_migrator.py b/erpnext/erpnext_integrations/doctype/quickbooks_migrator/quickbooks_migrator.py index b93c5c4d38c..da5699776fd 100644 --- a/erpnext/erpnext_integrations/doctype/quickbooks_migrator/quickbooks_migrator.py +++ b/erpnext/erpnext_integrations/doctype/quickbooks_migrator/quickbooks_migrator.py @@ -1345,7 +1345,7 @@ class QuickBooksMigrator(Document): )[0]["name"] def _publish(self, *args, **kwargs): - frappe.publish_realtime("quickbooks_progress_update", *args, **kwargs) + frappe.publish_realtime("quickbooks_progress_update", *args, **kwargs, user=self.modified_by) def _get_unique_account_name(self, quickbooks_name, number=0): if number: diff --git a/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.py b/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.py index 833bbdfa3f3..7682cd0fe80 100644 --- a/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.py +++ b/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.py @@ -302,6 +302,7 @@ class TallyMigration(Document): frappe.publish_realtime( "tally_migration_progress_update", {"title": title, "message": message, "count": count, "total": total}, + user=self.modified_by, ) def _import_master_data(self): diff --git a/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.py b/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.py index 77c4d7c6ca3..6db213aa275 100644 --- a/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.py +++ b/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.py @@ -146,7 +146,9 @@ class ImportSupplierInvoice(Document): def publish(self, title, message, count, total): frappe.publish_realtime( - "import_invoice_update", {"title": title, "message": message, "count": count, "total": total} + "import_invoice_update", + {"title": title, "message": message, "count": count, "total": total}, + user=self.modified_by, ) From 726c3d32be0af09965d2a405d8534e1a72553e19 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Mon, 14 Nov 2022 09:53:30 +0530 Subject: [PATCH 14/17] fix: incorrect fix of conversion factor in PP (cherry picked from commit 490b0e3cdf86371d35cb065596fb9c61c37411fa) --- .../production_plan/production_plan.py | 28 +++++++++++-------- .../production_plan/test_production_plan.py | 5 ++++ 2 files changed, 21 insertions(+), 12 deletions(-) diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py index f4960f0ed50..1c0d19fa95b 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -652,23 +652,13 @@ class ProductionPlan(Document): else: material_request = material_request_map[key] - conversion_factor = 1.0 - if ( - material_request_type == "Purchase" - and item_doc.purchase_uom - and item_doc.purchase_uom != item_doc.stock_uom - ): - conversion_factor = ( - get_conversion_factor(item_doc.name, item_doc.purchase_uom).get("conversion_factor") or 1.0 - ) - # add item material_request.append( "items", { "item_code": item.item_code, "from_warehouse": item.from_warehouse, - "qty": item.quantity / conversion_factor, + "qty": item.quantity, "schedule_date": schedule_date, "warehouse": item.warehouse, "sales_order": item.sales_order, @@ -988,11 +978,25 @@ def get_material_request_items( if include_safety_stock: required_qty += flt(row["safety_stock"]) + item_details = frappe.get_cached_value( + "Item", row.item_code, ["purchase_uom", "stock_uom"], as_dict=1 + ) + + conversion_factor = 1.0 + if ( + row.get("default_material_request_type") == "Purchase" + and item_details.purchase_uom + and item_details.purchase_uom != item_details.stock_uom + ): + conversion_factor = ( + get_conversion_factor(row.item_code, item_details.purchase_uom).get("conversion_factor") or 1.0 + ) + if required_qty > 0: return { "item_code": row.item_code, "item_name": row.item_name, - "quantity": required_qty, + "quantity": required_qty / conversion_factor, "required_bom_qty": total_qty, "stock_uom": row.get("stock_uom"), "warehouse": warehouse diff --git a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py index 142d521552d..8cd79202dd8 100644 --- a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py @@ -752,6 +752,11 @@ class TestProductionPlan(FrappeTestCase): ) pln.make_material_request() + + for row in pln.mr_items: + self.assertEqual(row.uom, "Nos") + self.assertEqual(row.quantity, 1) + for row in frappe.get_all( "Material Request Item", filters={"production_plan": pln.name}, From 58f3f2b6d9e124c705c76e3536a308b4c6450bf8 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 18 Nov 2022 15:07:12 +0530 Subject: [PATCH 15/17] fix: Accounting Dimension filtering for Sales and Purchase Report (cherry picked from commit 8b394afaa99b15cc4876fe28e13a8def01010cc7) --- .../accounts/report/purchase_register/purchase_register.py | 4 ++-- erpnext/accounts/report/sales_register/sales_register.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/report/purchase_register/purchase_register.py b/erpnext/accounts/report/purchase_register/purchase_register.py index e8a1e795d92..a05d581207c 100644 --- a/erpnext/accounts/report/purchase_register/purchase_register.py +++ b/erpnext/accounts/report/purchase_register/purchase_register.py @@ -232,12 +232,12 @@ def get_conditions(filters): conditions += ( common_condition - + "and ifnull(`tabPurchase Invoice Item`.{0}, '') in %({0})s)".format(dimension.fieldname) + + "and ifnull(`tabPurchase Invoice`.{0}, '') in %({0})s)".format(dimension.fieldname) ) else: conditions += ( common_condition - + "and ifnull(`tabPurchase Invoice Item`.{0}, '') in %({0})s)".format(dimension.fieldname) + + "and ifnull(`tabPurchase Invoice`.{0}, '') in %({0})s)".format(dimension.fieldname) ) return conditions diff --git a/erpnext/accounts/report/sales_register/sales_register.py b/erpnext/accounts/report/sales_register/sales_register.py index 06e3c6120de..b333901d7b3 100644 --- a/erpnext/accounts/report/sales_register/sales_register.py +++ b/erpnext/accounts/report/sales_register/sales_register.py @@ -390,12 +390,12 @@ def get_conditions(filters): conditions += ( common_condition - + "and ifnull(`tabSales Invoice Item`.{0}, '') in %({0})s)".format(dimension.fieldname) + + "and ifnull(`tabSales Invoice`.{0}, '') in %({0})s)".format(dimension.fieldname) ) else: conditions += ( common_condition - + "and ifnull(`tabSales Invoice Item`.{0}, '') in %({0})s)".format(dimension.fieldname) + + "and ifnull(`tabSales Invoice`.{0}, '') in %({0})s)".format(dimension.fieldname) ) return conditions From 72f9308df612d3e7e5c69fe9ba3985d5570da113 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 22 Nov 2022 15:06:27 +0530 Subject: [PATCH 16/17] fix: linter issue --- erpnext/accounts/report/gross_profit/gross_profit.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/report/gross_profit/gross_profit.py b/erpnext/accounts/report/gross_profit/gross_profit.py index 3c0c8bcb19a..b71b31a5ec2 100644 --- a/erpnext/accounts/report/gross_profit/gross_profit.py +++ b/erpnext/accounts/report/gross_profit/gross_profit.py @@ -5,7 +5,7 @@ import frappe from frappe import _, qb, scrub from frappe.query_builder import Order -from frappe.utils import cint, flt, formatdate +from frappe.utils import cint, flt from erpnext.controllers.queries import get_match_cond from erpnext.stock.utils import get_incoming_rate From 4834b78ed26dd79cc9a221e6eadcf79327189996 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 22 Nov 2022 15:06:46 +0530 Subject: [PATCH 17/17] refactor: clean up code in test suite Remove doctypes that are not in v13 --- erpnext/accounts/report/gross_profit/test_gross_profit.py | 1 - 1 file changed, 1 deletion(-) diff --git a/erpnext/accounts/report/gross_profit/test_gross_profit.py b/erpnext/accounts/report/gross_profit/test_gross_profit.py index 0ea6b5c8a44..1279dec25af 100644 --- a/erpnext/accounts/report/gross_profit/test_gross_profit.py +++ b/erpnext/accounts/report/gross_profit/test_gross_profit.py @@ -97,7 +97,6 @@ class TestGrossProfit(FrappeTestCase): doctype_list = [ "Sales Invoice", "GL Entry", - "Payment Ledger Entry", "Stock Entry", "Stock Ledger Entry", "Delivery Note",