From 44f97dfd5e2c839769d0bf3d542aacd82d34ad1b Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 6 Dec 2021 20:36:48 +0530 Subject: [PATCH 01/27] fix: TDS Monthly payable report (cherry picked from commit d106d59c3f750a2545cb0c82f86c75fd88ab8276) --- .../report/tds_payable_monthly/tds_payable_monthly.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.py b/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.py index a3a45d1e79c..caee1a10bbb 100644 --- a/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.py +++ b/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.py @@ -36,12 +36,16 @@ def get_result(filters, tds_docs, tds_accounts, tax_category_map): posting_date = entry.posting_date voucher_type = entry.voucher_type + if not tax_withholding_category: + tax_withholding_category = supplier_map.get(supplier, {}).get('tax_withholding_category') + rate = tax_rate_map.get(tax_withholding_category) + if entry.account in tds_accounts: tds_deducted += (entry.credit - entry.debit) total_amount_credited += (entry.credit - entry.debit) - if rate and tds_deducted: + if tds_deducted: row = { 'pan' if frappe.db.has_column('Supplier', 'pan') else 'tax_id': supplier_map.get(supplier, {}).get('pan'), 'supplier': supplier_map.get(supplier, {}).get('name') @@ -67,7 +71,7 @@ def get_result(filters, tds_docs, tds_accounts, tax_category_map): def get_supplier_pan_map(): supplier_map = frappe._dict() - suppliers = frappe.db.get_all('Supplier', fields=['name', 'pan', 'supplier_type', 'supplier_name']) + suppliers = frappe.db.get_all('Supplier', fields=['name', 'pan', 'supplier_type', 'supplier_name', 'tax_withholding_category']) for d in suppliers: supplier_map[d.name] = d From fac357ebe9d2ecbc3838fb15e134c5800d3ab88b Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 6 Dec 2021 15:05:20 +0530 Subject: [PATCH 02/27] fix: Ageing in AR/AP report for advances (cherry picked from commit 3dabac15edf4baedfc3da288400e16933d497e70) --- .../report/accounts_receivable/accounts_receivable.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py index 353f9087f1b..a990f23cd6b 100755 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py @@ -545,7 +545,9 @@ class ReceivablePayableReport(object): def set_ageing(self, row): if self.filters.ageing_based_on == "Due Date": - entry_date = row.due_date + # use posting date as a fallback for advances posted via journal and payment entry + # when ageing viewed by due date + entry_date = row.due_date or row.posting_date elif self.filters.ageing_based_on == "Supplier Invoice Date": entry_date = row.bill_date else: From 8fe5326c8ea5fb54956254d30f8fecdcfab4184b Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 13 Dec 2021 21:10:03 +0530 Subject: [PATCH 03/27] fix: validate if asset account is set against company (#28849) --- .../doctype/purchase_invoice/purchase_invoice.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 14cbc5916c7..33fbd748c7b 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -295,8 +295,15 @@ class PurchaseInvoice(BuyingController): item.expense_account = stock_not_billed_account elif item.is_fixed_asset and not is_cwip_accounting_enabled(asset_category): - item.expense_account = get_asset_category_account('fixed_asset_account', item=item.item_code, + asset_category_account = get_asset_category_account('fixed_asset_account', item=item.item_code, company = self.company) + if not asset_category_account: + form_link = get_link_to_form('Asset Category', asset_category) + throw( + _("Please set Fixed Asset Account in {} against {}.").format(form_link, self.company), + title=_("Missing Account") + ) + item.expense_account = asset_category_account elif item.is_fixed_asset and item.pr_detail: item.expense_account = asset_received_but_not_billed elif not item.expense_account and for_validate: From e10eea931bf20b5ebfe39303f1c312ad4a990438 Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Thu, 2 Dec 2021 01:18:30 +0530 Subject: [PATCH 04/27] fix: Fix 'Adjust Asset Value' button (cherry picked from commit 6f1cf94c9fb2c7f7abda9802c4868b427a339ed9) --- erpnext/assets/doctype/asset/asset.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index beb46f37ab1..f30b93fa27a 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -733,7 +733,7 @@ def create_asset_repair(asset, asset_name): @frappe.whitelist() def create_asset_adjustment(asset, asset_category, company): - asset_maintenance = frappe.get_doc("Asset Value Adjustment") + asset_maintenance = frappe.new_doc("Asset Value Adjustment") asset_maintenance.update({ "asset": asset, "company": company, From 414558aa7080f41e61ed362fa037c96732bb09ab Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Thu, 2 Dec 2021 01:19:35 +0530 Subject: [PATCH 05/27] fix: Rename variable (cherry picked from commit 5b224f841b0419d0250f02549300e0754fa816b5) --- erpnext/assets/doctype/asset/asset.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index f30b93fa27a..bb0e11533fa 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -733,13 +733,13 @@ def create_asset_repair(asset, asset_name): @frappe.whitelist() def create_asset_adjustment(asset, asset_category, company): - asset_maintenance = frappe.new_doc("Asset Value Adjustment") - asset_maintenance.update({ + asset_value_adjustment = frappe.new_doc("Asset Value Adjustment") + asset_value_adjustment.update({ "asset": asset, "company": company, "asset_category": asset_category }) - return asset_maintenance + return asset_value_adjustment @frappe.whitelist() def transfer_asset(args): From cefd0185a63dfe12a7b616f0f3f64d77fe091d4e Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Thu, 2 Dec 2021 01:22:18 +0530 Subject: [PATCH 06/27] fix: Rename function (cherry picked from commit 4629308d94b4fc2a667a7eb7d526b2a810bb486a) --- erpnext/assets/doctype/asset/asset.js | 6 +++--- erpnext/assets/doctype/asset/asset.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/erpnext/assets/doctype/asset/asset.js b/erpnext/assets/doctype/asset/asset.js index c2b1bbcf142..153f5c537a2 100644 --- a/erpnext/assets/doctype/asset/asset.js +++ b/erpnext/assets/doctype/asset/asset.js @@ -110,7 +110,7 @@ frappe.ui.form.on('Asset', { if (frm.doc.status != 'Fully Depreciated') { frm.add_custom_button(__("Adjust Asset Value"), function() { - frm.trigger("create_asset_adjustment"); + frm.trigger("create_asset_value_adjustment"); }, __("Manage")); } @@ -322,14 +322,14 @@ frappe.ui.form.on('Asset', { }); }, - create_asset_adjustment: function(frm) { + create_asset_value_adjustment: function(frm) { frappe.call({ args: { "asset": frm.doc.name, "asset_category": frm.doc.asset_category, "company": frm.doc.company }, - method: "erpnext.assets.doctype.asset.asset.create_asset_adjustment", + method: "erpnext.assets.doctype.asset.asset.create_asset_value_adjustment", freeze: 1, callback: function(r) { var doclist = frappe.model.sync(r.message); diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index bb0e11533fa..17a554bbbfd 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -732,7 +732,7 @@ def create_asset_repair(asset, asset_name): return asset_repair @frappe.whitelist() -def create_asset_adjustment(asset, asset_category, company): +def create_asset_value_adjustment(asset, asset_category, company): asset_value_adjustment = frappe.new_doc("Asset Value Adjustment") asset_value_adjustment.update({ "asset": asset, From 1c96c6fe0811cde264c563cfe6fe9bbeef53acde Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Wed, 8 Dec 2021 10:39:35 +0530 Subject: [PATCH 07/27] fix: hsn-wise summary is incorrect if an invoice has repeated item code (cherry picked from commit e74739a67777ec78134fa7ffff6c29d12ccf83de) --- .../hsn_wise_summary_of_outward_supplies.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/erpnext/regional/report/hsn_wise_summary_of_outward_supplies/hsn_wise_summary_of_outward_supplies.py b/erpnext/regional/report/hsn_wise_summary_of_outward_supplies/hsn_wise_summary_of_outward_supplies.py index 17d611738d3..ac78a8107df 100644 --- a/erpnext/regional/report/hsn_wise_summary_of_outward_supplies/hsn_wise_summary_of_outward_supplies.py +++ b/erpnext/regional/report/hsn_wise_summary_of_outward_supplies/hsn_wise_summary_of_outward_supplies.py @@ -115,9 +115,11 @@ def get_items(filters): items = frappe.db.sql(""" select - `tabSales Invoice Item`.name, `tabSales Invoice Item`.base_price_list_rate, - `tabSales Invoice Item`.gst_hsn_code, `tabSales Invoice Item`.stock_qty, - `tabSales Invoice Item`.stock_uom, `tabSales Invoice Item`.base_net_amount, + `tabSales Invoice Item`.gst_hsn_code, + `tabSales Invoice Item`.stock_uom, + sum(`tabSales Invoice Item`.stock_qty) as stock_qty, + sum(`tabSales Invoice Item`.base_net_amount) as base_net_amount, + sum(`tabSales Invoice Item`.base_price_list_rate) as base_price_list_rate, `tabSales Invoice Item`.parent, `tabSales Invoice Item`.item_code, `tabGST HSN Code`.description from `tabSales Invoice`, `tabSales Invoice Item`, `tabGST HSN Code` @@ -125,6 +127,8 @@ def get_items(filters): and `tabSales Invoice`.docstatus = 1 and `tabSales Invoice Item`.gst_hsn_code is not NULL and `tabSales Invoice Item`.gst_hsn_code = `tabGST HSN Code`.name %s %s + group by + `tabSales Invoice Item`.parent, `tabSales Invoice Item`.item_code """ % (conditions, match_conditions), filters, as_dict=1) From 1f41c7fed1251d136ff22b1a11c8fc2e0803ee58 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Sun, 12 Dec 2021 20:47:48 +0530 Subject: [PATCH 08/27] test: test_hsn_summary_for_invoice_with_duplicate_items (cherry picked from commit 6a239c8ea3686de3263d7fd7471ac8ad2c9f99d9) --- ...st_hsn_wise_summary_of_outward_supplies.py | 89 +++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 erpnext/regional/report/hsn_wise_summary_of_outward_supplies/test_hsn_wise_summary_of_outward_supplies.py diff --git a/erpnext/regional/report/hsn_wise_summary_of_outward_supplies/test_hsn_wise_summary_of_outward_supplies.py b/erpnext/regional/report/hsn_wise_summary_of_outward_supplies/test_hsn_wise_summary_of_outward_supplies.py new file mode 100644 index 00000000000..86dc458bdb1 --- /dev/null +++ b/erpnext/regional/report/hsn_wise_summary_of_outward_supplies/test_hsn_wise_summary_of_outward_supplies.py @@ -0,0 +1,89 @@ +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + + +from unittest import TestCase + +import frappe + +from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice +from erpnext.regional.doctype.gstr_3b_report.test_gstr_3b_report import ( + make_company as setup_company, +) +from erpnext.regional.doctype.gstr_3b_report.test_gstr_3b_report import ( + make_customers as setup_customers, +) +from erpnext.regional.doctype.gstr_3b_report.test_gstr_3b_report import ( + set_account_heads as setup_gst_settings, +) +from erpnext.regional.report.hsn_wise_summary_of_outward_supplies.hsn_wise_summary_of_outward_supplies import ( + execute as run_report, +) +from erpnext.stock.doctype.item.test_item import make_item + + +class TestHSNWiseSummaryReport(TestCase): + @classmethod + def setUpClass(cls): + setup_company() + setup_customers() + setup_gst_settings() + make_item("Golf Car", properties={ "gst_hsn_code": "999900" }) + + @classmethod + def tearDownClass(cls): + frappe.db.rollback() + + def test_hsn_summary_for_invoice_with_duplicate_items(self): + si = create_sales_invoice( + company="_Test Company GST", + customer = "_Test GST Customer", + currency = "INR", + warehouse = "Finished Goods - _GST", + debit_to = "Debtors - _GST", + income_account = "Sales - _GST", + expense_account = "Cost of Goods Sold - _GST", + cost_center = "Main - _GST", + do_not_save=1 + ) + + si.items = [] + si.append("items", { + "item_code": "Golf Car", + "gst_hsn_code": "999900", + "qty": "1", + "rate": "120", + "cost_center": "Main - _GST" + }) + si.append("items", { + "item_code": "Golf Car", + "gst_hsn_code": "999900", + "qty": "1", + "rate": "140", + "cost_center": "Main - _GST" + }) + si.append("taxes", { + "charge_type": "On Net Total", + "account_head": "Output Tax IGST - _GST", + "cost_center": "Main - _GST", + "description": "IGST @ 18.0", + "rate": 18 + }) + si.posting_date = "2020-11-17" + si.submit() + si.reload() + + [columns, data] = run_report(filters=frappe._dict({ + "company": "_Test Company GST", + "gst_hsn_code": "999900", + "company_gstin": si.company_gstin, + "from_date": si.posting_date, + "to_date": si.posting_date + })) + + filtered_rows = list(filter(lambda row: row['gst_hsn_code'] == "999900", data)) + self.assertTrue(filtered_rows) + + hsn_row = filtered_rows[0] + self.assertEquals(hsn_row['stock_qty'], 2.0) + self.assertEquals(hsn_row['total_amount'], 306.8) From 46a6c8dbfe437834a353de22c7753ab7950b7eda Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 14 Dec 2021 11:10:57 +0530 Subject: [PATCH 09/27] fix: migration hashes (#28850) Co-authored-by: Ankush Menat --- .../doctype/taxjar_settings/taxjar_settings.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/erpnext/erpnext_integrations/doctype/taxjar_settings/taxjar_settings.json b/erpnext/erpnext_integrations/doctype/taxjar_settings/taxjar_settings.json index ae1f36e73fa..6afd3f70381 100644 --- a/erpnext/erpnext_integrations/doctype/taxjar_settings/taxjar_settings.json +++ b/erpnext/erpnext_integrations/doctype/taxjar_settings/taxjar_settings.json @@ -115,8 +115,7 @@ ], "issingle": 1, "links": [], - "migration_hash": "8ca1ea3309ed28547b19da8e6e27e96f", - "modified": "2021-11-30 11:17:24.647979", + "modified": "2021-11-30 12:17:24.647979", "modified_by": "Administrator", "module": "ERPNext Integrations", "name": "TaxJar Settings", From 57986879515f9cec6b5cca1c4be21b7602968fb3 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 14 Dec 2021 12:16:42 +0530 Subject: [PATCH 10/27] fix: packing slip incorrect item value (bp #28862) (cherry picked from commit 534c64abcd1bf74a3c4d9fe322516a1410812ab8) Co-authored-by: Ankush Menat --- .../packing_slip_item/packing_slip_item.json | 543 ++++-------------- 1 file changed, 116 insertions(+), 427 deletions(-) diff --git a/erpnext/stock/doctype/packing_slip_item/packing_slip_item.json b/erpnext/stock/doctype/packing_slip_item/packing_slip_item.json index 29c4193f9e9..4270839bfdb 100644 --- a/erpnext/stock/doctype/packing_slip_item/packing_slip_item.json +++ b/erpnext/stock/doctype/packing_slip_item/packing_slip_item.json @@ -1,451 +1,140 @@ { - "allow_copy": 0, - "allow_import": 0, - "allow_rename": 0, - "autoname": "hash", - "beta": 0, - "creation": "2013-04-08 13:10:16", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "Document", - "editable_grid": 1, - "engine": "InnoDB", + "actions": [], + "autoname": "hash", + "creation": "2013-04-08 13:10:16", + "doctype": "DocType", + "document_type": "Document", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "item_code", + "column_break_2", + "item_name", + "batch_no", + "desc_section", + "description", + "quantity_section", + "qty", + "net_weight", + "column_break_10", + "stock_uom", + "weight_uom", + "page_break", + "dn_detail" + ], "fields": [ { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "item_code", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 1, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Item Code", - "length": 0, - "no_copy": 0, - "options": "Item", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": "100px", - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "unique": 0, + "fieldname": "item_code", + "fieldtype": "Link", + "in_global_search": 1, + "in_list_view": 1, + "label": "Item Code", + "options": "Item", + "print_width": "100px", + "reqd": 1, "width": "100px" - }, + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_2", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "fieldname": "column_break_2", + "fieldtype": "Column Break" + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "item_name", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Item Name", - "length": 0, - "no_copy": 0, - "options": "item_code.item_name", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": "200px", - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0, + "fetch_from": "item_code.item_name", + "fieldname": "item_name", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Item Name", + "print_width": "200px", + "read_only": 1, "width": "200px" - }, + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "batch_no", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Batch No", - "length": 0, - "no_copy": 0, - "options": "Batch", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "fieldname": "batch_no", + "fieldtype": "Link", + "label": "Batch No", + "options": "Batch" + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 1, - "columns": 0, - "fieldname": "desc_section", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Description", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "collapsible": 1, + "fieldname": "desc_section", + "fieldtype": "Section Break", + "label": "Description" + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "description", - "fieldtype": "Text Editor", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Description", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "fieldname": "description", + "fieldtype": "Text Editor", + "label": "Description" + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "quantity_section", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Quantity", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "fieldname": "quantity_section", + "fieldtype": "Section Break", + "label": "Quantity" + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "qty", - "fieldtype": "Float", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Quantity", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": "100px", - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "unique": 0, + "fieldname": "qty", + "fieldtype": "Float", + "in_list_view": 1, + "label": "Quantity", + "print_width": "100px", + "reqd": 1, "width": "100px" - }, + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "net_weight", - "fieldtype": "Float", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Net Weight", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": "100px", - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0, + "fieldname": "net_weight", + "fieldtype": "Float", + "in_list_view": 1, + "label": "Net Weight", + "print_width": "100px", "width": "100px" - }, + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_10", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "fieldname": "column_break_10", + "fieldtype": "Column Break" + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "stock_uom", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "UOM", - "length": 0, - "no_copy": 0, - "options": "UOM", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": "100px", - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0, + "fieldname": "stock_uom", + "fieldtype": "Link", + "label": "UOM", + "options": "UOM", + "print_width": "100px", + "read_only": 1, "width": "100px" - }, + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "weight_uom", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Weight UOM", - "length": 0, - "no_copy": 0, - "options": "UOM", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": "100px", - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0, + "fieldname": "weight_uom", + "fieldtype": "Link", + "label": "Weight UOM", + "options": "UOM", + "print_width": "100px", "width": "100px" - }, + }, { - "allow_on_submit": 1, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "page_break", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Page Break", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "allow_on_submit": 1, + "default": "0", + "fieldname": "page_break", + "fieldtype": "Check", + "in_list_view": 1, + "label": "Page Break" + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "dn_detail", - "fieldtype": "Data", - "hidden": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "DN Detail", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 + "fieldname": "dn_detail", + "fieldtype": "Data", + "hidden": 1, + "in_list_view": 1, + "label": "DN Detail" } - ], - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 1, - "image_view": 0, - "in_create": 0, - - "is_submittable": 0, - "issingle": 0, - "istable": 1, - "max_attachments": 0, - "modified": "2018-06-01 07:21:58.220980", - "modified_by": "Administrator", - "module": "Stock", - "name": "Packing Slip Item", - "owner": "Administrator", - "permissions": [], - "quick_entry": 0, - "read_only": 0, - "read_only_onload": 0, - "show_name_in_global_search": 0, - "track_changes": 1, - "track_seen": 0 + ], + "idx": 1, + "istable": 1, + "links": [], + "modified": "2021-12-14 01:22:00.715935", + "modified_by": "Administrator", + "module": "Stock", + "name": "Packing Slip Item", + "naming_rule": "Random", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 } \ No newline at end of file From 1a986f0b11a5658caf48ebd7a2fff7466b49bd49 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 14 Dec 2021 12:44:42 +0530 Subject: [PATCH 11/27] fix(UX): hide RM table(Job Card) if material transfer is against work order (backport #28746) * fix(UX): hide RM table(Job Card) if material transfer is against work order (#28746) (cherry picked from commit cc8c0595e29d0ab21753d95fbe7db703a50296d4) # Conflicts: # erpnext/manufacturing/doctype/job_card/job_card.js * chore: resolve conflicts Co-authored-by: Noah Jacob Co-authored-by: Ankush Menat --- erpnext/manufacturing/doctype/job_card/job_card.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/erpnext/manufacturing/doctype/job_card/job_card.js b/erpnext/manufacturing/doctype/job_card/job_card.js index bb258812b21..768cea33dae 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.js +++ b/erpnext/manufacturing/doctype/job_card/job_card.js @@ -76,6 +76,15 @@ frappe.ui.form.on('Job Card', { frm.trigger("prepare_timer_buttons"); } frm.trigger("setup_quality_inspection"); + + if (frm.doc.work_order) { + frappe.db.get_value('Work Order', frm.doc.work_order, + 'transfer_material_against').then((r) => { + if (r.message.transfer_material_against == 'Work Order') { + frm.set_df_property('items', 'hidden', 1); + } + }); + } }, setup_quality_inspection: function(frm) { From 55db2f88089ddb769db98997e189eb1cb267d160 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sun, 12 Dec 2021 12:55:30 +0530 Subject: [PATCH 12/27] fix: KSA QR field rename patch (cherry picked from commit 14d03a94460d0c7b7e05e1c2a580e0b27cade2eb) --- erpnext/patches/v13_0/rename_ksa_qr_field.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/erpnext/patches/v13_0/rename_ksa_qr_field.py b/erpnext/patches/v13_0/rename_ksa_qr_field.py index 0bb86e04509..f4f9b17fb81 100644 --- a/erpnext/patches/v13_0/rename_ksa_qr_field.py +++ b/erpnext/patches/v13_0/rename_ksa_qr_field.py @@ -2,6 +2,7 @@ # License: GNU General Public License v3. See license.txt import frappe +from frappe.custom.doctype.custom_field.custom_field import create_custom_fields from frappe.model.utils.rename_field import rename_field @@ -12,5 +13,20 @@ def execute(): if frappe.db.exists('DocType', 'Sales Invoice'): frappe.reload_doc('accounts', 'doctype', 'sales_invoice', force=True) + + # rename_field method assumes that the field already exists or the doc is synced + if not frappe.db.has_column('Sales Invoice', 'ksa_einv_qr'): + create_custom_fields({ + 'Sales Invoice': [ + dict( + fieldname='ksa_einv_qr', + label='KSA E-Invoicing QR', + fieldtype='Attach Image', + read_only=1, no_copy=1, hidden=1 + ) + ] + }) + if frappe.db.has_column('Sales Invoice', 'qr_code'): rename_field('Sales Invoice', 'qr_code', 'ksa_einv_qr') + frappe.delete_doc_if_exists("Custom Field", "Sales Invoice-qr_code") From 187a69e39066ea5b71e8fec8e2d6f7407e124324 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 14 Dec 2021 14:03:14 +0530 Subject: [PATCH 13/27] fix: reorder patch to fix custom field options (backport #28866) * fix: reorder patch to fix custom field options (#28866) (cherry picked from commit 7fd06108bf79f71877bbc49648120340833eb8bc) # Conflicts: # erpnext/patches.txt * fix: resolve conflict Co-authored-by: Ankush Menat --- erpnext/patches.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 09ca3df92fd..ee6a1775feb 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -178,6 +178,7 @@ erpnext.patches.v12_0.set_updated_purpose_in_pick_list erpnext.patches.v12_0.set_default_payroll_based_on erpnext.patches.v12_0.repost_stock_ledger_entries_for_target_warehouse erpnext.patches.v12_0.update_end_date_and_status_in_email_campaign +erpnext.patches.v13_0.validate_options_for_data_field erpnext.patches.v13_0.move_tax_slabs_from_payroll_period_to_income_tax_slab #123 erpnext.patches.v12_0.fix_quotation_expired_status erpnext.patches.v12_0.update_appointment_reminder_scheduler_entry @@ -311,7 +312,6 @@ execute:frappe.reload_doc("erpnext_integrations", "doctype", "TaxJar Settings") execute:frappe.reload_doc("erpnext_integrations", "doctype", "Product Tax Category") erpnext.patches.v13_0.custom_fields_for_taxjar_integration #08-11-2021 erpnext.patches.v13_0.set_operation_time_based_on_operating_cost -erpnext.patches.v13_0.validate_options_for_data_field erpnext.patches.v13_0.create_website_items #30-09-2021 erpnext.patches.v13_0.populate_e_commerce_settings erpnext.patches.v13_0.make_homepage_products_website_items From b3720f62d85f921c4b7027fd7a338b50781cba50 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 14 Dec 2021 15:19:11 +0530 Subject: [PATCH 14/27] fix: Nonstock item showing in Itemwise Recommended Reorder Level report bp (#28873) (cherry picked from commit 304aa44feb104dbf171facb64151c16b17734289) Co-authored-by: aaronmenezes --- .../itemwise_recommended_reorder_level.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/stock/report/itemwise_recommended_reorder_level/itemwise_recommended_reorder_level.py b/erpnext/stock/report/itemwise_recommended_reorder_level/itemwise_recommended_reorder_level.py index 314f1608fa1..3f490653e14 100644 --- a/erpnext/stock/report/itemwise_recommended_reorder_level/itemwise_recommended_reorder_level.py +++ b/erpnext/stock/report/itemwise_recommended_reorder_level/itemwise_recommended_reorder_level.py @@ -48,6 +48,7 @@ def get_item_info(filters): conditions = [get_item_group_condition(filters.get("item_group"))] if filters.get("brand"): conditions.append("item.brand=%(brand)s") + conditions.append("is_stock_item = 1") return frappe.db.sql("""select name, item_name, description, brand, item_group, safety_stock, lead_time_days from `tabItem` item where {}""" From d0d5236a1aaebdef99f3d8bacf67c80b1265a2bf Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 14 Dec 2021 15:32:22 +0530 Subject: [PATCH 15/27] Merge pull request #28845 from ruchamahabal/fix-salary-slip-timesheet (#28874) fix: incorrect amount based on payment days in timesheet salary slip (cherry picked from commit b027050f77f14d43e4acf82da1cb99b8b00bb8dd) Co-authored-by: Rucha Mahabal --- .../doctype/salary_slip/salary_slip.py | 6 ++- .../doctype/salary_slip/test_salary_slip.py | 51 +++++++++++++++++++ .../doctype/timesheet/test_timesheet.py | 7 ++- 3 files changed, 58 insertions(+), 6 deletions(-) diff --git a/erpnext/payroll/doctype/salary_slip/salary_slip.py b/erpnext/payroll/doctype/salary_slip/salary_slip.py index 6e9503fb9a6..efbc4681d7a 100644 --- a/erpnext/payroll/doctype/salary_slip/salary_slip.py +++ b/erpnext/payroll/doctype/salary_slip/salary_slip.py @@ -944,10 +944,12 @@ class SalarySlip(TransactionBase): def get_amount_based_on_payment_days(self, row, joining_date, relieving_date): amount, additional_amount = row.amount, row.additional_amount + timesheet_component = frappe.db.get_value("Salary Structure", self.salary_structure, "salary_component") + if (self.salary_structure and cint(row.depends_on_payment_days) and cint(self.total_working_days) and not (row.additional_salary and row.default_amount) # to identify overwritten additional salary - and (not self.salary_slip_based_on_timesheet or + and (row.salary_component != timesheet_component or getdate(self.start_date) < joining_date or (relieving_date and getdate(self.end_date) > relieving_date) )): @@ -956,7 +958,7 @@ class SalarySlip(TransactionBase): amount = flt((flt(row.default_amount) * flt(self.payment_days) / cint(self.total_working_days)), row.precision("amount")) + additional_amount - elif not self.payment_days and not self.salary_slip_based_on_timesheet and cint(row.depends_on_payment_days): + elif not self.payment_days and row.salary_component != timesheet_component and cint(row.depends_on_payment_days): amount, additional_amount = 0, 0 elif not row.amount: amount = flt(row.default_amount) + flt(row.additional_amount) diff --git a/erpnext/payroll/doctype/salary_slip/test_salary_slip.py b/erpnext/payroll/doctype/salary_slip/test_salary_slip.py index 0f97b7aaf85..e621322d940 100644 --- a/erpnext/payroll/doctype/salary_slip/test_salary_slip.py +++ b/erpnext/payroll/doctype/salary_slip/test_salary_slip.py @@ -134,6 +134,57 @@ class TestSalarySlip(unittest.TestCase): frappe.db.set_value("Payroll Settings", None, "payroll_based_on", "Leave") + def test_payment_days_in_salary_slip_based_on_timesheet(self): + from erpnext.hr.doctype.attendance.attendance import mark_attendance + from erpnext.projects.doctype.timesheet.test_timesheet import ( + make_salary_structure_for_timesheet, + make_timesheet, + ) + from erpnext.projects.doctype.timesheet.timesheet import ( + make_salary_slip as make_salary_slip_for_timesheet, + ) + + # Payroll based on attendance + frappe.db.set_value("Payroll Settings", None, "payroll_based_on", "Attendance") + + emp = make_employee("test_employee_timesheet@salary.com", company="_Test Company") + frappe.db.set_value("Employee", emp, {"relieving_date": None, "status": "Active"}) + + # mark attendance + month_start_date = get_first_day(nowdate()) + month_end_date = get_last_day(nowdate()) + + first_sunday = frappe.db.sql(""" + select holiday_date from `tabHoliday` + where parent = 'Salary Slip Test Holiday List' + and holiday_date between %s and %s + order by holiday_date + """, (month_start_date, month_end_date))[0][0] + + mark_attendance(emp, add_days(first_sunday, 1), 'Absent', ignore_validate=True) # counted as absent + + # salary structure based on timesheet + make_salary_structure_for_timesheet(emp) + timesheet = make_timesheet(emp, simulate=True, is_billable=1) + salary_slip = make_salary_slip_for_timesheet(timesheet.name) + salary_slip.start_date = month_start_date + salary_slip.end_date = month_end_date + salary_slip.save() + salary_slip.submit() + + no_of_days = self.get_no_of_days() + days_in_month = no_of_days[0] + no_of_holidays = no_of_days[1] + + self.assertEqual(salary_slip.payment_days, days_in_month - no_of_holidays - 1) + + # gross pay calculation based on attendance (payment days) + gross_pay = 78100 - ((78000 / (days_in_month - no_of_holidays)) * flt(salary_slip.leave_without_pay + salary_slip.absent_days)) + + self.assertEqual(salary_slip.gross_pay, flt(gross_pay, 2)) + + frappe.db.set_value("Payroll Settings", None, "payroll_based_on", "Leave") + def test_component_amount_dependent_on_another_payment_days_based_component(self): from erpnext.hr.doctype.attendance.attendance import mark_attendance from erpnext.payroll.doctype.salary_structure.test_salary_structure import ( diff --git a/erpnext/projects/doctype/timesheet/test_timesheet.py b/erpnext/projects/doctype/timesheet/test_timesheet.py index d59cc01013b..148d8ba29c2 100644 --- a/erpnext/projects/doctype/timesheet/test_timesheet.py +++ b/erpnext/projects/doctype/timesheet/test_timesheet.py @@ -34,10 +34,6 @@ class TestTimesheet(unittest.TestCase): for dt in ["Salary Slip", "Salary Structure", "Salary Structure Assignment", "Timesheet"]: frappe.db.sql("delete from `tab%s`" % dt) - if not frappe.db.exists("Salary Component", "Timesheet Component"): - frappe.get_doc({"doctype": "Salary Component", "salary_component": "Timesheet Component"}).insert() - - def test_timesheet_billing_amount(self): emp = make_employee("test_employee_6@salary.com") @@ -160,6 +156,9 @@ def make_salary_structure_for_timesheet(employee, company=None): salary_structure_name = "Timesheet Salary Structure Test" frequency = "Monthly" + if not frappe.db.exists("Salary Component", "Timesheet Component"): + frappe.get_doc({"doctype": "Salary Component", "salary_component": "Timesheet Component"}).insert() + salary_structure = make_salary_structure(salary_structure_name, frequency, company=company, dont_submit=True) salary_structure.salary_component = "Timesheet Component" salary_structure.salary_slip_based_on_timesheet = 1 From a4814f5519646a1ee37b6f619757abd944b24f30 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 14 Dec 2021 17:29:40 +0530 Subject: [PATCH 16/27] fix: remove set only once from valuation method (#28876) (#28882) (cherry picked from commit 66960cf0507649c58d04f31273c8d3255898f173) Co-authored-by: Ankush Menat --- erpnext/stock/doctype/item/item.json | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/erpnext/stock/doctype/item/item.json b/erpnext/stock/doctype/item/item.json index 39a94a0868b..d364d8a7d95 100644 --- a/erpnext/stock/doctype/item/item.json +++ b/erpnext/stock/doctype/item/item.json @@ -359,8 +359,7 @@ "fieldname": "valuation_method", "fieldtype": "Select", "label": "Valuation Method", - "options": "\nFIFO\nMoving Average", - "set_only_once": 1 + "options": "\nFIFO\nMoving Average" }, { "depends_on": "is_stock_item", @@ -956,7 +955,7 @@ "image_field": "image", "index_web_pages_for_search": 1, "links": [], - "modified": "2021-12-03 08:32:03.869294", + "modified": "2021-12-14 04:13:16.857534", "modified_by": "Administrator", "module": "Stock", "name": "Item", From 73693ac15337f8e3c804e198364684d1aa2107a7 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Tue, 14 Dec 2021 17:36:35 +0530 Subject: [PATCH 17/27] test: remove commits from tests (#27939) * test: remove commits from tests in selling module * test: remove commits from manufacturing module * test: remove commits from accounts module tests * test: remove commits from controller tests * test: keep commits in accounting dimension test * Revert "test: remove commits from controller tests" This reverts commit f5cfbee5da844f8b2bffeee907ac602754f16ff4. * Revert "test: remove commits from accounts module tests" This reverts commit df4c27efddf355e2731a18e8871e54d18590f219. (cherry picked from commit 96ef88d9355b00470d14fa1fa703235a8689d94d) --- .../doctype/blanket_order/test_blanket_order.py | 6 ++---- erpnext/manufacturing/doctype/bom/test_bom.py | 4 ++-- .../doctype/bom_update_tool/test_bom_update_tool.py | 7 ++----- erpnext/manufacturing/doctype/job_card/test_job_card.py | 6 +++--- .../doctype/production_plan/test_production_plan.py | 6 ++---- erpnext/manufacturing/doctype/routing/test_routing.py | 6 ++---- .../manufacturing/doctype/work_order/test_work_order.py | 1 - .../manufacturing/doctype/workstation/test_workstation.py | 6 ++---- erpnext/selling/doctype/customer/test_customer.py | 6 ++---- .../party_specific_item/test_party_specific_item.py | 3 ++- erpnext/selling/doctype/quotation/test_quotation.py | 6 +++--- erpnext/selling/doctype/sales_order/test_sales_order.py | 6 ++++-- .../test_pending_so_items_for_purchase_request.py | 5 ++--- erpnext/selling/report/sales_analytics/test_analytics.py | 5 ++--- 14 files changed, 30 insertions(+), 43 deletions(-) diff --git a/erpnext/manufacturing/doctype/blanket_order/test_blanket_order.py b/erpnext/manufacturing/doctype/blanket_order/test_blanket_order.py index d5db3fc1ccf..eff2344e85c 100644 --- a/erpnext/manufacturing/doctype/blanket_order/test_blanket_order.py +++ b/erpnext/manufacturing/doctype/blanket_order/test_blanket_order.py @@ -1,17 +1,15 @@ # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt - -import unittest - import frappe from frappe.utils import add_months, today from erpnext import get_company_currency +from erpnext.tests.utils import ERPNextTestCase from .blanket_order import make_order -class TestBlanketOrder(unittest.TestCase): +class TestBlanketOrder(ERPNextTestCase): def setUp(self): frappe.flags.args = frappe._dict() diff --git a/erpnext/manufacturing/doctype/bom/test_bom.py b/erpnext/manufacturing/doctype/bom/test_bom.py index 6126c95cd45..2f9804d1d4a 100644 --- a/erpnext/manufacturing/doctype/bom/test_bom.py +++ b/erpnext/manufacturing/doctype/bom/test_bom.py @@ -2,7 +2,6 @@ # License: GNU General Public License v3. See license.txt -import unittest from collections import deque from functools import partial @@ -18,10 +17,11 @@ from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import create_stock_reconciliation, ) from erpnext.tests.test_subcontracting import set_backflush_based_on +from erpnext.tests.utils import ERPNextTestCase test_records = frappe.get_test_records('BOM') -class TestBOM(unittest.TestCase): +class TestBOM(ERPNextTestCase): def setUp(self): if not frappe.get_value('Item', '_Test Item'): make_test_records('Item') diff --git a/erpnext/manufacturing/doctype/bom_update_tool/test_bom_update_tool.py b/erpnext/manufacturing/doctype/bom_update_tool/test_bom_update_tool.py index 526c243ed78..12576cbf322 100644 --- a/erpnext/manufacturing/doctype/bom_update_tool/test_bom_update_tool.py +++ b/erpnext/manufacturing/doctype/bom_update_tool/test_bom_update_tool.py @@ -1,19 +1,16 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt - - -import unittest - import frappe from erpnext.manufacturing.doctype.bom_update_tool.bom_update_tool import update_cost from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom from erpnext.stock.doctype.item.test_item import create_item +from erpnext.tests.utils import ERPNextTestCase test_records = frappe.get_test_records('BOM') -class TestBOMUpdateTool(unittest.TestCase): +class TestBOMUpdateTool(ERPNextTestCase): def test_replace_bom(self): current_bom = "BOM-_Test Item Home Desktop Manufactured-001" diff --git a/erpnext/manufacturing/doctype/job_card/test_job_card.py b/erpnext/manufacturing/doctype/job_card/test_job_card.py index 9b4fc8b8b7a..bb5004ba86f 100644 --- a/erpnext/manufacturing/doctype/job_card/test_job_card.py +++ b/erpnext/manufacturing/doctype/job_card/test_job_card.py @@ -1,6 +1,5 @@ # Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -import unittest import frappe from frappe.utils import random_string @@ -12,9 +11,10 @@ from erpnext.manufacturing.doctype.job_card.job_card import ( from erpnext.manufacturing.doctype.work_order.test_work_order import make_wo_order_test_record from erpnext.manufacturing.doctype.workstation.test_workstation import make_workstation from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry +from erpnext.tests.utils import ERPNextTestCase -class TestJobCard(unittest.TestCase): +class TestJobCard(ERPNextTestCase): def setUp(self): make_bom_for_jc_tests() @@ -329,4 +329,4 @@ def make_bom_for_jc_tests(): bom.rm_cost_as_per = "Valuation Rate" bom.items[0].uom = "_Test UOM 1" bom.items[0].conversion_factor = 5 - bom.insert() \ No newline at end of file + bom.insert() diff --git a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py index a2980a732ed..2febc1e23c0 100644 --- a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py @@ -1,8 +1,5 @@ # Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt - -import unittest - import frappe from frappe.utils import add_to_date, flt, now_datetime, nowdate @@ -17,9 +14,10 @@ from erpnext.stock.doctype.item.test_item import create_item from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import ( create_stock_reconciliation, ) +from erpnext.tests.utils import ERPNextTestCase -class TestProductionPlan(unittest.TestCase): +class TestProductionPlan(ERPNextTestCase): def setUp(self): for item in ['Test Production Item 1', 'Subassembly Item 1', 'Raw Material Item 1', 'Raw Material Item 2']: diff --git a/erpnext/manufacturing/doctype/routing/test_routing.py b/erpnext/manufacturing/doctype/routing/test_routing.py index 68d9dec04b1..e90b0a7d6d2 100644 --- a/erpnext/manufacturing/doctype/routing/test_routing.py +++ b/erpnext/manufacturing/doctype/routing/test_routing.py @@ -1,17 +1,15 @@ # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt - -import unittest - import frappe from frappe.test_runner import make_test_records from erpnext.manufacturing.doctype.job_card.job_card import OperationSequenceError from erpnext.manufacturing.doctype.work_order.test_work_order import make_wo_order_test_record from erpnext.stock.doctype.item.test_item import make_item +from erpnext.tests.utils import ERPNextTestCase -class TestRouting(unittest.TestCase): +class TestRouting(ERPNextTestCase): @classmethod def setUpClass(cls): cls.item_code = "Test Routing Item - A" diff --git a/erpnext/manufacturing/doctype/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py index f4a88dc4598..96140792e68 100644 --- a/erpnext/manufacturing/doctype/work_order/test_work_order.py +++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py @@ -1,6 +1,5 @@ # Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -import unittest import frappe from frappe.utils import add_months, cint, flt, now, today diff --git a/erpnext/manufacturing/doctype/workstation/test_workstation.py b/erpnext/manufacturing/doctype/workstation/test_workstation.py index 5ed51535282..c298c0a8dbb 100644 --- a/erpnext/manufacturing/doctype/workstation/test_workstation.py +++ b/erpnext/manufacturing/doctype/workstation/test_workstation.py @@ -1,8 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors and Contributors # See license.txt - -import unittest - import frappe from frappe.test_runner import make_test_records @@ -13,12 +10,13 @@ from erpnext.manufacturing.doctype.workstation.workstation import ( WorkstationHolidayError, check_if_within_operating_hours, ) +from erpnext.tests.utils import ERPNextTestCase test_dependencies = ["Warehouse"] test_records = frappe.get_test_records('Workstation') make_test_records('Workstation') -class TestWorkstation(unittest.TestCase): +class TestWorkstation(ERPNextTestCase): def test_validate_timings(self): check_if_within_operating_hours("_Test Workstation 1", "Operation 1", "2013-02-02 11:00:00", "2013-02-02 19:00:00") check_if_within_operating_hours("_Test Workstation 1", "Operation 1", "2013-02-02 10:00:00", "2013-02-02 20:00:00") diff --git a/erpnext/selling/doctype/customer/test_customer.py b/erpnext/selling/doctype/customer/test_customer.py index 80f618ce928..7802a3fea44 100644 --- a/erpnext/selling/doctype/customer/test_customer.py +++ b/erpnext/selling/doctype/customer/test_customer.py @@ -2,8 +2,6 @@ # License: GNU General Public License v3. See license.txt -import unittest - import frappe from frappe.test_runner import make_test_records from frappe.utils import flt @@ -11,7 +9,7 @@ from frappe.utils import flt from erpnext.accounts.party import get_due_date from erpnext.exceptions import PartyDisabled, PartyFrozen from erpnext.selling.doctype.customer.customer import get_credit_limit, get_customer_outstanding -from erpnext.tests.utils import create_test_contact_and_address +from erpnext.tests.utils import ERPNextTestCase, create_test_contact_and_address test_ignore = ["Price List"] test_dependencies = ['Payment Term', 'Payment Terms Template'] @@ -20,7 +18,7 @@ test_records = frappe.get_test_records('Customer') from six import iteritems -class TestCustomer(unittest.TestCase): +class TestCustomer(ERPNextTestCase): def setUp(self): if not frappe.get_value('Item', '_Test Item'): make_test_records('Item') diff --git a/erpnext/selling/doctype/party_specific_item/test_party_specific_item.py b/erpnext/selling/doctype/party_specific_item/test_party_specific_item.py index 874a3645929..b951044f332 100644 --- a/erpnext/selling/doctype/party_specific_item/test_party_specific_item.py +++ b/erpnext/selling/doctype/party_specific_item/test_party_specific_item.py @@ -6,6 +6,7 @@ import unittest import frappe from erpnext.controllers.queries import item_query +from erpnext.tests.utils import ERPNextTestCase test_dependencies = ['Item', 'Customer', 'Supplier'] @@ -17,7 +18,7 @@ def create_party_specific_item(**args): psi.based_on_value = args.get('based_on_value') psi.insert() -class TestPartySpecificItem(unittest.TestCase): +class TestPartySpecificItem(ERPNextTestCase): def setUp(self): self.customer = frappe.get_last_doc("Customer") self.supplier = frappe.get_last_doc("Supplier") diff --git a/erpnext/selling/doctype/quotation/test_quotation.py b/erpnext/selling/doctype/quotation/test_quotation.py index aa83726304f..4357201d23d 100644 --- a/erpnext/selling/doctype/quotation/test_quotation.py +++ b/erpnext/selling/doctype/quotation/test_quotation.py @@ -1,15 +1,15 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -import unittest - import frappe from frappe.utils import add_days, add_months, flt, getdate, nowdate +from erpnext.tests.utils import ERPNextTestCase + test_dependencies = ["Product Bundle"] -class TestQuotation(unittest.TestCase): +class TestQuotation(ERPNextTestCase): def test_make_quotation_without_terms(self): quotation = make_quotation(do_not_save=1) self.assertFalse(quotation.get('payment_schedule')) diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py index 7e6040c38f0..9c0150ef77c 100644 --- a/erpnext/selling/doctype/sales_order/test_sales_order.py +++ b/erpnext/selling/doctype/sales_order/test_sales_order.py @@ -2,7 +2,6 @@ # License: GNU General Public License v3. See license.txt import json -import unittest import frappe import frappe.permissions @@ -22,12 +21,14 @@ from erpnext.selling.doctype.sales_order.sales_order import ( ) from erpnext.stock.doctype.item.test_item import make_item from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry +from erpnext.tests.utils import ERPNextTestCase -class TestSalesOrder(unittest.TestCase): +class TestSalesOrder(ERPNextTestCase): @classmethod def setUpClass(cls): + super().setUpClass() cls.unlink_setting = int(frappe.db.get_value("Accounts Settings", "Accounts Settings", "unlink_advance_payment_on_cancelation_of_order")) @@ -36,6 +37,7 @@ class TestSalesOrder(unittest.TestCase): # reset config to previous state frappe.db.set_value("Accounts Settings", "Accounts Settings", "unlink_advance_payment_on_cancelation_of_order", cls.unlink_setting) + super().tearDownClass() def tearDown(self): frappe.set_user("Administrator") diff --git a/erpnext/selling/report/pending_so_items_for_purchase_request/test_pending_so_items_for_purchase_request.py b/erpnext/selling/report/pending_so_items_for_purchase_request/test_pending_so_items_for_purchase_request.py index 9c30afc5b1a..d62915fc66d 100644 --- a/erpnext/selling/report/pending_so_items_for_purchase_request/test_pending_so_items_for_purchase_request.py +++ b/erpnext/selling/report/pending_so_items_for_purchase_request/test_pending_so_items_for_purchase_request.py @@ -2,8 +2,6 @@ # For license information, please see license.txt -import unittest - from frappe.utils import add_months, nowdate from erpnext.selling.doctype.sales_order.sales_order import make_material_request @@ -11,9 +9,10 @@ from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_orde from erpnext.selling.report.pending_so_items_for_purchase_request.pending_so_items_for_purchase_request import ( execute, ) +from erpnext.tests.utils import ERPNextTestCase -class TestPendingSOItemsForPurchaseRequest(unittest.TestCase): +class TestPendingSOItemsForPurchaseRequest(ERPNextTestCase): def test_result_for_partial_material_request(self): so = make_sales_order() mr=make_material_request(so.name) diff --git a/erpnext/selling/report/sales_analytics/test_analytics.py b/erpnext/selling/report/sales_analytics/test_analytics.py index 8ffc5d6d0a7..f56cce2dfdc 100644 --- a/erpnext/selling/report/sales_analytics/test_analytics.py +++ b/erpnext/selling/report/sales_analytics/test_analytics.py @@ -2,15 +2,14 @@ # For license information, please see license.txt -import unittest - import frappe from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order from erpnext.selling.report.sales_analytics.sales_analytics import execute +from erpnext.tests.utils import ERPNextTestCase -class TestAnalytics(unittest.TestCase): +class TestAnalytics(ERPNextTestCase): def test_sales_analytics(self): frappe.db.sql("delete from `tabSales Order` where company='_Test Company 2'") From 25fd11e24f4ffee09d3abc71e8f0dd974e5a5311 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Wed, 1 Dec 2021 16:34:05 +0530 Subject: [PATCH 18/27] test: timeout certain tests in work order to avoid stuck tests (#28666) --- .../doctype/work_order/test_work_order.py | 5 ++++- erpnext/tests/utils.py | 21 +++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/erpnext/manufacturing/doctype/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py index 96140792e68..0d7e83927a1 100644 --- a/erpnext/manufacturing/doctype/work_order/test_work_order.py +++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py @@ -20,9 +20,10 @@ from erpnext.stock.doctype.item.test_item import create_item, make_item from erpnext.stock.doctype.stock_entry import test_stock_entry from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse from erpnext.stock.utils import get_bin +from erpnext.tests.utils import ERPNextTestCase, timeout -class TestWorkOrder(unittest.TestCase): +class TestWorkOrder(ERPNextTestCase): def setUp(self): self.warehouse = '_Test Warehouse 2 - _TC' self.item = '_Test Item' @@ -375,6 +376,7 @@ class TestWorkOrder(unittest.TestCase): self.assertEqual(len(ste.additional_costs), 1) self.assertEqual(ste.total_additional_costs, 1000) + @timeout(seconds=60) def test_job_card(self): stock_entries = [] bom = frappe.get_doc('BOM', { @@ -768,6 +770,7 @@ class TestWorkOrder(unittest.TestCase): total_pl_qty ) + @timeout(seconds=60) def test_job_card_scrap_item(self): items = ['Test FG Item for Scrap Item Test', 'Test RM Item 1 for Scrap Item Test', 'Test RM Item 2 for Scrap Item Test'] diff --git a/erpnext/tests/utils.py b/erpnext/tests/utils.py index 91df5480e35..fbf25948a79 100644 --- a/erpnext/tests/utils.py +++ b/erpnext/tests/utils.py @@ -2,6 +2,7 @@ # License: GNU General Public License v3. See license.txt import copy +import signal import unittest from contextlib import contextmanager from typing import Any, Dict, NewType, Optional @@ -135,3 +136,23 @@ def execute_script_report( report_execute_fn(filter_with_optional_param) return report_data + + +def timeout(seconds=30, error_message="Test timed out."): + """ Timeout decorator to ensure a test doesn't run for too long. + + adapted from https://stackoverflow.com/a/2282656""" + def decorator(func): + def _handle_timeout(signum, frame): + raise Exception(error_message) + + def wrapper(*args, **kwargs): + signal.signal(signal.SIGALRM, _handle_timeout) + signal.alarm(seconds) + try: + result = func(*args, **kwargs) + finally: + signal.alarm(0) + return result + return wrapper + return decorator From bdd43274b2fa648202c573ef9b28c4379b2e2a78 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Tue, 14 Dec 2021 17:02:42 +0530 Subject: [PATCH 19/27] fix: incorrect logic for "Reserved Qty for Production" (#28880) * fix: reservation for production incorrect The query uses case to decide what fields to compute reservation on, this case is outermost case hence the very first Work order's "Skip transfer" is considered for ALL work orders. Solution: move the case inside Sum. Steps to reproduce: 1. Make work order for more than 1 qty (with | without skip transfer) 2. Create manufacture and transfer entries. 3. Keep checking reserved quantities during this process. * test: use default warehouse for testing reservation (cherry picked from commit 80f1a8c645019f0587acd8116117d37e58e680e3) --- erpnext/manufacturing/doctype/work_order/test_work_order.py | 2 +- erpnext/stock/doctype/bin/bin.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/erpnext/manufacturing/doctype/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py index 0d7e83927a1..3949fe34852 100644 --- a/erpnext/manufacturing/doctype/work_order/test_work_order.py +++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py @@ -91,7 +91,7 @@ class TestWorkOrder(ERPNextTestCase): def test_reserved_qty_for_partial_completion(self): item = "_Test Item" - warehouse = create_warehouse("Test Warehouse for reserved_qty - _TC") + warehouse = "_Test Warehouse - _TC" bin1_at_start = get_bin(item, warehouse) diff --git a/erpnext/stock/doctype/bin/bin.py b/erpnext/stock/doctype/bin/bin.py index 17c8367a638..27e83caf99b 100644 --- a/erpnext/stock/doctype/bin/bin.py +++ b/erpnext/stock/doctype/bin/bin.py @@ -33,10 +33,10 @@ class Bin(Document): in open work orders''' self.reserved_qty_for_production = frappe.db.sql(''' SELECT - CASE WHEN ifnull(skip_transfer, 0) = 0 THEN - SUM(item.required_qty - item.transferred_qty) + SUM(CASE WHEN ifnull(skip_transfer, 0) = 0 THEN + item.required_qty - item.transferred_qty ELSE - SUM(item.required_qty - item.consumed_qty) + item.required_qty - item.consumed_qty END) END FROM `tabWork Order` pro, `tabWork Order Item` item WHERE From 1988e72710c5aaceeba422c067f5c2c6c7956c45 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 15 Dec 2021 11:09:21 +0530 Subject: [PATCH 20/27] revert: "fix: expected value after useful life validation" (backport #28853) --- erpnext/assets/doctype/asset/asset.py | 1 - 1 file changed, 1 deletion(-) diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index 17a554bbbfd..90257c784ee 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -470,7 +470,6 @@ class Asset(AccountsController): asset_value_after_full_schedule = flt( flt(self.gross_purchase_amount) - - flt(self.opening_accumulated_depreciation) - flt(accumulated_depreciation_after_full_schedule), self.precision('gross_purchase_amount')) if (row.expected_value_after_useful_life and From 970cdef911516faa34581d54ce6143167f0a624b Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 15 Dec 2021 12:49:23 +0530 Subject: [PATCH 21/27] feat: added required_date field to set date in child table (backport #28432) feat: added required_date field to set date in child table (backport #28432) --- .../request_for_quotation.js | 16 +++++++++++++++- .../request_for_quotation.json | 9 ++++++++- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js index b76c3784a47..e0ddc722cc9 100644 --- a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js +++ b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js @@ -124,6 +124,14 @@ frappe.ui.form.on("Request for Quotation",{ dialog.show() }, + schedule_date(frm) { + if(frm.doc.schedule_date){ + frm.doc.items.forEach((item) => { + item.schedule_date = frm.doc.schedule_date; + }) + } + refresh_field("items"); + }, preview: (frm) => { let dialog = new frappe.ui.Dialog({ title: __('Preview Email'), @@ -184,7 +192,13 @@ frappe.ui.form.on("Request for Quotation",{ dialog.show(); } }) - +frappe.ui.form.on("Request for Quotation Item", { + items_add(frm, cdt, cdn) { + if (frm.doc.schedule_date) { + frappe.model.set_value(cdt, cdn, 'schedule_date', frm.doc.schedule_date); + } + } +}); frappe.ui.form.on("Request for Quotation Supplier",{ supplier: function(frm, cdt, cdn) { var d = locals[cdt][cdn] diff --git a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.json b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.json index 4ce4100a7fc..4993df90d16 100644 --- a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.json +++ b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.json @@ -12,6 +12,7 @@ "vendor", "column_break1", "transaction_date", + "schedule_date", "status", "amended_from", "suppliers_section", @@ -246,16 +247,22 @@ "fieldname": "sec_break_email_2", "fieldtype": "Section Break", "hide_border": 1 + }, + { + "fieldname": "schedule_date", + "fieldtype": "Date", + "label": "Required Date" } ], "icon": "fa fa-shopping-cart", "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2020-11-05 22:04:29.017134", + "modified": "2021-11-24 17:47:49.909000", "modified_by": "Administrator", "module": "Buying", "name": "Request for Quotation", + "naming_rule": "By \"Naming Series\" field", "owner": "Administrator", "permissions": [ { From 85ded9e7ce2e681c88a818da4212f3e1cfe1c427 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 15 Dec 2021 19:23:12 +0530 Subject: [PATCH 22/27] perf: remove forcing of posting sort index on stock balance (#28902) (#28904) mysql is generally smart enough to figure out which index is better based on cardinality of index. While posting sort index is better for low item high # of SLE scenario it's bad for high item variety each with lower count of SLEs. (cherry picked from commit 228e011819959ae34287a91c144fbedb3c350d07) Co-authored-by: Ankush Menat --- erpnext/stock/report/stock_balance/stock_balance.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/stock/report/stock_balance/stock_balance.py b/erpnext/stock/report/stock_balance/stock_balance.py index 963c4489e44..7d65e1ab03c 100644 --- a/erpnext/stock/report/stock_balance/stock_balance.py +++ b/erpnext/stock/report/stock_balance/stock_balance.py @@ -168,7 +168,7 @@ def get_stock_ledger_entries(filters, items): sle.company, sle.voucher_type, sle.qty_after_transaction, sle.stock_value_difference, sle.item_code as name, sle.voucher_no, sle.stock_value, sle.batch_no from - `tabStock Ledger Entry` sle force index (posting_sort_index) + `tabStock Ledger Entry` sle where sle.docstatus < 2 %s %s and is_cancelled = 0 order by sle.posting_date, sle.posting_time, sle.creation, sle.actual_qty""" % #nosec From 167e8d3dde055834516d8f46bf26c37d5d72a623 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 16 Dec 2021 22:48:12 +0530 Subject: [PATCH 23/27] chore: ledger invariant check report (#28921) (#28922) (cherry picked from commit 6ba8f7644d03fc71420f60ed92a0164f774ad26c) Co-authored-by: Ankush Menat --- .../stock_ledger_invariant_check/__init__.py | 0 .../stock_ledger_invariant_check.js | 43 ++++ .../stock_ledger_invariant_check.json | 26 ++ .../stock_ledger_invariant_check.py | 236 ++++++++++++++++++ erpnext/stock/report/test_reports.py | 6 + 5 files changed, 311 insertions(+) create mode 100644 erpnext/stock/report/stock_ledger_invariant_check/__init__.py create mode 100644 erpnext/stock/report/stock_ledger_invariant_check/stock_ledger_invariant_check.js create mode 100644 erpnext/stock/report/stock_ledger_invariant_check/stock_ledger_invariant_check.json create mode 100644 erpnext/stock/report/stock_ledger_invariant_check/stock_ledger_invariant_check.py diff --git a/erpnext/stock/report/stock_ledger_invariant_check/__init__.py b/erpnext/stock/report/stock_ledger_invariant_check/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/stock/report/stock_ledger_invariant_check/stock_ledger_invariant_check.js b/erpnext/stock/report/stock_ledger_invariant_check/stock_ledger_invariant_check.js new file mode 100644 index 00000000000..c484516a163 --- /dev/null +++ b/erpnext/stock/report/stock_ledger_invariant_check/stock_ledger_invariant_check.js @@ -0,0 +1,43 @@ +// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt +/* eslint-disable */ + +const DIFFERNCE_FIELD_NAMES = [ + "difference_in_qty", + "fifo_qty_diff", + "fifo_value_diff", + "fifo_valuation_diff", + "valuation_diff", + "fifo_difference_diff" +]; + +frappe.query_reports["Stock Ledger Invariant Check"] = { + "filters": [ + { + "fieldname": "item_code", + "fieldtype": "Link", + "label": "Item", + "mandatory": 1, + "options": "Item", + get_query: function() { + return { + filters: {is_stock_item: 1, has_serial_no: 0} + } + } + }, + { + "fieldname": "warehouse", + "fieldtype": "Link", + "label": "Warehouse", + "mandatory": 1, + "options": "Warehouse", + } + ], + formatter (value, row, column, data, default_formatter) { + value = default_formatter(value, row, column, data); + if (DIFFERNCE_FIELD_NAMES.includes(column.fieldname) && Math.abs(data[column.fieldname]) > 0.001) { + value = "" + value + ""; + } + return value; + }, +}; diff --git a/erpnext/stock/report/stock_ledger_invariant_check/stock_ledger_invariant_check.json b/erpnext/stock/report/stock_ledger_invariant_check/stock_ledger_invariant_check.json new file mode 100644 index 00000000000..d28fe0f62d1 --- /dev/null +++ b/erpnext/stock/report/stock_ledger_invariant_check/stock_ledger_invariant_check.json @@ -0,0 +1,26 @@ +{ + "add_total_row": 0, + "columns": [], + "creation": "2021-12-16 06:31:23.290916", + "disable_prepared_report": 0, + "disabled": 0, + "docstatus": 0, + "doctype": "Report", + "filters": [], + "idx": 0, + "is_standard": "Yes", + "modified": "2021-12-16 09:55:58.341764", + "modified_by": "Administrator", + "module": "Stock", + "name": "Stock Ledger Invariant Check", + "owner": "Administrator", + "prepared_report": 0, + "ref_doctype": "Stock Ledger Entry", + "report_name": "Stock Ledger Invariant Check", + "report_type": "Script Report", + "roles": [ + { + "role": "System Manager" + } + ] +} \ No newline at end of file diff --git a/erpnext/stock/report/stock_ledger_invariant_check/stock_ledger_invariant_check.py b/erpnext/stock/report/stock_ledger_invariant_check/stock_ledger_invariant_check.py new file mode 100644 index 00000000000..ca47a1ec5b8 --- /dev/null +++ b/erpnext/stock/report/stock_ledger_invariant_check/stock_ledger_invariant_check.py @@ -0,0 +1,236 @@ +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors +# License: GNU GPL v3. See LICENSE + +import json + +import frappe + +SLE_FIELDS = ( + "name", + "posting_date", + "posting_time", + "creation", + "voucher_type", + "voucher_no", + "actual_qty", + "qty_after_transaction", + "incoming_rate", + "outgoing_rate", + "stock_queue", + "batch_no", + "stock_value", + "stock_value_difference", + "valuation_rate", +) + + +def execute(filters=None): + columns = get_columns() + data = get_data(filters) + return columns, data + + +def get_data(filters): + sles = get_stock_ledger_entries(filters) + return add_invariant_check_fields(sles) + + +def get_stock_ledger_entries(filters): + return frappe.get_all( + "Stock Ledger Entry", + fields=SLE_FIELDS, + filters={ + "item_code": filters.item_code, + "warehouse": filters.warehouse, + "is_cancelled": 0 + }, + order_by="timestamp(posting_date, posting_time), creation", + ) + + +def add_invariant_check_fields(sles): + balance_qty = 0.0 + for idx, sle in enumerate(sles): + queue = json.loads(sle.stock_queue) + + fifo_qty = 0.0 + fifo_value = 0.0 + for qty, rate in queue: + fifo_qty += qty + fifo_value += qty * rate + + balance_qty += sle.actual_qty + if sle.voucher_type == "Stock Reconciliation" and not sle.batch_no: + balance_qty = sle.qty_after_transaction + + sle.fifo_queue_qty = fifo_qty + sle.fifo_stock_value = fifo_value + sle.fifo_valuation_rate = fifo_value / fifo_qty if fifo_qty else None + sle.balance_value_by_qty = ( + sle.stock_value / sle.qty_after_transaction if sle.qty_after_transaction else None + ) + sle.expected_qty_after_transaction = balance_qty + + # set difference fields + sle.difference_in_qty = sle.qty_after_transaction - sle.expected_qty_after_transaction + sle.fifo_qty_diff = sle.qty_after_transaction - fifo_qty + sle.fifo_value_diff = sle.stock_value - fifo_value + sle.fifo_valuation_diff = ( + sle.valuation_rate - sle.fifo_valuation_rate if sle.fifo_valuation_rate else None + ) + sle.valuation_diff = ( + sle.valuation_rate - sle.balance_value_by_qty if sle.balance_value_by_qty else None + ) + + if idx > 0: + sle.fifo_stock_diff = sle.fifo_stock_value - sles[idx - 1].fifo_stock_value + sle.fifo_difference_diff = sle.fifo_stock_diff - sle.stock_value_difference + + return sles + + +def get_columns(): + return [ + { + "fieldname": "name", + "fieldtype": "Link", + "label": "Stock Ledger Entry", + "options": "Stock Ledger Entry", + }, + { + "fieldname": "posting_date", + "fieldtype": "Date", + "label": "Posting Date", + }, + { + "fieldname": "posting_time", + "fieldtype": "Time", + "label": "Posting Time", + }, + { + "fieldname": "creation", + "fieldtype": "Datetime", + "label": "Creation", + }, + { + "fieldname": "voucher_type", + "fieldtype": "Link", + "label": "Voucher Type", + "options": "DocType", + }, + { + "fieldname": "voucher_no", + "fieldtype": "Dynamic Link", + "label": "Voucher No", + "options": "voucher_type", + }, + { + "fieldname": "batch_no", + "fieldtype": "Link", + "label": "Batch", + "options": "Batch", + }, + { + "fieldname": "actual_qty", + "fieldtype": "Float", + "label": "Qty Change", + }, + { + "fieldname": "incoming_rate", + "fieldtype": "Float", + "label": "Incoming Rate", + }, + { + "fieldname": "outgoing_rate", + "fieldtype": "Float", + "label": "Outgoing Rate", + }, + { + "fieldname": "qty_after_transaction", + "fieldtype": "Float", + "label": "(A) Qty After Transaction", + }, + { + "fieldname": "expected_qty_after_transaction", + "fieldtype": "Float", + "label": "(B) Expected Qty After Transaction", + }, + { + "fieldname": "difference_in_qty", + "fieldtype": "Float", + "label": "A - B", + }, + { + "fieldname": "stock_queue", + "fieldtype": "Data", + "label": "FIFO Queue", + }, + + { + "fieldname": "fifo_queue_qty", + "fieldtype": "Float", + "label": "(C) Total qty in queue", + }, + { + "fieldname": "fifo_qty_diff", + "fieldtype": "Float", + "label": "A - C", + }, + { + "fieldname": "stock_value", + "fieldtype": "Float", + "label": "(D) Balance Stock Value", + }, + { + "fieldname": "fifo_stock_value", + "fieldtype": "Float", + "label": "(E) Balance Stock Value in Queue", + }, + { + "fieldname": "fifo_value_diff", + "fieldtype": "Float", + "label": "D - E", + }, + + { + "fieldname": "stock_value_difference", + "fieldtype": "Float", + "label": "(F) Stock Value Difference", + }, + { + "fieldname": "fifo_stock_diff", + "fieldtype": "Float", + "label": "(G) Stock Value difference (FIFO queue)", + }, + { + "fieldname": "fifo_difference_diff", + "fieldtype": "Float", + "label": "F - G", + }, + { + "fieldname": "valuation_rate", + "fieldtype": "Float", + "label": "(H) Valuation Rate", + }, + { + "fieldname": "fifo_valuation_rate", + "fieldtype": "Float", + "label": "(I) Valuation Rate as per FIFO", + }, + + { + "fieldname": "fifo_valuation_diff", + "fieldtype": "Float", + "label": "H - I", + }, + { + "fieldname": "balance_value_by_qty", + "fieldtype": "Float", + "label": "(J) Valuation = Value (D) รท Qty (A)", + }, + { + "fieldname": "valuation_diff", + "fieldtype": "Float", + "label": "H - J", + }, + ] diff --git a/erpnext/stock/report/test_reports.py b/erpnext/stock/report/test_reports.py index d7fb5b2bf3f..1dcf863a9d0 100644 --- a/erpnext/stock/report/test_reports.py +++ b/erpnext/stock/report/test_reports.py @@ -41,6 +41,12 @@ REPORT_FILTER_TEST_CASES: List[Tuple[ReportName, ReportFilters]] = [ ("Total Stock Summary", {"group_by": "warehouse",}), ("Batch Item Expiry Status", {}), ("Stock Ageing", {"range1": 30, "range2": 60, "range3": 90, "_optional": True}), + ("Stock Ledger Invariant Check", + { + "warehouse": "_Test Warehouse - _TC", + "item": "_Test Item" + } + ), ] OPTIONAL_FILTERS = { From fcb08d54ea35fb5f74b11ef2f3503740e1711796 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 16 Dec 2021 23:33:56 +0530 Subject: [PATCH 24/27] fix: mapping to maintenance visit gets erased (#28917) (#28925) * fix: mappings to maintenance visit gets erased * test: mapping of schedule to visit (cherry picked from commit 4cb5b627a19edd00ecc28753ddfea0bb6ed92444) Co-authored-by: Noah Jacob --- .../maintenance_schedule/test_maintenance_schedule.py | 9 +++++++-- .../doctype/maintenance_visit/maintenance_visit.js | 2 +- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/erpnext/maintenance/doctype/maintenance_schedule/test_maintenance_schedule.py b/erpnext/maintenance/doctype/maintenance_schedule/test_maintenance_schedule.py index 37ea3fdac30..501712613a8 100644 --- a/erpnext/maintenance/doctype/maintenance_schedule/test_maintenance_schedule.py +++ b/erpnext/maintenance/doctype/maintenance_schedule/test_maintenance_schedule.py @@ -56,9 +56,14 @@ class TestMaintenanceSchedule(unittest.TestCase): ms.submit() s_id = ms.get_pending_data(data_type = "id", item_name = i.item_name, s_date = expected_dates[1]) - test = make_maintenance_visit(source_name = ms.name, item_name = "_Test Item", s_id = s_id) + + # Check if item is mapped in visit. + test_map_visit = make_maintenance_visit(source_name = ms.name, item_name = "_Test Item", s_id = s_id) + self.assertEqual(len(test_map_visit.purposes), 1) + self.assertEqual(test_map_visit.purposes[0].item_name, "_Test Item") + visit = frappe.new_doc('Maintenance Visit') - visit = test + visit = test_map_visit visit.maintenance_schedule = ms.name visit.maintenance_schedule_detail = s_id visit.completion_status = "Partially Completed" diff --git a/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.js b/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.js index b920b6339ac..78289679744 100644 --- a/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.js +++ b/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.js @@ -47,7 +47,7 @@ frappe.ui.form.on('Maintenance Visit', { frm.set_value({ status: 'Draft' }); } if (frm.doc.__islocal) { - frm.clear_table("purposes"); + frm.doc.maintenance_type == 'Unscheduled' && frm.clear_table("purposes"); frm.set_value({ mntc_date: frappe.datetime.get_today() }); } }, From eb367424271cb589e739467145be33b17586a163 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Thu, 16 Dec 2021 16:51:41 +0530 Subject: [PATCH 25/27] fix: Multicurrency invoices using subscription (cherry picked from commit 0e4842b986b5379174349b4153d64a50278fd438) --- erpnext/accounts/doctype/subscription/subscription.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/erpnext/accounts/doctype/subscription/subscription.py b/erpnext/accounts/doctype/subscription/subscription.py index 5942ce5d637..744b99807fc 100644 --- a/erpnext/accounts/doctype/subscription/subscription.py +++ b/erpnext/accounts/doctype/subscription/subscription.py @@ -24,6 +24,7 @@ from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import ( get_accounting_dimensions, ) from erpnext.accounts.doctype.subscription_plan.subscription_plan import get_plan_rate +from erpnext.accounts.party import get_party_account_currency class Subscription(Document): @@ -356,6 +357,9 @@ class Subscription(Document): if frappe.db.get_value('Supplier', self.party, 'tax_withholding_category'): invoice.apply_tds = 1 + ### Add party currency to invoice + invoice.currency = get_party_account_currency(self.party_type, self.party, self.company) + ## Add dimensions in invoice for subscription: accounting_dimensions = get_accounting_dimensions() From 94d3e6642875f824109e0191df96312d0cf5a71c Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Thu, 16 Dec 2021 17:38:29 +0530 Subject: [PATCH 26/27] fix: Add tests for multicurrency subscription (cherry picked from commit 6ab753015efdc6a3017942ad319f965aec4aec91) --- .../doctype/subscription/test_subscription.py | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/erpnext/accounts/doctype/subscription/test_subscription.py b/erpnext/accounts/doctype/subscription/test_subscription.py index 9dd370bd472..6f67bc5128b 100644 --- a/erpnext/accounts/doctype/subscription/test_subscription.py +++ b/erpnext/accounts/doctype/subscription/test_subscription.py @@ -60,15 +60,38 @@ def create_plan(): plan.billing_interval_count = 3 plan.insert() + if not frappe.db.exists('Subscription Plan', '_Test Plan Multicurrency'): + plan = frappe.new_doc('Subscription Plan') + plan.plan_name = '_Test Plan Multicurrency' + plan.item = '_Test Non Stock Item' + plan.price_determination = "Fixed Rate" + plan.cost = 50 + plan.currency = 'USD' + plan.billing_interval = 'Month' + plan.billing_interval_count = 1 + plan.insert() + +def create_parties(): if not frappe.db.exists('Supplier', '_Test Supplier'): supplier = frappe.new_doc('Supplier') supplier.supplier_name = '_Test Supplier' supplier.supplier_group = 'All Supplier Groups' supplier.insert() + if not frappe.db.exists('Customer', '_Test Subscription Customer'): + customer = frappe.new_doc('Customer') + customer.customer_name = '_Test Subscription Customer' + customer.billing_currency = 'USD' + customer.append('accounts', { + 'company': '_Test Company', + 'account': '_Test Receivable USD - _TC' + }) + customer.insert() + class TestSubscription(unittest.TestCase): def setUp(self): create_plan() + create_parties() def test_create_subscription_with_trial_with_correct_period(self): subscription = frappe.new_doc('Subscription') @@ -637,3 +660,22 @@ class TestSubscription(unittest.TestCase): subscription.process() self.assertEqual(len(subscription.invoices), 1) + + def test_multicurrency_subscription(self): + subscription = frappe.new_doc('Subscription') + subscription.party_type = 'Customer' + subscription.party = '_Test Subscription Customer' + subscription.generate_invoice_at_period_start = 1 + subscription.company = '_Test Company' + # select subscription start date as '2018-01-15' + subscription.start_date = '2018-01-01' + subscription.append('plans', {'plan': '_Test Plan Multicurrency', 'qty': 1}) + subscription.save() + + subscription.process() + self.assertEqual(len(subscription.invoices), 1) + self.assertEqual(subscription.status, 'Unpaid') + + # Check the currency of the created invoice + currency = frappe.db.get_value('Sales Invoice', subscription.invoices[0].invoice, 'currency') + self.assertEqual(currency, 'USD') \ No newline at end of file From 9c2a9c28dfa697681987cada4fd8b97f483d1b13 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Fri, 17 Dec 2021 15:27:05 +0530 Subject: [PATCH 27/27] fix(ksa): enable ksa pos invoice print format (backport #28911) --- erpnext/patches.txt | 2 +- erpnext/patches/v13_0/disable_ksa_print_format_for_others.py | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/erpnext/patches.txt b/erpnext/patches.txt index ee6a1775feb..4a7bcbdb085 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -336,4 +336,4 @@ erpnext.patches.v13_0.item_naming_series_not_mandatory erpnext.patches.v13_0.update_category_in_ltds_certificate erpnext.patches.v13_0.create_ksa_vat_custom_fields erpnext.patches.v13_0.rename_ksa_qr_field -erpnext.patches.v13_0.disable_ksa_print_format_for_others +erpnext.patches.v13_0.disable_ksa_print_format_for_others # 16-12-2021 diff --git a/erpnext/patches/v13_0/disable_ksa_print_format_for_others.py b/erpnext/patches/v13_0/disable_ksa_print_format_for_others.py index c815b3bb3c9..aa2a2d3b785 100644 --- a/erpnext/patches/v13_0/disable_ksa_print_format_for_others.py +++ b/erpnext/patches/v13_0/disable_ksa_print_format_for_others.py @@ -3,10 +3,13 @@ import frappe +from erpnext.regional.saudi_arabia.setup import add_print_formats + def execute(): company = frappe.get_all('Company', filters = {'country': 'Saudi Arabia'}) if company: + add_print_formats() return if frappe.db.exists('DocType', 'Print Format'):