From 06e094b5fccfae350b7432edb6e89a38430da931 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Mon, 28 Nov 2022 22:58:31 +0100 Subject: [PATCH 01/25] fix: reload currency exchange settings --- erpnext/patches/v13_0/update_exchange_rate_settings.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/erpnext/patches/v13_0/update_exchange_rate_settings.py b/erpnext/patches/v13_0/update_exchange_rate_settings.py index 746195f2e1..130a7bf7df 100644 --- a/erpnext/patches/v13_0/update_exchange_rate_settings.py +++ b/erpnext/patches/v13_0/update_exchange_rate_settings.py @@ -1,5 +1,8 @@ +import frappe + from erpnext.setup.install import setup_currency_exchange def execute(): + frappe.reload_doc("accounts", "doctype", "currency_exchange_settings") setup_currency_exchange() From 9b8d6fe4119ae6cb7b913ef800e99d9c5d739518 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 1 Dec 2022 13:38:41 +0530 Subject: [PATCH 02/25] fix: key error while filtering on date range and different currency --- erpnext/accounts/report/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/report/utils.py b/erpnext/accounts/report/utils.py index eed5836773..81fea28515 100644 --- a/erpnext/accounts/report/utils.py +++ b/erpnext/accounts/report/utils.py @@ -28,7 +28,7 @@ def get_currency(filters): filters["presentation_currency"] if filters.get("presentation_currency") else company_currency ) - report_date = filters.get("to_date") + report_date = filters.get("period_end_date") if not report_date: fiscal_year_to_date = get_from_and_to_date(filters.get("to_fiscal_year"))["to_date"] From 826f45ad603c581a7ea0c29e1274474ce124f6eb Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Thu, 1 Dec 2022 15:51:38 +0530 Subject: [PATCH 03/25] fix: Bundle item rates --- erpnext/stock/doctype/packed_item/packed_item.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/packed_item/packed_item.py b/erpnext/stock/doctype/packed_item/packed_item.py index 4d05d7a345..02f8e903bb 100644 --- a/erpnext/stock/doctype/packed_item/packed_item.py +++ b/erpnext/stock/doctype/packed_item/packed_item.py @@ -259,7 +259,7 @@ def update_product_bundle_rate(parent_items_price, pi_row): if not rate: parent_items_price[key] = 0.0 - parent_items_price[key] += flt(pi_row.rate) + parent_items_price[key] += flt(pi_row.rate * pi_row.qty) def set_product_bundle_rate_amount(doc, parent_items_price): From 03f7bfbbde8f6572171e61e6f828149f5a839ff5 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Thu, 1 Dec 2022 12:42:03 +0100 Subject: [PATCH 04/25] refactor: validate dates --- erpnext/setup/doctype/employee/employee.py | 31 +++------------------- 1 file changed, 4 insertions(+), 27 deletions(-) diff --git a/erpnext/setup/doctype/employee/employee.py b/erpnext/setup/doctype/employee/employee.py index 13a6f20db2..facefa376a 100755 --- a/erpnext/setup/doctype/employee/employee.py +++ b/erpnext/setup/doctype/employee/employee.py @@ -145,33 +145,10 @@ class Employee(NestedSet): if self.date_of_birth and getdate(self.date_of_birth) > getdate(today()): throw(_("Date of Birth cannot be greater than today.")) - if ( - self.date_of_birth - and self.date_of_joining - and getdate(self.date_of_birth) >= getdate(self.date_of_joining) - ): - throw(_("Date of Joining must be greater than Date of Birth")) - - elif ( - self.date_of_retirement - and self.date_of_joining - and (getdate(self.date_of_retirement) <= getdate(self.date_of_joining)) - ): - throw(_("Date Of Retirement must be greater than Date of Joining")) - - elif ( - self.relieving_date - and self.date_of_joining - and (getdate(self.relieving_date) < getdate(self.date_of_joining)) - ): - throw(_("Relieving Date must be greater than or equal to Date of Joining")) - - elif ( - self.contract_end_date - and self.date_of_joining - and (getdate(self.contract_end_date) <= getdate(self.date_of_joining)) - ): - throw(_("Contract End Date must be greater than Date of Joining")) + self.validate_from_to_dates("date_of_birth", "date_of_joining") + self.validate_from_to_dates("date_of_joining", "date_of_retirement") + self.validate_from_to_dates("date_of_joining", "relieving_date") + self.validate_from_to_dates("date_of_joining", "contract_end_date") def validate_email(self): if self.company_email: From eb66b749b2b82f934c5de0b9dea52ec2972d25b6 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Sun, 4 Dec 2022 14:41:21 +0100 Subject: [PATCH 05/25] refactor: validate dates in accounts module --- erpnext/accounts/doctype/fiscal_year/fiscal_year.py | 13 ++----------- .../doctype/fiscal_year/test_fiscal_year.py | 4 +--- .../accounts/doctype/pricing_rule/pricing_rule.py | 5 ++--- erpnext/accounts/doctype/tax_rule/tax_rule.py | 6 +----- 4 files changed, 6 insertions(+), 22 deletions(-) diff --git a/erpnext/accounts/doctype/fiscal_year/fiscal_year.py b/erpnext/accounts/doctype/fiscal_year/fiscal_year.py index 4592421304..69cfe3109f 100644 --- a/erpnext/accounts/doctype/fiscal_year/fiscal_year.py +++ b/erpnext/accounts/doctype/fiscal_year/fiscal_year.py @@ -9,10 +9,6 @@ from frappe.model.document import Document from frappe.utils import add_days, add_years, cstr, getdate -class FiscalYearIncorrectDate(frappe.ValidationError): - pass - - class FiscalYear(Document): @frappe.whitelist() def set_as_default(self): @@ -53,23 +49,18 @@ class FiscalYear(Document): ) def validate_dates(self): + self.validate_from_to_dates("year_start_date", "year_end_date") if self.is_short_year: # Fiscal Year can be shorter than one year, in some jurisdictions # under certain circumstances. For example, in the USA and Germany. return - if getdate(self.year_start_date) > getdate(self.year_end_date): - frappe.throw( - _("Fiscal Year Start Date should be one year earlier than Fiscal Year End Date"), - FiscalYearIncorrectDate, - ) - date = getdate(self.year_start_date) + relativedelta(years=1) - relativedelta(days=1) if getdate(self.year_end_date) != date: frappe.throw( _("Fiscal Year End Date should be one year after Fiscal Year Start Date"), - FiscalYearIncorrectDate, + frappe.exceptions.InvalidDates, ) def on_update(self): diff --git a/erpnext/accounts/doctype/fiscal_year/test_fiscal_year.py b/erpnext/accounts/doctype/fiscal_year/test_fiscal_year.py index 6e946f7466..0fed1a17b6 100644 --- a/erpnext/accounts/doctype/fiscal_year/test_fiscal_year.py +++ b/erpnext/accounts/doctype/fiscal_year/test_fiscal_year.py @@ -7,8 +7,6 @@ import unittest import frappe from frappe.utils import now_datetime -from erpnext.accounts.doctype.fiscal_year.fiscal_year import FiscalYearIncorrectDate - test_ignore = ["Company"] @@ -26,7 +24,7 @@ class TestFiscalYear(unittest.TestCase): } ) - self.assertRaises(FiscalYearIncorrectDate, fy.insert) + self.assertRaises(frappe.exceptions.InvalidDates, fy.insert) def test_record_generator(): diff --git a/erpnext/accounts/doctype/pricing_rule/pricing_rule.py b/erpnext/accounts/doctype/pricing_rule/pricing_rule.py index ed46d85e3a..b666e0d7c6 100644 --- a/erpnext/accounts/doctype/pricing_rule/pricing_rule.py +++ b/erpnext/accounts/doctype/pricing_rule/pricing_rule.py @@ -10,7 +10,7 @@ import re import frappe from frappe import _, throw from frappe.model.document import Document -from frappe.utils import cint, flt, getdate +from frappe.utils import cint, flt apply_on_dict = {"Item Code": "items", "Item Group": "item_groups", "Brand": "brands"} @@ -184,8 +184,7 @@ class PricingRule(Document): if self.is_cumulative and not (self.valid_from and self.valid_upto): frappe.throw(_("Valid from and valid upto fields are mandatory for the cumulative")) - if self.valid_from and self.valid_upto and getdate(self.valid_from) > getdate(self.valid_upto): - frappe.throw(_("Valid from date must be less than valid upto date")) + self.validate_from_to_dates("valid_from", "valid_upto") def validate_condition(self): if ( diff --git a/erpnext/accounts/doctype/tax_rule/tax_rule.py b/erpnext/accounts/doctype/tax_rule/tax_rule.py index 4d201292ed..87c5e6d588 100644 --- a/erpnext/accounts/doctype/tax_rule/tax_rule.py +++ b/erpnext/accounts/doctype/tax_rule/tax_rule.py @@ -32,7 +32,7 @@ class TaxRule(Document): def validate(self): self.validate_tax_template() - self.validate_date() + self.validate_from_to_dates("from_date", "to_date") self.validate_filters() self.validate_use_for_shopping_cart() @@ -51,10 +51,6 @@ class TaxRule(Document): if not (self.sales_tax_template or self.purchase_tax_template): frappe.throw(_("Tax Template is mandatory.")) - def validate_date(self): - if self.from_date and self.to_date and self.from_date > self.to_date: - frappe.throw(_("From Date cannot be greater than To Date")) - def validate_filters(self): filters = { "tax_type": self.tax_type, From 2c4eb371a6f3dd72243f688b0cfdad27df464fc4 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Sun, 4 Dec 2022 15:15:07 +0100 Subject: [PATCH 06/25] refactor: validate dates in project and task --- erpnext/projects/doctype/project/project.py | 2 + erpnext/projects/doctype/task/task.py | 82 +++++++-------------- 2 files changed, 28 insertions(+), 56 deletions(-) diff --git a/erpnext/projects/doctype/project/project.py b/erpnext/projects/doctype/project/project.py index d80133c988..cbf2493d9a 100644 --- a/erpnext/projects/doctype/project/project.py +++ b/erpnext/projects/doctype/project/project.py @@ -42,6 +42,8 @@ class Project(Document): self.send_welcome_email() self.update_costing() self.update_percent_complete() + self.validate_from_to_dates("expected_start_date", "expected_end_date") + self.validate_from_to_dates("actual_start_date", "actual_end_date") def copy_from_template(self): """ diff --git a/erpnext/projects/doctype/task/task.py b/erpnext/projects/doctype/task/task.py index fa507854a6..936ff8eb73 100755 --- a/erpnext/projects/doctype/task/task.py +++ b/erpnext/projects/doctype/task/task.py @@ -16,10 +16,6 @@ class CircularReferenceError(frappe.ValidationError): pass -class EndDateCannotBeGreaterThanProjectEndDateError(frappe.ValidationError): - pass - - class Task(NestedSet): nsm_parent_field = "parent_task" @@ -34,8 +30,6 @@ class Task(NestedSet): def validate(self): self.validate_dates() - self.validate_parent_expected_end_date() - self.validate_parent_project_dates() self.validate_progress() self.validate_status() self.update_depends_on() @@ -43,51 +37,39 @@ class Task(NestedSet): self.validate_completed_on() def validate_dates(self): - if ( - self.exp_start_date - and self.exp_end_date - and getdate(self.exp_start_date) > getdate(self.exp_end_date) - ): - frappe.throw( - _("{0} can not be greater than {1}").format( - frappe.bold("Expected Start Date"), frappe.bold("Expected End Date") - ) - ) - - if ( - self.act_start_date - and self.act_end_date - and getdate(self.act_start_date) > getdate(self.act_end_date) - ): - frappe.throw( - _("{0} can not be greater than {1}").format( - frappe.bold("Actual Start Date"), frappe.bold("Actual End Date") - ) - ) + self.validate_from_to_dates("exp_start_date", "exp_end_date") + self.validate_from_to_dates("act_start_date", "act_end_date") + self.validate_parent_expected_end_date() + self.validate_parent_project_dates() def validate_parent_expected_end_date(self): - if self.parent_task: - parent_exp_end_date = frappe.db.get_value("Task", self.parent_task, "exp_end_date") - if parent_exp_end_date and getdate(self.get("exp_end_date")) > getdate(parent_exp_end_date): - frappe.throw( - _( - "Expected End Date should be less than or equal to parent task's Expected End Date {0}." - ).format(getdate(parent_exp_end_date)) - ) + if not self.parent_task: + return + + parent_exp_end_date = frappe.db.get_value("Task", self.parent_task, "exp_end_date") + if parent_exp_end_date and getdate(self.get("exp_end_date")) > getdate(parent_exp_end_date): + frappe.throw( + _( + "Expected End Date should be less than or equal to parent task's Expected End Date {0}." + ).format(getdate(parent_exp_end_date)), + frappe.exceptions.InvalidDates, + ) def validate_parent_project_dates(self): if not self.project or frappe.flags.in_test: return - expected_end_date = frappe.db.get_value("Project", self.project, "expected_end_date") - - if expected_end_date: - validate_project_dates( - getdate(expected_end_date), self, "exp_start_date", "exp_end_date", "Expected" - ) - validate_project_dates( - getdate(expected_end_date), self, "act_start_date", "act_end_date", "Actual" - ) + if project_end_date := frappe.db.get_value("Project", self.project, "expected_end_date"): + project_end_date = getdate(project_end_date) + for fieldname in ("exp_start_date", "exp_end_date", "act_start_date", "act_end_date"): + task_date = self.get(fieldname) + if task_date and date_diff(project_end_date, getdate(task_date)) < 0: + frappe.throw( + _("Task's {0} cannot be after Project's Expected End Date.").format( + _(self.meta.get_label(fieldname)) + ), + frappe.exceptions.InvalidDates, + ) def validate_status(self): if self.is_template and self.status != "Template": @@ -398,15 +380,3 @@ def add_multiple_tasks(data, parent): def on_doctype_update(): frappe.db.add_index("Task", ["lft", "rgt"]) - - -def validate_project_dates(project_end_date, task, task_start, task_end, actual_or_expected_date): - if task.get(task_start) and date_diff(project_end_date, getdate(task.get(task_start))) < 0: - frappe.throw( - _("Task's {0} Start Date cannot be after Project's End Date.").format(actual_or_expected_date) - ) - - if task.get(task_end) and date_diff(project_end_date, getdate(task.get(task_end))) < 0: - frappe.throw( - _("Task's {0} End Date cannot be after Project's End Date.").format(actual_or_expected_date) - ) From 31db0e7c79280fcf9725b8d9f7422b43a9533084 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Sun, 4 Dec 2022 15:28:38 +0100 Subject: [PATCH 07/25] refactor: validate parent_expected_end_date in Task --- erpnext/projects/doctype/task/task.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/erpnext/projects/doctype/task/task.py b/erpnext/projects/doctype/task/task.py index 936ff8eb73..79f1b3adb4 100755 --- a/erpnext/projects/doctype/task/task.py +++ b/erpnext/projects/doctype/task/task.py @@ -9,6 +9,7 @@ from frappe import _, throw from frappe.desk.form.assign_to import clear, close_all_assignments from frappe.model.mapper import get_mapped_doc from frappe.utils import add_days, cstr, date_diff, flt, get_link_to_form, getdate, today +from frappe.utils.data import format_date from frappe.utils.nestedset import NestedSet @@ -43,15 +44,18 @@ class Task(NestedSet): self.validate_parent_project_dates() def validate_parent_expected_end_date(self): - if not self.parent_task: + if not self.parent_task or not self.exp_end_date: return parent_exp_end_date = frappe.db.get_value("Task", self.parent_task, "exp_end_date") - if parent_exp_end_date and getdate(self.get("exp_end_date")) > getdate(parent_exp_end_date): + if not parent_exp_end_date: + return + + if getdate(self.exp_end_date) > getdate(parent_exp_end_date): frappe.throw( _( "Expected End Date should be less than or equal to parent task's Expected End Date {0}." - ).format(getdate(parent_exp_end_date)), + ).format(format_date(parent_exp_end_date)), frappe.exceptions.InvalidDates, ) From ef9d126254d4df91274b983b5a7e718d38d729f1 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 5 Dec 2022 10:17:19 +0530 Subject: [PATCH 08/25] fix: Allow item rate udpates for non-stock invoices --- .../purchase_invoice/purchase_invoice.py | 4 +- erpnext/controllers/buying_controller.py | 23 +++++----- erpnext/controllers/selling_controller.py | 45 ++++++++++--------- .../purchase_receipt/purchase_receipt.py | 4 +- 4 files changed, 41 insertions(+), 35 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 0a92820515..0e9f976106 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -231,7 +231,9 @@ class PurchaseInvoice(BuyingController): ) if ( - cint(frappe.db.get_single_value("Buying Settings", "maintain_same_rate")) and not self.is_return + cint(frappe.db.get_single_value("Buying Settings", "maintain_same_rate")) + and not self.is_return + and not self.is_internal_supplier ): self.validate_rate_with_reference_doc( [ diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py index 051460474a..2efa545736 100644 --- a/erpnext/controllers/buying_controller.py +++ b/erpnext/controllers/buying_controller.py @@ -322,17 +322,18 @@ class BuyingController(SubcontractingController): ) if self.is_internal_transfer(): - if rate != d.rate: - d.rate = rate - frappe.msgprint( - _( - "Row {0}: Item rate has been updated as per valuation rate since its an internal stock transfer" - ).format(d.idx), - alert=1, - ) - d.discount_percentage = 0.0 - d.discount_amount = 0.0 - d.margin_rate_or_amount = 0.0 + if self.doctype == "Purchase Receipt" or self.get("update_stock"): + if rate != d.rate: + d.rate = rate + frappe.msgprint( + _( + "Row {0}: Item rate has been updated as per valuation rate since its an internal stock transfer" + ).format(d.idx), + alert=1, + ) + d.discount_percentage = 0.0 + d.discount_amount = 0.0 + d.margin_rate_or_amount = 0.0 def validate_for_subcontracting(self): if self.is_subcontracted and self.get("is_old_subcontracting_flow"): diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py index 965335b1a3..0ebc8d4b4d 100644 --- a/erpnext/controllers/selling_controller.py +++ b/erpnext/controllers/selling_controller.py @@ -442,30 +442,31 @@ class SellingController(StockController): # For internal transfers use incoming rate as the valuation rate if self.is_internal_transfer(): - if d.doctype == "Packed Item": - incoming_rate = flt( - flt(d.incoming_rate, d.precision("incoming_rate")) * d.conversion_factor, - d.precision("incoming_rate"), - ) - if d.incoming_rate != incoming_rate: - d.incoming_rate = incoming_rate - else: - rate = flt( - flt(d.incoming_rate, d.precision("incoming_rate")) * d.conversion_factor, - d.precision("rate"), - ) - if d.rate != rate: - d.rate = rate - frappe.msgprint( - _( - "Row {0}: Item rate has been updated as per valuation rate since its an internal stock transfer" - ).format(d.idx), - alert=1, + if self.doctype == "Delivery Note" or self.get("update_stock"): + if d.doctype == "Packed Item": + incoming_rate = flt( + flt(d.incoming_rate, d.precision("incoming_rate")) * d.conversion_factor, + d.precision("incoming_rate"), ) + if d.incoming_rate != incoming_rate: + d.incoming_rate = incoming_rate + else: + rate = flt( + flt(d.incoming_rate, d.precision("incoming_rate")) * d.conversion_factor, + d.precision("rate"), + ) + if d.rate != rate: + d.rate = rate + frappe.msgprint( + _( + "Row {0}: Item rate has been updated as per valuation rate since its an internal stock transfer" + ).format(d.idx), + alert=1, + ) - d.discount_percentage = 0.0 - d.discount_amount = 0.0 - d.margin_rate_or_amount = 0.0 + d.discount_percentage = 0.0 + d.discount_amount = 0.0 + d.margin_rate_or_amount = 0.0 elif self.get("return_against"): # Get incoming rate of return entry from reference document diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index 673fcb526d..3739cb8c9d 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -173,7 +173,9 @@ class PurchaseReceipt(BuyingController): ) if ( - cint(frappe.db.get_single_value("Buying Settings", "maintain_same_rate")) and not self.is_return + cint(frappe.db.get_single_value("Buying Settings", "maintain_same_rate")) + and not self.is_return + and not self.is_internal_supplier ): self.validate_rate_with_reference_doc( [["Purchase Order", "purchase_order", "purchase_order_item"]] From 3814db02eb0d598e324ac73fcea9fca772d0fcd7 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 5 Dec 2022 16:22:59 +0530 Subject: [PATCH 09/25] fix: data import mandatory account_head, charge_type --- erpnext/accounts/doctype/sales_invoice/sales_invoice.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json index 18d2b5c995..b38bce7216 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json @@ -921,6 +921,7 @@ "fieldtype": "Table", "hide_days": 1, "hide_seconds": 1, + "label": "Sales Taxes and Charges", "oldfieldname": "other_charges", "oldfieldtype": "Table", "options": "Sales Taxes and Charges" @@ -2133,7 +2134,7 @@ "link_fieldname": "consolidated_invoice" } ], - "modified": "2022-11-17 17:17:10.883487", + "modified": "2022-12-05 16:18:14.532114", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice", From 713330cbf69bf285b6231c0ea603d6156edfde35 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Mon, 5 Dec 2022 18:15:13 +0530 Subject: [PATCH 10/25] fix: non empty FG batch picked while completing work order --- .../doctype/work_order/test_work_order.py | 24 ++++++++++++++++++- .../stock/doctype/stock_entry/stock_entry.py | 13 +++++----- 2 files changed, 30 insertions(+), 7 deletions(-) diff --git a/erpnext/manufacturing/doctype/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py index 694dc79d4a..36466ff6d7 100644 --- a/erpnext/manufacturing/doctype/work_order/test_work_order.py +++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py @@ -635,6 +635,10 @@ class TestWorkOrder(FrappeTestCase): bom.submit() bom_name = bom.name + ste1 = test_stock_entry.make_stock_entry( + item_code=rm1, target="_Test Warehouse - _TC", qty=32, basic_rate=5000.0 + ) + work_order = make_wo_order_test_record( item=fg_item, skip_transfer=True, planned_start_date=now(), qty=1 ) @@ -659,11 +663,29 @@ class TestWorkOrder(FrappeTestCase): work_order.insert() work_order.submit() self.assertEqual(work_order.has_batch_no, 1) - ste1 = frappe.get_doc(make_stock_entry(work_order.name, "Manufacture", 30)) + batches = frappe.get_all("Batch", filters={"reference_name": work_order.name}) + self.assertEqual(len(batches), 3) + batches = [batch.name for batch in batches] + + ste1 = frappe.get_doc(make_stock_entry(work_order.name, "Manufacture", 10)) for row in ste1.get("items"): if row.is_finished_item: self.assertEqual(row.item_code, fg_item) self.assertEqual(row.qty, 10) + self.assertTrue(row.batch_no in batches) + batches.remove(row.batch_no) + + ste1.submit() + + remaining_batches = [] + ste1 = frappe.get_doc(make_stock_entry(work_order.name, "Manufacture", 20)) + for row in ste1.get("items"): + if row.is_finished_item: + self.assertEqual(row.item_code, fg_item) + self.assertEqual(row.qty, 10) + remaining_batches.append(row.batch_no) + + self.assertEqual(sorted(remaining_batches), sorted(batches)) frappe.db.set_value("Manufacturing Settings", None, "make_serial_no_batch_from_work_order", 0) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 83c280f72e..f6c53f7bca 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -1545,6 +1545,7 @@ class StockEntry(StockController): "reference_name": self.pro_doc.name, "reference_doctype": self.pro_doc.doctype, "qty_to_produce": (">", 0), + "batch_qty": ("=", 0), } fields = ["qty_to_produce as qty", "produced_qty", "name"] @@ -2238,14 +2239,14 @@ class StockEntry(StockController): d.qty -= process_loss_dict[d.item_code][1] def set_serial_no_batch_for_finished_good(self): - args = {} + serial_nos = "" if self.pro_doc.serial_no: - self.get_serial_nos_for_fg(args) + serial_nos = self.get_serial_nos_for_fg() for row in self.items: if row.is_finished_item and row.item_code == self.pro_doc.production_item: - if args.get("serial_no"): - row.serial_no = "\n".join(args["serial_no"][0 : cint(row.qty)]) + if serial_nos: + row.serial_no = "\n".join(serial_nos[0 : cint(row.qty)]) def get_serial_nos_for_fg(self, args): fields = [ @@ -2258,14 +2259,14 @@ class StockEntry(StockController): filters = [ ["Stock Entry", "work_order", "=", self.work_order], ["Stock Entry", "purpose", "=", "Manufacture"], - ["Stock Entry", "docstatus", "=", 1], + ["Stock Entry", "docstatus", "<", 2], ["Stock Entry Detail", "item_code", "=", self.pro_doc.production_item], ] stock_entries = frappe.get_all("Stock Entry", fields=fields, filters=filters) if self.pro_doc.serial_no: - args["serial_no"] = self.get_available_serial_nos(stock_entries) + return self.get_available_serial_nos(stock_entries) def get_available_serial_nos(self, stock_entries): used_serial_nos = [] From 748c74ba5276b707125a361b2b241b7b7a978dd0 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Mon, 5 Dec 2022 18:24:22 +0100 Subject: [PATCH 11/25] fix: add translation variable order --- erpnext/controllers/status_updater.py | 2 +- erpnext/translations/de.csv | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/controllers/status_updater.py b/erpnext/controllers/status_updater.py index 6e7d2b33c2..bf077282bf 100644 --- a/erpnext/controllers/status_updater.py +++ b/erpnext/controllers/status_updater.py @@ -349,7 +349,7 @@ class StatusUpdater(Document): def warn_about_bypassing_with_role(self, item, qty_or_amount, role): action = _("Over Receipt/Delivery") if qty_or_amount == "qty" else _("Overbilling") - msg = _("{} of {} {} ignored for item {} because you have {} role.").format( + msg = _("{0} of {1} {2} ignored for item {3} because you have {4} role.").format( action, _(item["target_ref_field"].title()), frappe.bold(item["reduce_by"]), diff --git a/erpnext/translations/de.csv b/erpnext/translations/de.csv index 0fdd3c94a1..f1d830245f 100644 --- a/erpnext/translations/de.csv +++ b/erpnext/translations/de.csv @@ -9914,3 +9914,4 @@ Cost and Freight,Kosten und Fracht, Delivered at Place,Geliefert benannter Ort, Delivered at Place Unloaded,Geliefert benannter Ort entladen, Delivered Duty Paid,Geliefert verzollt, +{0} of {1} {2} ignored for item {3} because you have {4} role,"{0} von Artikel {3} mit {1} {2} wurde ignoriert, weil Sie die Rolle {4} haben." From a26a29f33b2f87a2f84db6290982cae3e3407de0 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Mon, 5 Dec 2022 19:49:05 +0100 Subject: [PATCH 12/25] fix: incorrect dates in test records --- erpnext/accounts/doctype/fiscal_year/test_fiscal_year.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/fiscal_year/test_fiscal_year.py b/erpnext/accounts/doctype/fiscal_year/test_fiscal_year.py index 0fed1a17b6..181406bad7 100644 --- a/erpnext/accounts/doctype/fiscal_year/test_fiscal_year.py +++ b/erpnext/accounts/doctype/fiscal_year/test_fiscal_year.py @@ -33,8 +33,8 @@ def test_record_generator(): "doctype": "Fiscal Year", "year": "_Test Short Fiscal Year 2011", "is_short_year": 1, - "year_end_date": "2011-04-01", - "year_start_date": "2011-12-31", + "year_start_date": "2011-04-01", + "year_end_date": "2011-12-31", } ] From d23b5d8f2fc9bdf2e0f267fbaf38bdf11167fc87 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Tue, 6 Dec 2022 12:58:07 +0530 Subject: [PATCH 13/25] ci: use mariadb 10.6 (#33220) https://github.com/frappe/frappe/pull/19116 [skip ci] --- .github/helper/install.sh | 13 ++++++------- .github/helper/site_config_mariadb.json | 2 +- .github/workflows/patch.yml | 2 +- .github/workflows/server-tests-mariadb.yml | 4 ++-- 4 files changed, 10 insertions(+), 11 deletions(-) diff --git a/.github/helper/install.sh b/.github/helper/install.sh index 2bb950fcfc..2b3d8cb9b3 100644 --- a/.github/helper/install.sh +++ b/.github/helper/install.sh @@ -24,15 +24,14 @@ fi if [ "$DB" == "mariadb" ];then - mysql --host 127.0.0.1 --port 3306 -u root -e "SET GLOBAL character_set_server = 'utf8mb4'" - mysql --host 127.0.0.1 --port 3306 -u root -e "SET GLOBAL collation_server = 'utf8mb4_unicode_ci'" + mysql --host 127.0.0.1 --port 3306 -u root -proot -e "SET GLOBAL character_set_server = 'utf8mb4'" + mysql --host 127.0.0.1 --port 3306 -u root -proot -e "SET GLOBAL collation_server = 'utf8mb4_unicode_ci'" - mysql --host 127.0.0.1 --port 3306 -u root -e "CREATE USER 'test_frappe'@'localhost' IDENTIFIED BY 'test_frappe'" - mysql --host 127.0.0.1 --port 3306 -u root -e "CREATE DATABASE test_frappe" - mysql --host 127.0.0.1 --port 3306 -u root -e "GRANT ALL PRIVILEGES ON \`test_frappe\`.* TO 'test_frappe'@'localhost'" + mysql --host 127.0.0.1 --port 3306 -u root -proot -e "CREATE USER 'test_frappe'@'localhost' IDENTIFIED BY 'test_frappe'" + mysql --host 127.0.0.1 --port 3306 -u root -proot -e "CREATE DATABASE test_frappe" + mysql --host 127.0.0.1 --port 3306 -u root -proot -e "GRANT ALL PRIVILEGES ON \`test_frappe\`.* TO 'test_frappe'@'localhost'" - mysql --host 127.0.0.1 --port 3306 -u root -e "UPDATE mysql.user SET Password=PASSWORD('travis') WHERE User='root'" - mysql --host 127.0.0.1 --port 3306 -u root -e "FLUSH PRIVILEGES" + mysql --host 127.0.0.1 --port 3306 -u root -proot -e "FLUSH PRIVILEGES" fi if [ "$DB" == "postgres" ];then diff --git a/.github/helper/site_config_mariadb.json b/.github/helper/site_config_mariadb.json index 948ad08bab..49e7fcf7da 100644 --- a/.github/helper/site_config_mariadb.json +++ b/.github/helper/site_config_mariadb.json @@ -9,7 +9,7 @@ "mail_password": "test", "admin_password": "admin", "root_login": "root", - "root_password": "travis", + "root_password": "root", "host_name": "http://test_site:8000", "install_apps": ["erpnext"], "throttle_user_limit": 100 diff --git a/.github/workflows/patch.yml b/.github/workflows/patch.yml index 9e06254bac..d5f0052744 100644 --- a/.github/workflows/patch.yml +++ b/.github/workflows/patch.yml @@ -25,7 +25,7 @@ jobs: mysql: image: mariadb:10.3 env: - MYSQL_ALLOW_EMPTY_PASSWORD: YES + MARIADB_ROOT_PASSWORD: 'root' ports: - 3306:3306 options: --health-cmd="mysqladmin ping" --health-interval=5s --health-timeout=2s --health-retries=3 diff --git a/.github/workflows/server-tests-mariadb.yml b/.github/workflows/server-tests-mariadb.yml index b40faa7d3b..bbb8a7e9fa 100644 --- a/.github/workflows/server-tests-mariadb.yml +++ b/.github/workflows/server-tests-mariadb.yml @@ -45,9 +45,9 @@ jobs: services: mysql: - image: mariadb:10.3 + image: mariadb:10.6 env: - MYSQL_ALLOW_EMPTY_PASSWORD: YES + MARIADB_ROOT_PASSWORD: 'root' ports: - 3306:3306 options: --health-cmd="mysqladmin ping" --health-interval=5s --health-timeout=2s --health-retries=3 From 19db7e2989f36d3dd2f6fce1ea03e21199665b21 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 6 Dec 2022 13:42:33 +0530 Subject: [PATCH 14/25] fix: replace sql code with fields list in get_cached_value --- erpnext/accounts/doctype/fiscal_year/fiscal_year.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/fiscal_year/fiscal_year.py b/erpnext/accounts/doctype/fiscal_year/fiscal_year.py index 4592421304..c3e83ad168 100644 --- a/erpnext/accounts/doctype/fiscal_year/fiscal_year.py +++ b/erpnext/accounts/doctype/fiscal_year/fiscal_year.py @@ -169,5 +169,6 @@ def auto_create_fiscal_year(): def get_from_and_to_date(fiscal_year): - fields = ["year_start_date as from_date", "year_end_date as to_date"] - return frappe.get_cached_value("Fiscal Year", fiscal_year, fields, as_dict=1) + fields = ["year_start_date", "year_end_date"] + cached_results = frappe.get_cached_value("Fiscal Year", fiscal_year, fields, as_dict=1) + return dict(from_date=cached_results.year_start_date, to_date=cached_results.year_end_date) From a6794c36062acb7963a387271e2f4b4800643a68 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 6 Dec 2022 13:44:54 +0530 Subject: [PATCH 15/25] fix: key error on p/l and balance sheet reports on foreign currency --- erpnext/accounts/report/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/report/utils.py b/erpnext/accounts/report/utils.py index 81fea28515..d3cd29013f 100644 --- a/erpnext/accounts/report/utils.py +++ b/erpnext/accounts/report/utils.py @@ -28,7 +28,7 @@ def get_currency(filters): filters["presentation_currency"] if filters.get("presentation_currency") else company_currency ) - report_date = filters.get("period_end_date") + report_date = filters.get("to_date") or filters.get("period_end_date") if not report_date: fiscal_year_to_date = get_from_and_to_date(filters.get("to_fiscal_year"))["to_date"] From e5566b31d5b7d4c057b43b5f0fa7d8d9119c942c Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 6 Dec 2022 13:59:45 +0530 Subject: [PATCH 16/25] chore: Consider bundle qty as well --- erpnext/stock/doctype/packed_item/packed_item.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/stock/doctype/packed_item/packed_item.py b/erpnext/stock/doctype/packed_item/packed_item.py index 02f8e903bb..d606751666 100644 --- a/erpnext/stock/doctype/packed_item/packed_item.py +++ b/erpnext/stock/doctype/packed_item/packed_item.py @@ -48,7 +48,7 @@ def make_packing_list(doc): update_packed_item_from_cancelled_doc(item_row, bundle_item, pi_row, doc) if set_price_from_children: # create/update bundle item wise price dict - update_product_bundle_rate(parent_items_price, pi_row) + update_product_bundle_rate(parent_items_price, pi_row, item_row) if parent_items_price: set_product_bundle_rate_amount(doc, parent_items_price) # set price in bundle item @@ -247,7 +247,7 @@ def get_cancelled_doc_packed_item_details(old_packed_items): return prev_doc_packed_items_map -def update_product_bundle_rate(parent_items_price, pi_row): +def update_product_bundle_rate(parent_items_price, pi_row, item_row): """ Update the price dict of Product Bundles based on the rates of the Items in the bundle. @@ -259,7 +259,7 @@ def update_product_bundle_rate(parent_items_price, pi_row): if not rate: parent_items_price[key] = 0.0 - parent_items_price[key] += flt(pi_row.rate * pi_row.qty) + parent_items_price[key] += flt((pi_row.rate * pi_row.qty) / item_row.stock_qty) def set_product_bundle_rate_amount(doc, parent_items_price): From b1242bc56c3485d37fd37627a3196a754abfb442 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 6 Dec 2022 15:14:48 +0530 Subject: [PATCH 17/25] chore: Update tests --- erpnext/stock/doctype/packed_item/test_packed_item.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/doctype/packed_item/test_packed_item.py b/erpnext/stock/doctype/packed_item/test_packed_item.py index ad7fd9a697..ad06732bc3 100644 --- a/erpnext/stock/doctype/packed_item/test_packed_item.py +++ b/erpnext/stock/doctype/packed_item/test_packed_item.py @@ -126,8 +126,8 @@ class TestPackedItem(FrappeTestCase): so.packed_items[1].rate = 200 so.save() - self.assertEqual(so.items[0].rate, 350) - self.assertEqual(so.items[0].amount, 700) + self.assertEqual(so.items[0].rate, 700) + self.assertEqual(so.items[0].amount, 1400) def test_newly_mapped_doc_packed_items(self): "Test impact on packed items in newly mapped DN from SO." From 861aa9e08a4c95bbe26afab14c7bdd61637f80cd Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Tue, 6 Dec 2022 16:52:45 +0530 Subject: [PATCH 18/25] feat: warehouse wise stock balance --- .../warehouse_wise_stock_balance/__init__.py | 0 .../warehouse_wise_stock_balance.js | 20 +++ .../warehouse_wise_stock_balance.json | 30 ++++ .../warehouse_wise_stock_balance.py | 89 ++++++++++ erpnext/stock/workspace/stock/stock.json | 163 ++++++++++-------- 5 files changed, 226 insertions(+), 76 deletions(-) create mode 100644 erpnext/stock/report/warehouse_wise_stock_balance/__init__.py create mode 100644 erpnext/stock/report/warehouse_wise_stock_balance/warehouse_wise_stock_balance.js create mode 100644 erpnext/stock/report/warehouse_wise_stock_balance/warehouse_wise_stock_balance.json create mode 100644 erpnext/stock/report/warehouse_wise_stock_balance/warehouse_wise_stock_balance.py diff --git a/erpnext/stock/report/warehouse_wise_stock_balance/__init__.py b/erpnext/stock/report/warehouse_wise_stock_balance/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/stock/report/warehouse_wise_stock_balance/warehouse_wise_stock_balance.js b/erpnext/stock/report/warehouse_wise_stock_balance/warehouse_wise_stock_balance.js new file mode 100644 index 0000000000..58a043ec20 --- /dev/null +++ b/erpnext/stock/report/warehouse_wise_stock_balance/warehouse_wise_stock_balance.js @@ -0,0 +1,20 @@ +// Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt +/* eslint-disable */ + +frappe.query_reports["Warehouse Wise Stock Balance"] = { + "filters": [ + { + "fieldname":"company", + "label": __("Company"), + "fieldtype": "Link", + "options": "Company", + "reqd": 1, + "default": frappe.defaults.get_user_default("Company") + } + ], + "initial_depth": 3, + "tree": true, + "parent_field": "parent_warehouse", + "name_field": "warehouse" +}; diff --git a/erpnext/stock/report/warehouse_wise_stock_balance/warehouse_wise_stock_balance.json b/erpnext/stock/report/warehouse_wise_stock_balance/warehouse_wise_stock_balance.json new file mode 100644 index 0000000000..4f7ec65de3 --- /dev/null +++ b/erpnext/stock/report/warehouse_wise_stock_balance/warehouse_wise_stock_balance.json @@ -0,0 +1,30 @@ +{ + "add_total_row": 0, + "columns": [], + "creation": "2022-12-06 14:15:31.924345", + "disable_prepared_report": 0, + "disabled": 0, + "docstatus": 0, + "doctype": "Report", + "filters": [], + "idx": 0, + "is_standard": "Yes", + "json": "{}", + "modified": "2022-12-06 14:16:55.969214", + "modified_by": "Administrator", + "module": "Stock", + "name": "Warehouse Wise Stock Balance", + "owner": "Administrator", + "prepared_report": 0, + "ref_doctype": "Stock Ledger Entry", + "report_name": "Warehouse Wise Stock Balance", + "report_type": "Script Report", + "roles": [ + { + "role": "Stock User" + }, + { + "role": "Accounts Manager" + } + ] +} \ No newline at end of file diff --git a/erpnext/stock/report/warehouse_wise_stock_balance/warehouse_wise_stock_balance.py b/erpnext/stock/report/warehouse_wise_stock_balance/warehouse_wise_stock_balance.py new file mode 100644 index 0000000000..81700099fa --- /dev/null +++ b/erpnext/stock/report/warehouse_wise_stock_balance/warehouse_wise_stock_balance.py @@ -0,0 +1,89 @@ +# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from typing import Any, Dict, List, Optional, TypedDict + +import frappe +from frappe import _ +from frappe.query_builder.functions import Sum + + +class StockBalanceFilter(TypedDict): + company: Optional[str] + warehouse: Optional[str] + + +SLEntry = Dict[str, Any] + + +def execute(filters=None): + columns, data = [], [] + columns = get_columns() + data = get_data(filters) + + return columns, data + + +def get_warehouse_wise_balance(filters: StockBalanceFilter) -> List[SLEntry]: + sle = frappe.qb.DocType("Stock Ledger Entry") + + query = ( + frappe.qb.from_(sle) + .select(sle.warehouse, Sum(sle.stock_value_difference).as_("stock_balance")) + .where((sle.docstatus < 2) & (sle.is_cancelled == 0)) + .groupby(sle.warehouse) + ) + + if filters.get("company"): + query = query.where(sle.company == filters.get("company")) + + data = query.run(as_list=True) + return frappe._dict(data) if data else frappe._dict() + + +def get_warehouses(report_filters: StockBalanceFilter): + return frappe.get_all( + "Warehouse", + fields=["name", "parent_warehouse", "is_group"], + filters={"company": report_filters.company, "disabled": 0}, + order_by="lft", + ) + + +def get_data(filters: StockBalanceFilter): + warehouse_balance = get_warehouse_wise_balance(filters) + warehouses = get_warehouses(filters) + + for warehouse in warehouses: + warehouse["stock_balance"] = warehouse_balance.get(warehouse.name, 0) + + update_indent(warehouses) + + return warehouses + + +def update_indent(warehouses): + for warehouse in warehouses: + + def add_indent(warehouse, indent): + warehouse.indent = indent + for child in warehouses: + if child.parent_warehouse == warehouse.name: + warehouse.stock_balance += child.stock_balance + add_indent(child, indent + 1) + + if warehouse.is_group: + add_indent(warehouse, warehouse.indent or 0) + + +def get_columns(): + return [ + { + "label": _("Warehouse"), + "fieldname": "name", + "fieldtype": "Link", + "options": "Warehouse", + "width": 200, + }, + {"label": _("Stock Balance"), "fieldname": "stock_balance", "fieldtype": "Float", "width": 150}, + ] diff --git a/erpnext/stock/workspace/stock/stock.json b/erpnext/stock/workspace/stock/stock.json index ed33067e73..de5e6de8f1 100644 --- a/erpnext/stock/workspace/stock/stock.json +++ b/erpnext/stock/workspace/stock/stock.json @@ -5,7 +5,7 @@ "label": "Warehouse wise Stock Value" } ], - "content": "[{\"type\":\"onboarding\",\"data\":{\"onboarding_name\":\"Stock\",\"col\":12}},{\"type\":\"chart\",\"data\":{\"chart_name\":\"Warehouse wise Stock Value\",\"col\":12}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"Quick Access\",\"col\":12}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Item\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Material Request\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Stock Entry\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Purchase Receipt\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Delivery Note\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Stock Ledger\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Stock Balance\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Dashboard\",\"col\":3}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"Masters & Reports\",\"col\":12}},{\"type\":\"card\",\"data\":{\"card_name\":\"Items and Pricing\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Stock Transactions\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Stock Reports\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Settings\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Serial No and Batch\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Tools\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Key Reports\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Other Reports\",\"col\":4}}]", + "content": "[{\"type\":\"onboarding\",\"data\":{\"onboarding_name\":\"Stock\",\"col\":12}},{\"type\":\"chart\",\"data\":{\"chart_name\":\"Warehouse wise Stock Value\",\"col\":12}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"Quick Access\",\"col\":12}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Item\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Material Request\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Stock Entry\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Purchase Receipt\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Delivery Note\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Stock Ledger\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Stock Balance\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Dashboard\",\"col\":3}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"Masters & Reports\",\"col\":12}},{\"type\":\"card\",\"data\":{\"card_name\":\"Items and Pricing\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Stock Transactions\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Stock Reports\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Settings\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Serial No and Batch\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Tools\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Key Reports\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Other Reports\",\"col\":4}}]", "creation": "2020-03-02 15:43:10.096528", "docstatus": 0, "doctype": "Workspace", @@ -207,80 +207,6 @@ "onboard": 0, "type": "Link" }, - { - "hidden": 0, - "is_query_report": 0, - "label": "Stock Reports", - "link_count": 0, - "onboard": 0, - "type": "Card Break" - }, - { - "dependencies": "Item", - "hidden": 0, - "is_query_report": 1, - "label": "Stock Ledger", - "link_count": 0, - "link_to": "Stock Ledger", - "link_type": "Report", - "onboard": 1, - "type": "Link" - }, - { - "dependencies": "Item", - "hidden": 0, - "is_query_report": 1, - "label": "Stock Balance", - "link_count": 0, - "link_to": "Stock Balance", - "link_type": "Report", - "onboard": 1, - "type": "Link" - }, - { - "dependencies": "Item", - "hidden": 0, - "is_query_report": 1, - "label": "Stock Projected Qty", - "link_count": 0, - "link_to": "Stock Projected Qty", - "link_type": "Report", - "onboard": 1, - "type": "Link" - }, - { - "dependencies": "Item", - "hidden": 0, - "is_query_report": 0, - "label": "Stock Summary", - "link_count": 0, - "link_to": "stock-balance", - "link_type": "Page", - "onboard": 0, - "type": "Link" - }, - { - "dependencies": "Item", - "hidden": 0, - "is_query_report": 1, - "label": "Stock Ageing", - "link_count": 0, - "link_to": "Stock Ageing", - "link_type": "Report", - "onboard": 0, - "type": "Link" - }, - { - "dependencies": "Item", - "hidden": 0, - "is_query_report": 1, - "label": "Item Price Stock", - "link_count": 0, - "link_to": "Item Price Stock", - "link_type": "Report", - "onboard": 0, - "type": "Link" - }, { "hidden": 0, "is_query_report": 0, @@ -705,15 +631,100 @@ "link_type": "Report", "onboard": 0, "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Stock Reports", + "link_count": 7, + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "Item", + "hidden": 0, + "is_query_report": 1, + "label": "Stock Ledger", + "link_count": 0, + "link_to": "Stock Ledger", + "link_type": "Report", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "Item", + "hidden": 0, + "is_query_report": 1, + "label": "Stock Balance", + "link_count": 0, + "link_to": "Stock Balance", + "link_type": "Report", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "Item", + "hidden": 0, + "is_query_report": 1, + "label": "Stock Projected Qty", + "link_count": 0, + "link_to": "Stock Projected Qty", + "link_type": "Report", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "Item", + "hidden": 0, + "is_query_report": 0, + "label": "Stock Summary", + "link_count": 0, + "link_to": "stock-balance", + "link_type": "Page", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "Item", + "hidden": 0, + "is_query_report": 1, + "label": "Stock Ageing", + "link_count": 0, + "link_to": "Stock Ageing", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "Item", + "hidden": 0, + "is_query_report": 1, + "label": "Item Price Stock", + "link_count": 0, + "link_to": "Item Price Stock", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Warehouse Wise Stock Balance", + "link_count": 0, + "link_to": "Warehouse Wise Stock Balance", + "link_type": "Report", + "onboard": 0, + "type": "Link" } ], - "modified": "2022-01-13 17:47:38.339931", + "modified": "2022-12-06 17:03:56.397272", "modified_by": "Administrator", "module": "Stock", "name": "Stock", "owner": "Administrator", "parent_page": "", "public": 1, + "quick_lists": [], "restrict_to_domain": "", "roles": [], "sequence_id": 24.0, From a6fe79f29df03e4393e5f9d2da493bac59d340ac Mon Sep 17 00:00:00 2001 From: wuzhouquan Date: Wed, 7 Dec 2022 15:04:56 +0800 Subject: [PATCH 19/25] fix: bugs in zh.csv --- erpnext/translations/zh.csv | 38 ++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/erpnext/translations/zh.csv b/erpnext/translations/zh.csv index 716f1f2f31..d1f1b07f11 100644 --- a/erpnext/translations/zh.csv +++ b/erpnext/translations/zh.csv @@ -453,11 +453,11 @@ Cancel Subscription,取消订阅, Cancel the journal entry {0} first,首先取消日记条目{0}, Canceled,取消, "Cannot Submit, Employees left to mark attendance",无法提交,不能为已离职员工登记考勤, -Cannot be a fixed asset item as Stock Ledger is created.,不能成为股票分类账创建的固定资产项目。, +Cannot be a fixed asset item as Stock Ledger is created.,不能成为库存分类账创建的固定资产项目。, Cannot cancel because submitted Stock Entry {0} exists,不能取消,因为提交的仓储记录{0}已经存在, Cannot cancel transaction for Completed Work Order.,无法取消已完成工单的交易。, Cannot cancel {0} {1} because Serial No {2} does not belong to the warehouse {3},无法取消{0} {1},因为序列号{2}不属于仓库{3}, -Cannot change Attributes after stock transaction. Make a new Item and transfer stock to the new Item,股票交易后不能更改属性。创建一个新项目并将库存转移到新项目, +Cannot change Attributes after stock transaction. Make a new Item and transfer stock to the new Item,库存交易后不能更改属性。创建一个新项目并将库存转移到新项目, Cannot change Fiscal Year Start Date and Fiscal Year End Date once the Fiscal Year is saved.,财年保存后便不能更改财年开始日期和结束日期, Cannot change Service Stop Date for item in row {0},无法更改行{0}中项目的服务停止日期, Cannot change Variant properties after stock transaction. You will have to make a new Item to do this.,存货业务发生后不能更改变体物料的属性。需要新建新物料。, @@ -2309,7 +2309,7 @@ Receivable Account,应收账款, Received,收到, Received On,收到的, Received Quantity,收到的数量, -Received Stock Entries,收到的股票条目, +Received Stock Entries,收到的库存条目, Receiver List is empty. Please create Receiver List,接收人列表为空。请创建接收人列表, Recipients,收件人, Reconcile,核消(对帐), @@ -2783,7 +2783,7 @@ State,州, State/UT Tax,州/ UT税, Statement of Account,对账单, Status must be one of {0},状态必须是{0}中的一个, -Stock,股票, +Stock,库存, Stock Adjustment,库存调整, Stock Analytics,库存分析, Stock Assets,库存资产, @@ -2963,7 +2963,7 @@ The folio numbers are not matching,作品集编号不匹配, The holiday on {0} is not between From Date and To Date,在{0}这个节日之间没有从日期和结束日期, The name of the institute for which you are setting up this system.,对于要为其建立这个系统的该机构的名称。, The name of your company for which you are setting up this system.,贵公司的名称, -The number of shares and the share numbers are inconsistent,股份数量和股票数量不一致, +The number of shares and the share numbers are inconsistent,股份数量和库存数量不一致, The payment gateway account in plan {0} is different from the payment gateway account in this payment request,计划{0}中的支付网关帐户与此付款请求中的支付网关帐户不同, The selected BOMs are not for the same item,所选物料清单不能用于同一个物料, The selected item cannot have Batch,所选物料不能有批次, @@ -3514,7 +3514,7 @@ or,或, Ageing Range 4,老化范围4, Allocated amount cannot be greater than unadjusted amount,分配的金额不能大于未调整的金额, Allocated amount cannot be negative,分配数量不能为负数, -"Difference Account must be a Asset/Liability type account, since this Stock Entry is an Opening Entry",差异账户必须是资产/负债类型账户,因为此股票分录是开仓分录, +"Difference Account must be a Asset/Liability type account, since this Stock Entry is an Opening Entry",差异账户必须是资产/负债类型账户,因为此库存分录是开仓分录, Error in some rows,某些行出错, Import Successful,导入成功, Please save first,请先保存, @@ -3531,7 +3531,7 @@ Duplicate entry against the item code {0} and manufacturer {1},项目代码{0} Invalid GSTIN! The input you've entered doesn't match the GSTIN format for UIN Holders or Non-Resident OIDAR Service Providers,GSTIN无效!您输入的输入与UIN持有人或非居民OIDAR服务提供商的GSTIN格式不符, Invoice Grand Total,发票总计, Last carbon check date cannot be a future date,最后的碳检查日期不能是未来的日期, -Make Stock Entry,进入股票, +Make Stock Entry,进入库存, Quality Feedback,质量反馈, Quality Feedback Template,质量反馈模板, Rules for applying different promotional schemes.,适用不同促销计划的规则。, @@ -3626,7 +3626,7 @@ BOM 2,BOM 2, BOM Comparison Tool,BOM比较工具, BOM recursion: {0} cannot be child of {1},BOM递归:{0}不能是{1}的子代, BOM recursion: {0} cannot be parent or child of {1},BOM递归:{0}不能是{1}的父级或子级, -Back to Home,回到家, +Back to Home,回到主页, Back to Messages,回到消息, Bank Data mapper doesn't exist,银行数据映射器不存在, Bank Details,银行明细, @@ -3786,7 +3786,7 @@ Help,帮助, Help Article,帮助文章, "Helps you keep tracks of Contracts based on Supplier, Customer and Employee",帮助您根据供应商,客户和员工记录合同, Helps you manage appointments with your leads,帮助您管理潜在客户的约会, -Home,家, +Home,主页, IBAN is not valid,IBAN无效, Import Data from CSV / Excel files.,从CSV / Excel文件导入数据。, In Progress,进行中, @@ -4064,8 +4064,8 @@ Start Time,开始时间, Status,状态, Status must be Cancelled or Completed,状态必须已取消或已完成, Stock Balance Report,库存余额报告, -Stock Entry has been already created against this Pick List,已经根据此选择列表创建了股票输入, -Stock Ledger ID,股票分类帐编号, +Stock Entry has been already created against this Pick List,已经根据此选择列表创建了库存输入, +Stock Ledger ID,库存分类帐编号, Stock Value ({0}) and Account Balance ({1}) are out of sync for account {2} and it's linked warehouses.,库存值({0})和帐户余额({1})与帐户{2}及其链接的仓库不同步。, Stores - {0},商店-{0}, Student with email {0} does not exist,电子邮件{0}的学生不存在, @@ -6127,7 +6127,7 @@ Procedure Template,程序模板, Procedure Prescription,程序处方, Service Unit,服务单位, Consumables,耗材, -Consume Stock,消费股票, +Consume Stock,消费库存, Invoice Consumables Separately,发票耗材分开, Consumption Invoiced,消费发票, Consumable Total Amount,耗材总量, @@ -8285,8 +8285,8 @@ Out of AMC,出资产管理公司, Warranty Period (Days),保修期限(天数), Serial No Details,序列号信息, MAT-STE-.YYYY.-,MAT-STE-.YYYY.-, -Stock Entry Type,股票进入类型, -Stock Entry (Outward GIT),股票进入(外向GIT), +Stock Entry Type,库存进入类型, +Stock Entry (Outward GIT),库存进入(外向GIT), Material Consumption for Manufacture,生产所需的材料消耗, Repack,包装, Send to Subcontractor,发送给分包商, @@ -8318,8 +8318,8 @@ Serial No / Batch,序列号/批次, BOM No. for a Finished Good Item,成品物料的物料清单编号, Material Request used to make this Stock Entry,创建此手工库存移动的材料申请, Subcontracted Item,外包物料, -Against Stock Entry,反对股票进入, -Stock Entry Child,股票入境儿童, +Against Stock Entry,反对库存进入, +Stock Entry Child,库存入境儿童, PO Supplied Item,PO提供的物品, Reference Purchase Receipt,参考购买收据, Stock Ledger Entry,库存分类帐分录, @@ -8571,7 +8571,7 @@ Serial No Service Contract Expiry,序列号/年度保养合同过期, Serial No Status,序列号状态, Serial No Warranty Expiry,序列号/保修到期, Stock Ageing,库存账龄, -Stock and Account Value Comparison,股票和账户价值比较, +Stock and Account Value Comparison,库存和账户价值比较, Stock Projected Qty,预期可用库存, Student and Guardian Contact Details,学生和监护人联系方式, Student Batch-Wise Attendance,学生按批考勤, @@ -9655,7 +9655,7 @@ Raise Material Request When Stock Reaches Re-order Level,库存达到再订购 Notify by Email on Creation of Automatic Material Request,通过电子邮件通知创建自动物料请求, Allow Material Transfer from Delivery Note to Sales Invoice,允许物料从交货单转移到销售发票, Allow Material Transfer from Purchase Receipt to Purchase Invoice,允许从收货到采购发票的物料转移, -Freeze Stocks Older Than (Days),冻结大于(天)的股票, +Freeze Stocks Older Than (Days),冻结大于(天)的库存, Role Allowed to Edit Frozen Stock,允许角色编辑冻结库存, The unallocated amount of Payment Entry {0} is greater than the Bank Transaction's unallocated amount,付款条目{0}的未分配金额大于银行交易的未分配金额, Payment Received,已收到付款, @@ -9698,7 +9698,7 @@ Creating {} out of {} {},在{} {}中创建{}, Item {0} {1},项目{0} {1}, Last Stock Transaction for item {0} under warehouse {1} was on {2}.,仓库{1}下项目{0}的上次库存交易在{2}上。, Stock Transactions for Item {0} under warehouse {1} cannot be posted before this time.,在此之前,不能过帐仓库{1}下物料{0}的库存交易。, -Posting future stock transactions are not allowed due to Immutable Ledger,由于总帐不可变,不允许过帐未来的股票交易, +Posting future stock transactions are not allowed due to Immutable Ledger,由于总帐不可变,不允许过帐未来的库存交易, A BOM with name {0} already exists for item {1}.,项目{1}的名称为{0}的BOM已存在。, {0}{1} Did you rename the item? Please contact Administrator / Tech support,{0} {1}您是否重命名了该项目?请联系管理员/技术支持, At row #{0}: the sequence id {1} cannot be less than previous row sequence id {2},在第{0}行:序列ID {1}不能小于上一行的序列ID {2}, From 922375c3baf2a5028e60033ee268bded9677037d Mon Sep 17 00:00:00 2001 From: MOHAMMED NIYAS <76736615+niyazrazak@users.noreply.github.com> Date: Wed, 7 Dec 2022 17:50:35 +0530 Subject: [PATCH 20/25] feat: get lead with WhatsApp number --- erpnext/crm/doctype/lead/lead.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/crm/doctype/lead/lead.py b/erpnext/crm/doctype/lead/lead.py index 0d12499771..361e13cd1f 100644 --- a/erpnext/crm/doctype/lead/lead.py +++ b/erpnext/crm/doctype/lead/lead.py @@ -453,6 +453,7 @@ def get_lead_with_phone_number(number): "Lead", or_filters={ "phone": ["like", "%{}".format(number)], + "whatsapp_no": ["like", "%{}".format(number)], "mobile_no": ["like", "%{}".format(number)], }, limit=1, From cf1e3dc8eac0df6731c333ad207cfeece514b8ab Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 6 Dec 2022 17:13:11 +0530 Subject: [PATCH 21/25] fix: index error on customer master --- erpnext/selling/doctype/customer/customer.py | 21 +++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/erpnext/selling/doctype/customer/customer.py b/erpnext/selling/doctype/customer/customer.py index d0eb3774e2..60c33567be 100644 --- a/erpnext/selling/doctype/customer/customer.py +++ b/erpnext/selling/doctype/customer/customer.py @@ -6,7 +6,7 @@ import json import frappe import frappe.defaults -from frappe import _, msgprint +from frappe import _, msgprint, qb from frappe.contacts.address_and_contact import ( delete_contact_and_address, load_address_and_contact, @@ -732,12 +732,15 @@ def make_address(args, is_primary_address=1): @frappe.validate_and_sanitize_search_inputs def get_customer_primary_contact(doctype, txt, searchfield, start, page_len, filters): customer = filters.get("customer") - return frappe.db.sql( - """ - select `tabContact`.name from `tabContact`, `tabDynamic Link` - where `tabContact`.name = `tabDynamic Link`.parent and `tabDynamic Link`.link_name = %(customer)s - and `tabDynamic Link`.link_doctype = 'Customer' - and `tabContact`.name like %(txt)s - """, - {"customer": customer, "txt": "%%%s%%" % txt}, + + con = qb.DocType("Contact") + dlink = qb.DocType("Dynamic Link") + + return ( + qb.from_(con) + .join(dlink) + .on(con.name == dlink.parent) + .select(con.name, con.full_name, con.email_id) + .where((dlink.link_name == customer) & (con.name.like(f"%{txt}%"))) + .run() ) From 632c08f7e0b0c6a932457afdfa484f7668e9a9e5 Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Mon, 5 Dec 2022 22:47:05 +0530 Subject: [PATCH 22/25] fix: order status in `Production Planning Report` --- .../production_planning_report.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/erpnext/manufacturing/report/production_planning_report/production_planning_report.py b/erpnext/manufacturing/report/production_planning_report/production_planning_report.py index 16c25ce7e6..109d9ab656 100644 --- a/erpnext/manufacturing/report/production_planning_report/production_planning_report.py +++ b/erpnext/manufacturing/report/production_planning_report/production_planning_report.py @@ -49,7 +49,7 @@ class ProductionPlanReport(object): parent.bom_no, parent.fg_warehouse.as_("warehouse"), ) - .where(parent.status.notin(["Completed", "Stopped"])) + .where(parent.status.notin(["Completed", "Stopped", "Closed"])) ) if order_by == "Planned Start Date": @@ -79,10 +79,11 @@ class ProductionPlanReport(object): query = query.where(child.parent.isin(self.filters.docnames)) if doctype == "Sales Order": - query = query.select( - child.delivery_date, - parent.base_grand_total, - ).where((child.stock_qty > child.produced_qty) & (parent.per_delivered < 100.0)) + query = query.select(child.delivery_date, parent.base_grand_total,).where( + (child.stock_qty > child.produced_qty) + & (parent.per_delivered < 100.0) + & (parent.status.notin(["Completed", "Closed"])) + ) if order_by == "Delivery Date": query = query.orderby(child.delivery_date, order=Order.asc) @@ -91,7 +92,9 @@ class ProductionPlanReport(object): elif doctype == "Material Request": query = query.select(child.schedule_date,).where( - (parent.per_ordered < 100) & (parent.material_request_type == "Manufacture") + (parent.per_ordered < 100) + & (parent.material_request_type == "Manufacture") + & (parent.status != "Stopped") ) if order_by == "Required Date": From f598da7c81998dab9607c57017ee3a51fd2d6d66 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Thu, 8 Dec 2022 00:55:45 +0530 Subject: [PATCH 23/25] fix: total value in Warehouse Wise Stock Balance --- .../warehouse_wise_stock_balance.py | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/erpnext/stock/report/warehouse_wise_stock_balance/warehouse_wise_stock_balance.py b/erpnext/stock/report/warehouse_wise_stock_balance/warehouse_wise_stock_balance.py index 81700099fa..d364b577a2 100644 --- a/erpnext/stock/report/warehouse_wise_stock_balance/warehouse_wise_stock_balance.py +++ b/erpnext/stock/report/warehouse_wise_stock_balance/warehouse_wise_stock_balance.py @@ -45,7 +45,7 @@ def get_warehouses(report_filters: StockBalanceFilter): return frappe.get_all( "Warehouse", fields=["name", "parent_warehouse", "is_group"], - filters={"company": report_filters.company, "disabled": 0}, + filters={"company": report_filters.company}, order_by="lft", ) @@ -55,9 +55,10 @@ def get_data(filters: StockBalanceFilter): warehouses = get_warehouses(filters) for warehouse in warehouses: - warehouse["stock_balance"] = warehouse_balance.get(warehouse.name, 0) + warehouse.stock_balance = warehouse_balance.get(warehouse.name, 0) or 0.0 update_indent(warehouses) + set_balance_in_parent(warehouses) return warehouses @@ -69,13 +70,26 @@ def update_indent(warehouses): warehouse.indent = indent for child in warehouses: if child.parent_warehouse == warehouse.name: - warehouse.stock_balance += child.stock_balance add_indent(child, indent + 1) if warehouse.is_group: add_indent(warehouse, warehouse.indent or 0) +def set_balance_in_parent(warehouses): + # sort warehouses by indent in descending order + warehouses = sorted(warehouses, key=lambda x: x.get("indent", 0), reverse=1) + + for warehouse in warehouses: + + def update_balance(warehouse, balance): + for parent in warehouses: + if warehouse.parent_warehouse == parent.name: + parent.stock_balance += balance + + update_balance(warehouse, warehouse.stock_balance) + + def get_columns(): return [ { From 0b86b1baca3cd6ba4001f3a4ec747a9aa49d569a Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Thu, 8 Dec 2022 16:40:13 +0530 Subject: [PATCH 24/25] refactor: make payments app a soft dependency (#33245) refactor: make payment app a soft dependency --- .github/helper/site_config_mariadb.json | 2 +- .../payment_gateway_account.js | 1 + .../doctype/payment_request/payment_request.py | 15 +++++++++++---- .../subscription_plan/subscription_plan.js | 6 +++++- .../e_commerce_settings/e_commerce_settings.js | 6 ++++++ .../gocardless_settings/gocardless_settings.js | 3 +++ .../gocardless_settings/gocardless_settings.json | 3 +-- .../gocardless_settings/gocardless_settings.py | 6 +++++- .../doctype/mpesa_settings/mpesa_settings.js | 2 ++ .../doctype/mpesa_settings/mpesa_settings.py | 5 ++++- .../erpnext_integrations/stripe_integration.py | 9 ++++++++- erpnext/hooks.py | 1 - erpnext/public/js/utils.js | 12 +++++++++++- erpnext/utilities/__init__.py | 16 ++++++++++++++++ 14 files changed, 74 insertions(+), 13 deletions(-) diff --git a/.github/helper/site_config_mariadb.json b/.github/helper/site_config_mariadb.json index 49e7fcf7da..8c86f73729 100644 --- a/.github/helper/site_config_mariadb.json +++ b/.github/helper/site_config_mariadb.json @@ -11,6 +11,6 @@ "root_login": "root", "root_password": "root", "host_name": "http://test_site:8000", - "install_apps": ["erpnext"], + "install_apps": ["payments", "erpnext"], "throttle_user_limit": 100 } diff --git a/erpnext/accounts/doctype/payment_gateway_account/payment_gateway_account.js b/erpnext/accounts/doctype/payment_gateway_account/payment_gateway_account.js index 8f09bc3691..aff067eab8 100644 --- a/erpnext/accounts/doctype/payment_gateway_account/payment_gateway_account.js +++ b/erpnext/accounts/doctype/payment_gateway_account/payment_gateway_account.js @@ -3,6 +3,7 @@ frappe.ui.form.on('Payment Gateway Account', { refresh(frm) { + erpnext.utils.check_payments_app(); if(!frm.doc.__islocal) { frm.set_df_property('payment_gateway', 'read_only', 1); } diff --git a/erpnext/accounts/doctype/payment_request/payment_request.py b/erpnext/accounts/doctype/payment_request/payment_request.py index 8665b70956..d82083cea0 100644 --- a/erpnext/accounts/doctype/payment_request/payment_request.py +++ b/erpnext/accounts/doctype/payment_request/payment_request.py @@ -9,7 +9,6 @@ from frappe import _ from frappe.model.document import Document from frappe.utils import flt, get_url, nowdate from frappe.utils.background_jobs import enqueue -from payments.utils import get_payment_gateway_controller from erpnext.accounts.doctype.payment_entry.payment_entry import ( get_company_defaults, @@ -19,6 +18,14 @@ from erpnext.accounts.doctype.subscription_plan.subscription_plan import get_pla from erpnext.accounts.party import get_party_account, get_party_bank_account from erpnext.accounts.utils import get_account_currency from erpnext.erpnext_integrations.stripe_integration import create_stripe_subscription +from erpnext.utilities import payment_app_import_guard + + +def _get_payment_gateway_controller(*args, **kwargs): + with payment_app_import_guard(): + from payments.utils import get_payment_gateway_controller + + return get_payment_gateway_controller(*args, **kwargs) class PaymentRequest(Document): @@ -107,7 +114,7 @@ class PaymentRequest(Document): self.request_phone_payment() def request_phone_payment(self): - controller = get_payment_gateway_controller(self.payment_gateway) + controller = _get_payment_gateway_controller(self.payment_gateway) request_amount = self.get_request_amount() payment_record = dict( @@ -156,7 +163,7 @@ class PaymentRequest(Document): def payment_gateway_validation(self): try: - controller = get_payment_gateway_controller(self.payment_gateway) + controller = _get_payment_gateway_controller(self.payment_gateway) if hasattr(controller, "on_payment_request_submission"): return controller.on_payment_request_submission(self) else: @@ -189,7 +196,7 @@ class PaymentRequest(Document): ) data.update({"company": frappe.defaults.get_defaults().company}) - controller = get_payment_gateway_controller(self.payment_gateway) + controller = _get_payment_gateway_controller(self.payment_gateway) controller.validate_transaction_currency(self.currency) if hasattr(controller, "validate_minimum_transaction_amount"): diff --git a/erpnext/accounts/doctype/subscription_plan/subscription_plan.js b/erpnext/accounts/doctype/subscription_plan/subscription_plan.js index 7d6f2aed10..00727f103f 100644 --- a/erpnext/accounts/doctype/subscription_plan/subscription_plan.js +++ b/erpnext/accounts/doctype/subscription_plan/subscription_plan.js @@ -5,5 +5,9 @@ frappe.ui.form.on('Subscription Plan', { price_determination: function(frm) { frm.toggle_reqd("cost", frm.doc.price_determination === 'Fixed rate'); frm.toggle_reqd("price_list", frm.doc.price_determination === 'Based on price list'); - } + }, + + subscription_plan: function (frm) { + erpnext.utils.check_payments_app(); + }, }); diff --git a/erpnext/e_commerce/doctype/e_commerce_settings/e_commerce_settings.js b/erpnext/e_commerce/doctype/e_commerce_settings/e_commerce_settings.js index 69b9cfaa68..c37fa2f6ea 100644 --- a/erpnext/e_commerce/doctype/e_commerce_settings/e_commerce_settings.js +++ b/erpnext/e_commerce/doctype/e_commerce_settings/e_commerce_settings.js @@ -48,5 +48,11 @@ frappe.ui.form.on("E Commerce Settings", { frm.set_value('default_customer_group', ''); frm.set_value('quotation_series', ''); } + }, + + enable_checkout: function(frm) { + if (frm.doc.enable_checkout) { + erpnext.utils.check_payments_app(); + } } }); diff --git a/erpnext/erpnext_integrations/doctype/gocardless_settings/gocardless_settings.js b/erpnext/erpnext_integrations/doctype/gocardless_settings/gocardless_settings.js index b649d9d6cc..241129719b 100644 --- a/erpnext/erpnext_integrations/doctype/gocardless_settings/gocardless_settings.js +++ b/erpnext/erpnext_integrations/doctype/gocardless_settings/gocardless_settings.js @@ -2,4 +2,7 @@ // For license information, please see license.txt frappe.ui.form.on('GoCardless Settings', { + refresh: function(frm) { + erpnext.utils.check_payments_app(); + } }); diff --git a/erpnext/erpnext_integrations/doctype/gocardless_settings/gocardless_settings.json b/erpnext/erpnext_integrations/doctype/gocardless_settings/gocardless_settings.json index 9738106a30..cca36536ac 100644 --- a/erpnext/erpnext_integrations/doctype/gocardless_settings/gocardless_settings.json +++ b/erpnext/erpnext_integrations/doctype/gocardless_settings/gocardless_settings.json @@ -173,7 +173,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2018-02-12 14:18:47.209114", + "modified": "2022-02-12 14:18:47.209114", "modified_by": "Administrator", "module": "ERPNext Integrations", "name": "GoCardless Settings", @@ -201,7 +201,6 @@ "write": 1 } ], - "quick_entry": 1, "read_only": 0, "read_only_onload": 0, "show_name_in_global_search": 0, diff --git a/erpnext/erpnext_integrations/doctype/gocardless_settings/gocardless_settings.py b/erpnext/erpnext_integrations/doctype/gocardless_settings/gocardless_settings.py index f9a293fc30..4a29a6a21d 100644 --- a/erpnext/erpnext_integrations/doctype/gocardless_settings/gocardless_settings.py +++ b/erpnext/erpnext_integrations/doctype/gocardless_settings/gocardless_settings.py @@ -10,7 +10,8 @@ from frappe import _ from frappe.integrations.utils import create_request_log from frappe.model.document import Document from frappe.utils import call_hook_method, cint, flt, get_url -from payments.utils import create_payment_gateway + +from erpnext.utilities import payment_app_import_guard class GoCardlessSettings(Document): @@ -30,6 +31,9 @@ class GoCardlessSettings(Document): frappe.throw(e) def on_update(self): + with payment_app_import_guard(): + from payments.utils import create_payment_gateway + create_payment_gateway( "GoCardless-" + self.gateway_name, settings="GoCardLess Settings", controller=self.gateway_name ) diff --git a/erpnext/erpnext_integrations/doctype/mpesa_settings/mpesa_settings.js b/erpnext/erpnext_integrations/doctype/mpesa_settings/mpesa_settings.js index 7c8ae5c802..447d720ca2 100644 --- a/erpnext/erpnext_integrations/doctype/mpesa_settings/mpesa_settings.js +++ b/erpnext/erpnext_integrations/doctype/mpesa_settings/mpesa_settings.js @@ -7,6 +7,8 @@ frappe.ui.form.on('Mpesa Settings', { }, refresh: function(frm) { + erpnext.utils.check_payments_app(); + frappe.realtime.on("refresh_mpesa_dashboard", function(){ frm.reload_doc(); frm.events.setup_account_balance_html(frm); diff --git a/erpnext/erpnext_integrations/doctype/mpesa_settings/mpesa_settings.py b/erpnext/erpnext_integrations/doctype/mpesa_settings/mpesa_settings.py index b534783864..a298e11eaf 100644 --- a/erpnext/erpnext_integrations/doctype/mpesa_settings/mpesa_settings.py +++ b/erpnext/erpnext_integrations/doctype/mpesa_settings/mpesa_settings.py @@ -9,13 +9,13 @@ from frappe import _ from frappe.integrations.utils import create_request_log from frappe.model.document import Document from frappe.utils import call_hook_method, fmt_money, get_request_site_address -from payments.utils import create_payment_gateway from erpnext.erpnext_integrations.doctype.mpesa_settings.mpesa_connector import MpesaConnector from erpnext.erpnext_integrations.doctype.mpesa_settings.mpesa_custom_fields import ( create_custom_pos_fields, ) from erpnext.erpnext_integrations.utils import create_mode_of_payment +from erpnext.utilities import payment_app_import_guard class MpesaSettings(Document): @@ -30,6 +30,9 @@ class MpesaSettings(Document): ) def on_update(self): + with payment_app_import_guard(): + from payments.utils import create_payment_gateway + create_custom_pos_fields() create_payment_gateway( "Mpesa-" + self.payment_gateway_name, diff --git a/erpnext/erpnext_integrations/stripe_integration.py b/erpnext/erpnext_integrations/stripe_integration.py index 2d7e8a5d31..634e5c2e89 100644 --- a/erpnext/erpnext_integrations/stripe_integration.py +++ b/erpnext/erpnext_integrations/stripe_integration.py @@ -2,12 +2,16 @@ # For license information, please see license.txt import frappe -import stripe from frappe import _ from frappe.integrations.utils import create_request_log +from erpnext.utilities import payment_app_import_guard + def create_stripe_subscription(gateway_controller, data): + with payment_app_import_guard(): + import stripe + stripe_settings = frappe.get_doc("Stripe Settings", gateway_controller) stripe_settings.data = frappe._dict(data) @@ -35,6 +39,9 @@ def create_stripe_subscription(gateway_controller, data): def create_subscription_on_stripe(stripe_settings): + with payment_app_import_guard(): + import stripe + items = [] for payment_plan in stripe_settings.payment_plans: plan = frappe.db.get_value("Subscription Plan", payment_plan.plan, "product_price_id") diff --git a/erpnext/hooks.py b/erpnext/hooks.py index 92601b3444..fd19d2585c 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -8,7 +8,6 @@ app_email = "info@erpnext.com" app_license = "GNU General Public License (v3)" source_link = "https://github.com/frappe/erpnext" app_logo_url = "/assets/erpnext/images/erpnext-logo.svg" -required_apps = ["payments"] develop_version = "14.x.x-develop" diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js index 6d64625270..d37b7bb43b 100755 --- a/erpnext/public/js/utils.js +++ b/erpnext/public/js/utils.js @@ -333,8 +333,18 @@ $.extend(erpnext.utils, { } frappe.ui.form.make_quick_entry(doctype, null, null, new_doc); }); - } + }, + // check if payments app is installed on site, if not warn user. + check_payments_app: () => { + if (frappe.boot.versions && !frappe.boot.versions.payments) { + const marketplace_link = 'Marketplace' + const github_link = 'GitHub' + const msg = __("payments app is not installed. Please install it from {0} or {1}", [marketplace_link, github_link]) + frappe.msgprint(msg); + } + + }, }); erpnext.utils.select_alternate_items = function(opts) { diff --git a/erpnext/utilities/__init__.py b/erpnext/utilities/__init__.py index c2b4229f17..24bfdc63af 100644 --- a/erpnext/utilities/__init__.py +++ b/erpnext/utilities/__init__.py @@ -1,6 +1,9 @@ ## temp utility +from contextlib import contextmanager + import frappe +from frappe import _ from frappe.utils import cstr from erpnext.utilities.activation import get_level @@ -35,3 +38,16 @@ def get_site_info(site_info): domain = frappe.get_cached_value("Company", cstr(company), "domain") return {"company": company, "domain": domain, "activation": get_level()} + + +@contextmanager +def payment_app_import_guard(): + marketplace_link = 'Marketplace' + github_link = 'GitHub' + msg = _("payments app is not installed. Please install it from {} or {}").format( + marketplace_link, github_link + ) + try: + yield + except ImportError: + frappe.throw(msg, title=_("Missing Payments App")) From b6bd408f19fd7b65c8a41fe05c467789eb91b399 Mon Sep 17 00:00:00 2001 From: Sabu Siyad Date: Thu, 8 Dec 2022 16:47:50 +0530 Subject: [PATCH 25/25] fix(ecommerce): remove query parameters from referer inclusion of query parameters results in logic failure example: - logic check if referrer is `all-products` - `http://shop.example/all-products` -> `all-products`, valid outcome - `http://shop.example/all-products?start=1` -> `all-products?start=1`, invalid outcome Signed-off-by: Sabu Siyad --- erpnext/setup/doctype/item_group/item_group.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/setup/doctype/item_group/item_group.py b/erpnext/setup/doctype/item_group/item_group.py index 411176b70a..95bbf84616 100644 --- a/erpnext/setup/doctype/item_group/item_group.py +++ b/erpnext/setup/doctype/item_group/item_group.py @@ -152,7 +152,7 @@ def get_parent_item_groups(item_group_name, from_item=False): if from_item and frappe.request.environ.get("HTTP_REFERER"): # base page after 'Home' will vary on Item page - last_page = frappe.request.environ["HTTP_REFERER"].split("/")[-1] + last_page = frappe.request.environ["HTTP_REFERER"].split("/")[-1].split("?")[0] if last_page and last_page in ("shop-by-category", "all-products"): base_nav_page_title = " ".join(last_page.split("-")).title() base_nav_page = {"name": _(base_nav_page_title), "route": "/" + last_page}