From c957a5cd2eda6d2ab589919c20dd56245589e140 Mon Sep 17 00:00:00 2001 From: Anand Baburajan Date: Tue, 11 Apr 2023 12:16:07 +0530 Subject: [PATCH 01/46] fix: provide filter by depreciable assets in fixed asset register (#34803) --- .../fixed_asset_register.js | 24 +++++++++----- .../fixed_asset_register.py | 33 ++++++++++--------- 2 files changed, 32 insertions(+), 25 deletions(-) diff --git a/erpnext/assets/report/fixed_asset_register/fixed_asset_register.js b/erpnext/assets/report/fixed_asset_register/fixed_asset_register.js index 06989a95da..65a4226ebd 100644 --- a/erpnext/assets/report/fixed_asset_register/fixed_asset_register.js +++ b/erpnext/assets/report/fixed_asset_register/fixed_asset_register.js @@ -24,7 +24,7 @@ frappe.query_reports["Fixed Asset Register"] = { "label": __("Period Based On"), "fieldtype": "Select", "options": ["Fiscal Year", "Date Range"], - "default": ["Fiscal Year"], + "default": "Fiscal Year", "reqd": 1 }, { @@ -75,12 +75,6 @@ frappe.query_reports["Fixed Asset Register"] = { fieldtype: "Link", options: "Asset Category" }, - { - fieldname:"finance_book", - label: __("Finance Book"), - fieldtype: "Link", - options: "Finance Book" - }, { fieldname:"cost_center", label: __("Cost Center"), @@ -96,8 +90,20 @@ frappe.query_reports["Fixed Asset Register"] = { reqd: 1 }, { - fieldname:"is_existing_asset", - label: __("Is Existing Asset"), + fieldname:"finance_book", + label: __("Finance Book"), + fieldtype: "Link", + options: "Finance Book", + depends_on: "eval: doc.only_depreciable_assets == 1", + }, + { + fieldname:"only_depreciable_assets", + label: __("Only depreciable assets"), + fieldtype: "Check" + }, + { + fieldname:"only_existing_assets", + label: __("Only existing assets"), fieldtype: "Check" }, ] diff --git a/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py b/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py index 59d43b1ea6..5fbcbe2f7f 100644 --- a/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py +++ b/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py @@ -45,8 +45,10 @@ def get_conditions(filters): filters.year_end_date = getdate(fiscal_year.year_end_date) conditions[date_field] = ["between", [filters.year_start_date, filters.year_end_date]] - if filters.get("is_existing_asset"): - conditions["is_existing_asset"] = filters.get("is_existing_asset") + if filters.get("only_depreciable_assets"): + conditions["calculate_depreciation"] = filters.get("only_depreciable_assets") + if filters.get("only_existing_assets"): + conditions["is_existing_asset"] = filters.get("only_existing_assets") if filters.get("asset_category"): conditions["asset_category"] = filters.get("asset_category") if filters.get("cost_center"): @@ -102,19 +104,18 @@ def get_data(filters): ] assets_record = frappe.db.get_all("Asset", filters=conditions, fields=fields) - assets_linked_to_fb = frappe.db.get_all( - doctype="Asset Finance Book", - filters={"finance_book": filters.finance_book or ("is", "not set")}, - pluck="parent", - ) + assets_linked_to_fb = None + + if filters.only_depreciable_assets: + assets_linked_to_fb = frappe.db.get_all( + doctype="Asset Finance Book", + filters={"finance_book": filters.finance_book or ("is", "not set")}, + pluck="parent", + ) for asset in assets_record: - if filters.finance_book: - if asset.asset_id not in assets_linked_to_fb: - continue - else: - if asset.calculate_depreciation and asset.asset_id not in assets_linked_to_fb: - continue + if assets_linked_to_fb and asset.asset_id not in assets_linked_to_fb: + continue asset_value = get_asset_value_after_depreciation(asset.asset_id, filters.finance_book) row = { @@ -172,11 +173,11 @@ def prepare_chart_data(data, filters): "datasets": [ { "name": _("Asset Value"), - "values": [d.get("asset_value") for d in labels_values_map.values()], + "values": [flt(d.get("asset_value"), 2) for d in labels_values_map.values()], }, { "name": _("Depreciatied Amount"), - "values": [d.get("depreciated_amount") for d in labels_values_map.values()], + "values": [flt(d.get("depreciated_amount"), 2) for d in labels_values_map.values()], }, ], }, @@ -312,7 +313,7 @@ def get_columns(filters): return [ { - "label": _("Asset Id"), + "label": _("Asset ID"), "fieldtype": "Link", "fieldname": "asset_id", "options": "Asset", From 6851b5ba974a3460fca7af2e66440f64f6361223 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Tue, 11 Apr 2023 13:24:43 +0530 Subject: [PATCH 02/46] fix: reposting record not created for backdated stock reco --- .../stock_reconciliation.py | 16 +++- .../test_stock_reconciliation.py | 73 +++++++++++++++++++ erpnext/stock/stock_ledger.py | 1 + 3 files changed, 87 insertions(+), 3 deletions(-) diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py index 482b103d1e..e304bd1819 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py @@ -5,7 +5,7 @@ from typing import Optional import frappe from frappe import _, bold, msgprint -from frappe.query_builder.functions import Sum +from frappe.query_builder.functions import CombineDatetime, Sum from frappe.utils import cint, cstr, flt import erpnext @@ -575,7 +575,9 @@ class StockReconciliation(StockController): if not (row.item_code == item_code and row.batch_no == batch_no): continue - row.current_qty = get_batch_qty_for_stock_reco(item_code, row.warehouse, batch_no) + row.current_qty = get_batch_qty_for_stock_reco( + item_code, row.warehouse, batch_no, self.posting_date, self.posting_time, self.name + ) qty, val_rate = get_stock_balance( item_code, @@ -596,7 +598,9 @@ class StockReconciliation(StockController): ) -def get_batch_qty_for_stock_reco(item_code, warehouse, batch_no): +def get_batch_qty_for_stock_reco( + item_code, warehouse, batch_no, posting_date, posting_time, voucher_no +): ledger = frappe.qb.DocType("Stock Ledger Entry") query = ( @@ -610,6 +614,12 @@ def get_batch_qty_for_stock_reco(item_code, warehouse, batch_no): & (ledger.docstatus == 1) & (ledger.is_cancelled == 0) & (ledger.batch_no == batch_no) + & (ledger.posting_date <= posting_date) + & ( + CombineDatetime(ledger.posting_date, ledger.posting_time) + <= CombineDatetime(posting_date, posting_time) + ) + & (ledger.voucher_no != voucher_no) ) .groupby(ledger.batch_no) ) diff --git a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py index eaea301432..7d59441d8b 100644 --- a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py @@ -676,6 +676,79 @@ class TestStockReconciliation(FrappeTestCase, StockTestMixin): self.assertEqual(flt(sl_entry.actual_qty), 1.0) self.assertEqual(flt(sl_entry.qty_after_transaction), 1.0) + def test_backdated_stock_reco_entry(self): + from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry + + item_code = self.make_item( + "Test New Batch Item ABCV", + { + "is_stock_item": 1, + "has_batch_no": 1, + "batch_number_series": "BNS9.####", + "create_new_batch": 1, + }, + ).name + + warehouse = "_Test Warehouse - _TC" + + # Added 100 Qty, Balace Qty 100 + se1 = make_stock_entry( + item_code=item_code, posting_time="09:00:00", target=warehouse, qty=100, basic_rate=700 + ) + + # Removed 50 Qty, Balace Qty 50 + se2 = make_stock_entry( + item_code=item_code, + batch_no=se1.items[0].batch_no, + posting_time="10:00:00", + source=warehouse, + qty=50, + basic_rate=700, + ) + + # Stock Reco for 100, Balace Qty 100 + stock_reco = create_stock_reconciliation( + item_code=item_code, + posting_time="11:00:00", + warehouse=warehouse, + batch_no=se1.items[0].batch_no, + qty=100, + rate=100, + ) + + # Removed 50 Qty, Balace Qty 50 + make_stock_entry( + item_code=item_code, + batch_no=se1.items[0].batch_no, + posting_time="12:00:00", + source=warehouse, + qty=50, + basic_rate=700, + ) + + self.assertFalse(frappe.db.exists("Repost Item Valuation", {"voucher_no": stock_reco.name})) + + # Cancel the backdated Stock Entry se2, + # Since Stock Reco entry in the future the Balace Qty should remain as it's (50) + + se2.cancel() + + self.assertTrue(frappe.db.exists("Repost Item Valuation", {"voucher_no": stock_reco.name})) + + self.assertEqual( + frappe.db.get_value("Repost Item Valuation", {"voucher_no": stock_reco.name}, "status"), + "Completed", + ) + + sle = frappe.get_all( + "Stock Ledger Entry", + filters={"item_code": item_code, "warehouse": warehouse, "is_cancelled": 0}, + fields=["qty_after_transaction"], + order_by="posting_time desc, creation desc", + ) + + self.assertEqual(flt(sle[0].qty_after_transaction), flt(50.0)) + def create_batch_item_with_batch(item_name, batch_id): batch_item_doc = create_item(item_name, is_stock_item=1) diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index c954befdc2..b0a093def4 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -1375,6 +1375,7 @@ def regenerate_sle_for_batch_stock_reco(detail): doc.recalculate_current_qty(detail.item_code, detail.batch_no) doc.docstatus = 1 doc.update_stock_ledger() + doc.repost_future_sle_and_gle() def get_stock_reco_qty_shift(args): From aa8b241d5a7767911c585bd244c60b6fafbf0f30 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Tue, 11 Apr 2023 15:46:47 +0530 Subject: [PATCH 03/46] chore: update CODEOWNERS [skip ci] --- CODEOWNERS | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/CODEOWNERS b/CODEOWNERS index c4ea16328e..7f8c4d1ac8 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -3,13 +3,13 @@ # These owners will be the default owners for everything in # the repo. Unless a later match takes precedence, -erpnext/accounts/ @nextchamp-saqib @deepeshgarg007 @ruthra-kumar +erpnext/accounts/ @deepeshgarg007 @ruthra-kumar erpnext/assets/ @anandbaburajan @deepeshgarg007 -erpnext/loan_management/ @nextchamp-saqib @deepeshgarg007 -erpnext/regional @nextchamp-saqib @deepeshgarg007 @ruthra-kumar -erpnext/selling @nextchamp-saqib @deepeshgarg007 @ruthra-kumar -erpnext/support/ @nextchamp-saqib @deepeshgarg007 -pos* @nextchamp-saqib +erpnext/loan_management/ @deepeshgarg007 +erpnext/regional @deepeshgarg007 @ruthra-kumar +erpnext/selling @deepeshgarg007 @ruthra-kumar +erpnext/support/ @deepeshgarg007 +pos* erpnext/buying/ @rohitwaghchaure @s-aga-r erpnext/maintenance/ @rohitwaghchaure @s-aga-r @@ -18,12 +18,8 @@ erpnext/quality_management/ @rohitwaghchaure @s-aga-r erpnext/stock/ @rohitwaghchaure @s-aga-r erpnext/subcontracting @rohitwaghchaure @s-aga-r -erpnext/crm/ @NagariaHussain -erpnext/education/ @rutwikhdev -erpnext/projects/ @ruchamahabal +erpnext/controllers/ @deepeshgarg007 @rohitwaghchaure +erpnext/patches/ @deepeshgarg007 -erpnext/controllers/ @deepeshgarg007 @nextchamp-saqib @rohitwaghchaure -erpnext/patches/ @deepeshgarg007 @nextchamp-saqib - -.github/ @ankush +.github/ @deepeshgarg007 pyproject.toml @ankush From 66130493ebb7bc5d4fa69f5fd96cd3cebcbacb21 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 14 Apr 2023 09:47:15 +0530 Subject: [PATCH 04/46] fix: Remove unnecessary checkbox from Accounts doctype (#34821) --- erpnext/accounts/doctype/account/account.json | 88 +++++-------------- .../journal_entry/test_journal_entry.py | 4 - .../general_ledger/test_general_ledger.py | 1 - 3 files changed, 20 insertions(+), 73 deletions(-) diff --git a/erpnext/accounts/doctype/account/account.json b/erpnext/accounts/doctype/account/account.json index d2659d429b..e79fb66062 100644 --- a/erpnext/accounts/doctype/account/account.json +++ b/erpnext/accounts/doctype/account/account.json @@ -18,7 +18,6 @@ "root_type", "report_type", "account_currency", - "inter_company_account", "column_break1", "parent_account", "account_type", @@ -34,15 +33,11 @@ { "fieldname": "properties", "fieldtype": "Section Break", - "oldfieldtype": "Section Break", - "show_days": 1, - "show_seconds": 1 + "oldfieldtype": "Section Break" }, { "fieldname": "column_break0", "fieldtype": "Column Break", - "show_days": 1, - "show_seconds": 1, "width": "50%" }, { @@ -53,9 +48,7 @@ "no_copy": 1, "oldfieldname": "account_name", "oldfieldtype": "Data", - "reqd": 1, - "show_days": 1, - "show_seconds": 1 + "reqd": 1 }, { "fieldname": "account_number", @@ -63,17 +56,13 @@ "in_list_view": 1, "in_standard_filter": 1, "label": "Account Number", - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "default": "0", "fieldname": "is_group", "fieldtype": "Check", - "label": "Is Group", - "show_days": 1, - "show_seconds": 1 + "label": "Is Group" }, { "fieldname": "company", @@ -85,9 +74,7 @@ "options": "Company", "read_only": 1, "remember_last_selected_value": 1, - "reqd": 1, - "show_days": 1, - "show_seconds": 1 + "reqd": 1 }, { "fieldname": "root_type", @@ -95,9 +82,7 @@ "in_standard_filter": 1, "label": "Root Type", "options": "\nAsset\nLiability\nIncome\nExpense\nEquity", - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "report_type", @@ -105,32 +90,18 @@ "in_standard_filter": 1, "label": "Report Type", "options": "\nBalance Sheet\nProfit and Loss", - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "depends_on": "eval:doc.is_group==0", "fieldname": "account_currency", "fieldtype": "Link", "label": "Currency", - "options": "Currency", - "show_days": 1, - "show_seconds": 1 - }, - { - "default": "0", - "fieldname": "inter_company_account", - "fieldtype": "Check", - "label": "Inter Company Account", - "show_days": 1, - "show_seconds": 1 + "options": "Currency" }, { "fieldname": "column_break1", "fieldtype": "Column Break", - "show_days": 1, - "show_seconds": 1, "width": "50%" }, { @@ -142,9 +113,7 @@ "oldfieldtype": "Link", "options": "Account", "reqd": 1, - "search_index": 1, - "show_days": 1, - "show_seconds": 1 + "search_index": 1 }, { "description": "Setting Account Type helps in selecting this Account in transactions.", @@ -154,9 +123,7 @@ "label": "Account Type", "oldfieldname": "account_type", "oldfieldtype": "Select", - "options": "\nAccumulated Depreciation\nAsset Received But Not Billed\nBank\nCash\nChargeable\nCapital Work in Progress\nCost of Goods Sold\nDepreciation\nEquity\nExpense Account\nExpenses Included In Asset Valuation\nExpenses Included In Valuation\nFixed Asset\nIncome Account\nPayable\nReceivable\nRound Off\nStock\nStock Adjustment\nStock Received But Not Billed\nService Received But Not Billed\nTax\nTemporary", - "show_days": 1, - "show_seconds": 1 + "options": "\nAccumulated Depreciation\nAsset Received But Not Billed\nBank\nCash\nChargeable\nCapital Work in Progress\nCost of Goods Sold\nDepreciation\nEquity\nExpense Account\nExpenses Included In Asset Valuation\nExpenses Included In Valuation\nFixed Asset\nIncome Account\nPayable\nReceivable\nRound Off\nStock\nStock Adjustment\nStock Received But Not Billed\nService Received But Not Billed\nTax\nTemporary" }, { "description": "Rate at which this tax is applied", @@ -164,9 +131,7 @@ "fieldtype": "Float", "label": "Rate", "oldfieldname": "tax_rate", - "oldfieldtype": "Currency", - "show_days": 1, - "show_seconds": 1 + "oldfieldtype": "Currency" }, { "description": "If the account is frozen, entries are allowed to restricted users.", @@ -175,17 +140,13 @@ "label": "Frozen", "oldfieldname": "freeze_account", "oldfieldtype": "Select", - "options": "No\nYes", - "show_days": 1, - "show_seconds": 1 + "options": "No\nYes" }, { "fieldname": "balance_must_be", "fieldtype": "Select", "label": "Balance must be", - "options": "\nDebit\nCredit", - "show_days": 1, - "show_seconds": 1 + "options": "\nDebit\nCredit" }, { "fieldname": "lft", @@ -194,9 +155,7 @@ "label": "Lft", "print_hide": 1, "read_only": 1, - "search_index": 1, - "show_days": 1, - "show_seconds": 1 + "search_index": 1 }, { "fieldname": "rgt", @@ -205,9 +164,7 @@ "label": "Rgt", "print_hide": 1, "read_only": 1, - "search_index": 1, - "show_days": 1, - "show_seconds": 1 + "search_index": 1 }, { "fieldname": "old_parent", @@ -215,33 +172,27 @@ "hidden": 1, "label": "Old Parent", "print_hide": 1, - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "default": "0", "depends_on": "eval:(doc.report_type == 'Profit and Loss' && !doc.is_group)", "fieldname": "include_in_gross", "fieldtype": "Check", - "label": "Include in gross", - "show_days": 1, - "show_seconds": 1 + "label": "Include in gross" }, { "default": "0", "fieldname": "disabled", "fieldtype": "Check", - "label": "Disable", - "show_days": 1, - "show_seconds": 1 + "label": "Disable" } ], "icon": "fa fa-money", "idx": 1, "is_tree": 1, "links": [], - "modified": "2020-06-11 15:15:54.338622", + "modified": "2023-04-11 16:08:46.983677", "modified_by": "Administrator", "module": "Accounts", "name": "Account", @@ -301,5 +252,6 @@ "show_name_in_global_search": 1, "sort_field": "modified", "sort_order": "ASC", + "states": [], "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/accounts/doctype/journal_entry/test_journal_entry.py b/erpnext/accounts/doctype/journal_entry/test_journal_entry.py index 2cc5378e92..f7297d19e0 100644 --- a/erpnext/accounts/doctype/journal_entry/test_journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/test_journal_entry.py @@ -287,10 +287,6 @@ class TestJournalEntry(unittest.TestCase): jv.submit() def test_inter_company_jv(self): - frappe.db.set_value("Account", "Sales Expenses - _TC", "inter_company_account", 1) - frappe.db.set_value("Account", "Buildings - _TC", "inter_company_account", 1) - frappe.db.set_value("Account", "Sales Expenses - _TC1", "inter_company_account", 1) - frappe.db.set_value("Account", "Buildings - _TC1", "inter_company_account", 1) jv = make_journal_entry( "Sales Expenses - _TC", "Buildings - _TC", diff --git a/erpnext/accounts/report/general_ledger/test_general_ledger.py b/erpnext/accounts/report/general_ledger/test_general_ledger.py index c563785763..a8c362e78c 100644 --- a/erpnext/accounts/report/general_ledger/test_general_ledger.py +++ b/erpnext/accounts/report/general_ledger/test_general_ledger.py @@ -24,7 +24,6 @@ class TestGeneralLedger(FrappeTestCase): "root_type": "Asset", "report_type": "Balance Sheet", "account_currency": "USD", - "inter_company_account": 0, "parent_account": "Bank Accounts - _TC", "account_type": "Bank", "doctype": "Account", From a7051cb9b59d3f86670a2cb5968af9585a083ab9 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 14 Apr 2023 09:59:42 +0530 Subject: [PATCH 05/46] fix: Don't use stale item details (#34847) --- erpnext/controllers/accounts_controller.py | 13 +------------ erpnext/stock/get_item_details.py | 22 +++------------------- 2 files changed, 4 insertions(+), 31 deletions(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index a347323e35..7fcc28bac3 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -515,7 +515,6 @@ class AccountsController(TransactionBase): parent_dict.update({"customer": parent_dict.get("party_name")}) self.pricing_rules = [] - basic_item_details_map = {} for item in self.get("items"): if item.get("item_code"): @@ -535,17 +534,7 @@ class AccountsController(TransactionBase): if self.get("is_subcontracted"): args["is_subcontracted"] = self.is_subcontracted - basic_details = basic_item_details_map.get(item.item_code) - ret, basic_item_details = get_item_details( - args, - self, - for_validate=True, - overwrite_warehouse=False, - return_basic_details=True, - basic_details=basic_details, - ) - - basic_item_details_map.setdefault(item.item_code, basic_item_details) + ret = get_item_details(args, self, for_validate=True, overwrite_warehouse=False) for fieldname, value in ret.items(): if item.meta.get_field(fieldname) and value is not None: diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index 2cf3797a36..ce85702f48 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -35,14 +35,7 @@ purchase_doctypes = [ @frappe.whitelist() -def get_item_details( - args, - doc=None, - for_validate=False, - overwrite_warehouse=True, - return_basic_details=False, - basic_details=None, -): +def get_item_details(args, doc=None, for_validate=False, overwrite_warehouse=True): """ args = { "item_code": "", @@ -80,12 +73,7 @@ def get_item_details( if doc.get("doctype") == "Purchase Invoice": args["bill_date"] = doc.get("bill_date") - if not basic_details: - out = get_basic_details(args, item, overwrite_warehouse) - else: - out = basic_details - - basic_details = out.copy() + out = get_basic_details(args, item, overwrite_warehouse) get_item_tax_template(args, item, out) out["item_tax_rate"] = get_item_tax_map( @@ -154,11 +142,7 @@ def get_item_details( out.amount = flt(args.qty) * flt(out.rate) out = remove_standard_fields(out) - - if return_basic_details: - return out, basic_details - else: - return out + return out def remove_standard_fields(details): From 9d1cae01bd7b9e33835d2d56b81b73d1ebfb45ef Mon Sep 17 00:00:00 2001 From: Wolfram Schmidt Date: Fri, 14 Apr 2023 09:05:09 +0200 Subject: [PATCH 06/46] fix allowing rename of Lead This is needed to consolidate data like merging leads together when duplicated appear. --- erpnext/crm/doctype/lead/lead.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/erpnext/crm/doctype/lead/lead.json b/erpnext/crm/doctype/lead/lead.json index 077e7fa4af..0cb8824577 100644 --- a/erpnext/crm/doctype/lead/lead.json +++ b/erpnext/crm/doctype/lead/lead.json @@ -2,6 +2,7 @@ "actions": [], "allow_events_in_timeline": 1, "allow_import": 1, + "allow_rename": 1, "autoname": "naming_series:", "creation": "2022-02-08 13:14:41.083327", "doctype": "DocType", @@ -515,7 +516,7 @@ "idx": 5, "image_field": "image", "links": [], - "modified": "2023-01-24 18:20:05.044791", + "modified": "2023-04-14 18:20:05.044791", "modified_by": "Administrator", "module": "CRM", "name": "Lead", @@ -582,4 +583,4 @@ "states": [], "subject_field": "title", "title_field": "title" -} \ No newline at end of file +} From 59f6b773cdb0eb0964c7b27123bb2e03cf447d93 Mon Sep 17 00:00:00 2001 From: Raffael Meyer <14891507+barredterra@users.noreply.github.com> Date: Fri, 14 Apr 2023 09:44:00 +0200 Subject: [PATCH 07/46] feat: add german sales tax template (#34823) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Nullsteuersatz nach § 12 Abs. 3 UStG --- .../setup_wizard/data/country_wise_tax.json | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/erpnext/setup/setup_wizard/data/country_wise_tax.json b/erpnext/setup/setup_wizard/data/country_wise_tax.json index 5750914b9a..3532d6b456 100644 --- a/erpnext/setup/setup_wizard/data/country_wise_tax.json +++ b/erpnext/setup/setup_wizard/data/country_wise_tax.json @@ -581,6 +581,11 @@ "title": "Bauleistungen nach § 13b UStG", "is_default": 0, "taxes": [] + }, + { + "title": "Nullsteuersatz nach § 12 Abs. 3 UStG", + "is_default": 0, + "taxes": [] } ], "purchase_tax_templates": [ @@ -1339,6 +1344,11 @@ "title": "Bauleistungen nach § 13b UStG", "is_default": 0, "taxes": [] + }, + { + "title": "Nullsteuersatz nach § 12 Abs. 3 UStG", + "is_default": 0, + "taxes": [] } ], "purchase_tax_templates": [ @@ -2097,6 +2107,11 @@ "title": "Bauleistungen nach § 13b UStG", "is_default": 0, "taxes": [] + }, + { + "title": "Nullsteuersatz nach § 12 Abs. 3 UStG", + "is_default": 0, + "taxes": [] } ], "purchase_tax_templates": [ @@ -2849,6 +2864,11 @@ "title": "Bauleistungen nach § 13b UStG", "is_default": 0, "taxes": [] + }, + { + "title": "Nullsteuersatz nach § 12 Abs. 3 UStG", + "is_default": 0, + "taxes": [] } ], "purchase_tax_templates": [ From 7bfc8f12367c14245682b32bb0853bbf044114c4 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Fri, 14 Apr 2023 12:22:19 +0530 Subject: [PATCH 08/46] fix: too many writes error while making backdated stock reconciliation --- .../stock_reconciliation.py | 26 ++++++++++++++----- erpnext/stock/stock_ledger.py | 24 +++++++++++++---- 2 files changed, 38 insertions(+), 12 deletions(-) diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py index e304bd1819..3fd4cec5d8 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py @@ -571,24 +571,33 @@ class StockReconciliation(StockController): self._cancel() def recalculate_current_qty(self, item_code, batch_no): + from erpnext.stock.stock_ledger import get_valuation_rate + + sl_entries = [] for row in self.items: if not (row.item_code == item_code and row.batch_no == batch_no): continue - row.current_qty = get_batch_qty_for_stock_reco( + current_qty = get_batch_qty_for_stock_reco( item_code, row.warehouse, batch_no, self.posting_date, self.posting_time, self.name ) - qty, val_rate = get_stock_balance( - item_code, - row.warehouse, - self.posting_date, - self.posting_time, - with_valuation_rate=True, + precesion = row.precision("current_qty") + if flt(current_qty, precesion) == flt(row.current_qty, precesion): + continue + + val_rate = get_valuation_rate( + item_code, row.warehouse, self.doctype, self.name, company=self.company, batch_no=batch_no ) row.current_valuation_rate = val_rate + if not row.current_qty and current_qty: + sle = self.get_sle_for_items(row) + sle.actual_qty = current_qty * -1 + sle.valuation_rate = val_rate + sl_entries.append(sle) + row.current_qty = current_qty row.db_set( { "current_qty": row.current_qty, @@ -597,6 +606,9 @@ class StockReconciliation(StockController): } ) + if sl_entries: + self.make_sl_entries(sl_entries) + def get_batch_qty_for_stock_reco( item_code, warehouse, batch_no, posting_date, posting_time, voucher_no diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index b0a093def4..a605b0c24a 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -544,6 +544,14 @@ class update_entries_after(object): if not self.args.get("sle_id"): self.get_dynamic_incoming_outgoing_rate(sle) + if ( + sle.voucher_type == "Stock Reconciliation" + and sle.batch_no + and sle.voucher_detail_no + and sle.actual_qty < 0 + ): + self.reset_actual_qty_for_stock_reco(sle) + if ( sle.voucher_type in ["Purchase Receipt", "Purchase Invoice"] and sle.voucher_detail_no @@ -605,6 +613,16 @@ class update_entries_after(object): if not self.args.get("sle_id"): self.update_outgoing_rate_on_transaction(sle) + def reset_actual_qty_for_stock_reco(self, sle): + current_qty = frappe.get_cached_value( + "Stock Reconciliation Item", sle.voucher_detail_no, "current_qty" + ) + + if current_qty: + sle.actual_qty = current_qty * -1 + elif current_qty == 0: + sle.is_cancelled = 1 + def validate_negative_stock(self, sle): """ validate negative stock for entries current datetime onwards @@ -1369,12 +1387,7 @@ def update_qty_in_future_sle(args, allow_negative_stock=False): def regenerate_sle_for_batch_stock_reco(detail): doc = frappe.get_cached_doc("Stock Reconciliation", detail.voucher_no) - doc.docstatus = 2 - doc.update_stock_ledger() - doc.recalculate_current_qty(detail.item_code, detail.batch_no) - doc.docstatus = 1 - doc.update_stock_ledger() doc.repost_future_sle_and_gle() @@ -1416,6 +1429,7 @@ def get_next_stock_reco(args): and voucher_type = 'Stock Reconciliation' and voucher_no != %(voucher_no)s and is_cancelled = 0 + and batch_no = %(batch_no)s and (timestamp(posting_date, posting_time) > timestamp(%(posting_date)s, %(posting_time)s) or ( timestamp(posting_date, posting_time) = timestamp(%(posting_date)s, %(posting_time)s) From 51c4338661817267e383b094ad802da4cfa87940 Mon Sep 17 00:00:00 2001 From: Devin Slauenwhite Date: Fri, 14 Apr 2023 05:50:51 -0400 Subject: [PATCH 09/46] fix(ux): don't throw error when company defaults aren't set (#34825) * fix(ux): don't throw error when company defaults aren't set; instead prompt account input. * fix: translate label and title --- .../doctype/payment_entry/payment_entry.js | 52 +++++++++++++------ .../doctype/payment_entry/payment_entry.py | 12 +---- 2 files changed, 36 insertions(+), 28 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js index f8969b8b46..ed6d0a710a 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.js +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js @@ -970,29 +970,47 @@ frappe.ui.form.on('Payment Entry', { }, callback: function(r, rt) { if(r.message) { - var write_off_row = $.map(frm.doc["deductions"] || [], function(t) { + const write_off_row = $.map(frm.doc["deductions"] || [], function(t) { return t.account==r.message[account] ? t : null; }); - var row = []; - - var difference_amount = flt(frm.doc.difference_amount, + const difference_amount = flt(frm.doc.difference_amount, precision("difference_amount")); - if (!write_off_row.length && difference_amount) { - row = frm.add_child("deductions"); - row.account = r.message[account]; - row.cost_center = r.message["cost_center"]; - } else { - row = write_off_row[0]; - } + const add_deductions = (details) => { + if (!write_off_row.length && difference_amount) { + row = frm.add_child("deductions"); + row.account = details[account]; + row.cost_center = details["cost_center"]; + } else { + row = write_off_row[0]; + } - if (row) { - row.amount = flt(row.amount) + difference_amount; - } else { - frappe.msgprint(__("No gain or loss in the exchange rate")) - } + if (row) { + row.amount = flt(row.amount) + difference_amount; + } else { + frappe.msgprint(__("No gain or loss in the exchange rate")) + } + refresh_field("deductions"); + }; - refresh_field("deductions"); + if (!r.message[account]) { + frappe.prompt({ + label: __("Please Specify Account"), + fieldname: account, + fieldtype: "Link", + options: "Account", + get_query: () => ({ + filters: { + company: frm.doc.company, + } + }) + }, (values) => { + const details = Object.assign({}, r.message, values); + add_deductions(details); + }, __(frappe.unscrub(account))); + } else { + add_deductions(r.message); + } frm.events.set_unallocated_amount(frm); } diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index c34bddd77e..f8a2653759 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -1594,17 +1594,7 @@ def get_account_details(account, date, cost_center=None): @frappe.whitelist() def get_company_defaults(company): fields = ["write_off_account", "exchange_gain_loss_account", "cost_center"] - ret = frappe.get_cached_value("Company", company, fields, as_dict=1) - - for fieldname in fields: - if not ret[fieldname]: - frappe.throw( - _("Please set default {0} in Company {1}").format( - frappe.get_meta("Company").get_label(fieldname), company - ) - ) - - return ret + return frappe.get_cached_value("Company", company, fields, as_dict=1) def get_outstanding_on_journal_entry(name): From fb3271c6249d647c2add2a83c06cbbb879762b81 Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Fri, 14 Apr 2023 15:59:35 +0530 Subject: [PATCH 10/46] fix: unable to change `company` for manual `Serial No` entry --- erpnext/stock/doctype/serial_no/serial_no.json | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/erpnext/stock/doctype/serial_no/serial_no.json b/erpnext/stock/doctype/serial_no/serial_no.json index 6e1e0d461a..7989b1ac75 100644 --- a/erpnext/stock/doctype/serial_no/serial_no.json +++ b/erpnext/stock/doctype/serial_no/serial_no.json @@ -410,10 +410,10 @@ "fieldtype": "Link", "label": "Company", "options": "Company", - "read_only": 1, "remember_last_selected_value": 1, "reqd": 1, - "search_index": 1 + "search_index": 1, + "set_only_once": 1 }, { "fieldname": "status", @@ -433,7 +433,7 @@ "icon": "fa fa-barcode", "idx": 1, "links": [], - "modified": "2021-12-23 10:44:30.299450", + "modified": "2023-04-14 15:58:46.139887", "modified_by": "Administrator", "module": "Stock", "name": "Serial No", @@ -461,7 +461,6 @@ "read": 1, "report": 1, "role": "Stock Manager", - "set_user_permissions": 1, "write": 1 }, { From c9418aab45a147b2654de60f3817006a25e36d62 Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Tue, 4 Apr 2023 16:57:14 +0530 Subject: [PATCH 11/46] chore: add items field label --- erpnext/buying/doctype/purchase_order/purchase_order.json | 6 +++--- .../doctype/supplier_quotation/supplier_quotation.json | 4 ++-- erpnext/selling/doctype/quotation/quotation.json | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.json b/erpnext/buying/doctype/purchase_order/purchase_order.json index 29afc8476e..ff08ddd33d 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.json +++ b/erpnext/buying/doctype/purchase_order/purchase_order.json @@ -495,6 +495,7 @@ "allow_bulk_edit": 1, "fieldname": "items", "fieldtype": "Table", + "label": "Items", "oldfieldname": "po_details", "oldfieldtype": "Table", "options": "Purchase Order Item", @@ -1100,8 +1101,7 @@ { "fieldname": "before_items_section", "fieldtype": "Section Break", - "hide_border": 1, - "label": "Items" + "hide_border": 1 }, { "fieldname": "items_col_break", @@ -1271,7 +1271,7 @@ "idx": 105, "is_submittable": 1, "links": [], - "modified": "2023-01-28 18:59:16.322824", + "modified": "2023-04-14 16:42:29.448464", "modified_by": "Administrator", "module": "Buying", "name": "Purchase Order", diff --git a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json index c5b369bedd..11ff91af94 100644 --- a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json +++ b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json @@ -310,7 +310,6 @@ "fieldname": "items_section", "fieldtype": "Section Break", "hide_border": 1, - "label": "Items", "oldfieldtype": "Section Break", "options": "fa fa-shopping-cart" }, @@ -318,6 +317,7 @@ "allow_bulk_edit": 1, "fieldname": "items", "fieldtype": "Table", + "label": "Items", "oldfieldname": "po_details", "oldfieldtype": "Table", "options": "Supplier Quotation Item", @@ -844,7 +844,7 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2022-12-12 18:35:39.740974", + "modified": "2023-04-14 16:43:41.714832", "modified_by": "Administrator", "module": "Buying", "name": "Supplier Quotation", diff --git a/erpnext/selling/doctype/quotation/quotation.json b/erpnext/selling/doctype/quotation/quotation.json index eb2c0a48ac..2ffa6a5c12 100644 --- a/erpnext/selling/doctype/quotation/quotation.json +++ b/erpnext/selling/doctype/quotation/quotation.json @@ -416,7 +416,6 @@ "fieldname": "items_section", "fieldtype": "Section Break", "hide_border": 1, - "label": "Items", "oldfieldtype": "Section Break", "options": "fa fa-shopping-cart" }, @@ -424,6 +423,7 @@ "allow_bulk_edit": 1, "fieldname": "items", "fieldtype": "Table", + "label": "Items", "oldfieldname": "quotation_details", "oldfieldtype": "Table", "options": "Quotation Item", @@ -1072,7 +1072,7 @@ "idx": 82, "is_submittable": 1, "links": [], - "modified": "2022-12-12 18:32:28.671332", + "modified": "2023-04-14 16:50:44.550098", "modified_by": "Administrator", "module": "Selling", "name": "Quotation", From 5a4dd354c1662db7a7c2ed7312a04833a4822223 Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Sun, 16 Apr 2023 13:10:42 +0530 Subject: [PATCH 12/46] fix: selling workspace is not migrating properly --- erpnext/selling/workspace/selling/selling.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/selling/workspace/selling/selling.json b/erpnext/selling/workspace/selling/selling.json index 45e160d143..180a3d783e 100644 --- a/erpnext/selling/workspace/selling/selling.json +++ b/erpnext/selling/workspace/selling/selling.json @@ -704,7 +704,7 @@ "type": "Link" } ], - "modified": "2022-04-26 13:29:55.087240", + "modified": "2023-04-16 13:29:55.087240", "modified_by": "Administrator", "module": "Selling", "name": "Selling", From 5c75894065cd0d20be33eeed43a8f0d6e9ed3656 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sun, 16 Apr 2023 17:11:24 +0530 Subject: [PATCH 13/46] fix: Advance payment against payment terms (#34872) --- .../accounts_settings/accounts_settings.json | 3 +- .../doctype/payment_entry/payment_entry.py | 7 +++- erpnext/controllers/accounts_controller.py | 34 +++++++++++-------- erpnext/public/js/controllers/transaction.js | 4 +-- 4 files changed, 30 insertions(+), 18 deletions(-) diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json index c0eed18ad1..5cecddd2a9 100644 --- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json +++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json @@ -184,6 +184,7 @@ }, { "default": "0", + "description": "Payment Terms from orders will be fetched into the invoices as is", "fieldname": "automatically_fetch_payment_terms", "fieldtype": "Check", "label": "Automatically Fetch Payment Terms from Order" @@ -375,7 +376,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2023-03-28 09:50:20.375233", + "modified": "2023-04-14 17:22:03.680886", "modified_by": "Administrator", "module": "Accounts", "name": "Accounts Settings", diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index f8a2653759..3583dc7a90 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -1754,7 +1754,12 @@ def get_payment_entry( if doc.doctype == "Purchase Invoice" and doc.invoice_is_blocked(): frappe.msgprint(_("{0} is on hold till {1}").format(doc.name, doc.release_date)) else: - if doc.doctype in ("Sales Invoice", "Purchase Invoice") and frappe.get_cached_value( + if doc.doctype in ( + "Sales Invoice", + "Purchase Invoice", + "Purchase Order", + "Sales Order", + ) and frappe.get_cached_value( "Payment Terms Template", doc.payment_terms_template, "allocate_payment_based_on_payment_terms", diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 7fcc28bac3..c7416228ee 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -273,8 +273,8 @@ class AccountsController(TransactionBase): self.validate_payment_schedule_dates() self.set_due_date() self.set_payment_schedule() - self.validate_payment_schedule_amount() if not self.get("ignore_default_payment_terms_template"): + self.validate_payment_schedule_amount() self.validate_due_date() self.validate_advance_entries() @@ -1607,6 +1607,7 @@ class AccountsController(TransactionBase): base_grand_total = self.get("base_rounded_total") or self.base_grand_total grand_total = self.get("rounded_total") or self.grand_total + automatically_fetch_payment_terms = 0 if self.doctype in ("Sales Invoice", "Purchase Invoice"): base_grand_total = base_grand_total - flt(self.base_write_off_amount) @@ -1652,19 +1653,20 @@ class AccountsController(TransactionBase): ) self.append("payment_schedule", data) - for d in self.get("payment_schedule"): - if d.invoice_portion: - d.payment_amount = flt( - grand_total * flt(d.invoice_portion / 100), d.precision("payment_amount") - ) - d.base_payment_amount = flt( - base_grand_total * flt(d.invoice_portion / 100), d.precision("base_payment_amount") - ) - d.outstanding = d.payment_amount - elif not d.invoice_portion: - d.base_payment_amount = flt( - d.payment_amount * self.get("conversion_rate"), d.precision("base_payment_amount") - ) + if not automatically_fetch_payment_terms: + for d in self.get("payment_schedule"): + if d.invoice_portion: + d.payment_amount = flt( + grand_total * flt(d.invoice_portion / 100), d.precision("payment_amount") + ) + d.base_payment_amount = flt( + base_grand_total * flt(d.invoice_portion / 100), d.precision("base_payment_amount") + ) + d.outstanding = d.payment_amount + elif not d.invoice_portion: + d.base_payment_amount = flt( + d.payment_amount * self.get("conversion_rate"), d.precision("base_payment_amount") + ) def get_order_details(self): if self.doctype == "Sales Invoice": @@ -1717,6 +1719,10 @@ class AccountsController(TransactionBase): "invoice_portion": schedule.invoice_portion, "mode_of_payment": schedule.mode_of_payment, "description": schedule.description, + "payment_amount": schedule.payment_amount, + "base_payment_amount": schedule.base_payment_amount, + "outstanding": schedule.outstanding, + "paid_amount": schedule.paid_amount, } if schedule.discount_type == "Percentage": diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 0bd4d91c6f..1675e2c963 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -1920,7 +1920,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe } prompt_user_for_reference_date(){ - var me = this; + let me = this; frappe.prompt({ label: __("Cheque/Reference Date"), fieldname: "reference_date", @@ -1947,7 +1947,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe let has_payment_schedule = this.frm.doc.payment_schedule && this.frm.doc.payment_schedule.length; if(!is_eligible || !has_payment_schedule) return false; - let has_discount = this.frm.doc.payment_schedule.some(row => row.discount_date); + let has_discount = this.frm.doc.payment_schedule.some(row => row.discount); return has_discount; } From d9dd64b4d2fcce9ffb6d2f21b5a9f56c39580b02 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Fri, 14 Apr 2023 13:00:12 +0530 Subject: [PATCH 14/46] fix: linters issues --- erpnext/controllers/stock_controller.py | 13 ++++-- erpnext/stock/stock_ledger.py | 61 +++++++++++++++---------- 2 files changed, 47 insertions(+), 27 deletions(-) diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index 1e4fabe0d2..479fef72c6 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -859,6 +859,8 @@ def is_reposting_pending(): def future_sle_exists(args, sl_entries=None): key = (args.voucher_type, args.voucher_no) + if not hasattr(frappe.local, "future_sle"): + frappe.local.future_sle = {} if validate_future_sle_not_exists(args, key, sl_entries): return False @@ -892,6 +894,9 @@ def future_sle_exists(args, sl_entries=None): ) for d in data: + if key not in frappe.local.future_sle: + frappe.local.future_sle[key] = frappe._dict({}) + frappe.local.future_sle[key][(d.item_code, d.warehouse)] = d.total_row return len(data) @@ -903,6 +908,9 @@ def validate_future_sle_not_exists(args, key, sl_entries=None): item_key = (args.get("item_code"), args.get("warehouse")) if not sl_entries and hasattr(frappe.local, "future_sle"): + if key not in frappe.local.future_sle: + return False + if not frappe.local.future_sle.get(key) or ( item_key and item_key not in frappe.local.future_sle.get(key) ): @@ -910,11 +918,8 @@ def validate_future_sle_not_exists(args, key, sl_entries=None): def get_cached_data(args, key): - if not hasattr(frappe.local, "future_sle"): - frappe.local.future_sle = {} - if key not in frappe.local.future_sle: - frappe.local.future_sle[key] = frappe._dict({}) + return False if args.get("item_code"): item_key = (args.get("item_code"), args.get("warehouse")) diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index a605b0c24a..03c04a54ad 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -1414,35 +1414,50 @@ def get_stock_reco_qty_shift(args): return stock_reco_qty_shift -def get_next_stock_reco(args): +def get_next_stock_reco(kwargs): """Returns next nearest stock reconciliaton's details.""" - return frappe.db.sql( - """ - select - name, posting_date, posting_time, creation, voucher_no, item_code, batch_no, actual_qty - from - `tabStock Ledger Entry` - where - item_code = %(item_code)s - and warehouse = %(warehouse)s - and voucher_type = 'Stock Reconciliation' - and voucher_no != %(voucher_no)s - and is_cancelled = 0 - and batch_no = %(batch_no)s - and (timestamp(posting_date, posting_time) > timestamp(%(posting_date)s, %(posting_time)s) - or ( - timestamp(posting_date, posting_time) = timestamp(%(posting_date)s, %(posting_time)s) - and creation > %(creation)s + sle = frappe.qb.DocType("Stock Ledger Entry") + + query = ( + frappe.qb.from_(sle) + .select( + sle.name, + sle.posting_date, + sle.posting_time, + sle.creation, + sle.voucher_no, + sle.item_code, + sle.batch_no, + sle.actual_qty, + ) + .where( + (sle.item_code == kwargs.get("item_code")) + & (sle.warehouse == kwargs.get("warehouse")) + & (sle.voucher_type == "Stock Reconciliation") + & (sle.voucher_no != kwargs.get("voucher_no")) + & (sle.is_cancelled == 0) + & ( + ( + CombineDatetime(sle.posting_date, sle.posting_time) + > CombineDatetime(kwargs.get("posting_date"), kwargs.get("posting_time")) + | ( + ( + CombineDatetime(sle.posting_date, sle.posting_time) + == CombineDatetime(kwargs.get("posting_date"), kwargs.get("posting_time")) + ) + & (sle.creation > kwargs.get("creation")) + ) ) ) - order by timestamp(posting_date, posting_time) asc, creation asc - limit 1 - """, - args, - as_dict=1, + ) ) + if kwargs.get("batch_no"): + query.where(sle.batch_no == kwargs.get("batch_no")) + + return query.run(as_dict=True) + def get_datetime_limit_condition(detail): return f""" From 9ceb1f6bda093061c7f8d70d6ab169258d4815a9 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Mon, 17 Apr 2023 11:05:32 +0530 Subject: [PATCH 15/46] fix: don't show disabled warehouses in the Warehouse Wise Stock Balance report --- .../warehouse_wise_stock_balance.js | 7 +++++ .../warehouse_wise_stock_balance.py | 27 +++++++++++++++---- 2 files changed, 29 insertions(+), 5 deletions(-) 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 index 58a043ec20..752e464e27 100644 --- 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 @@ -11,6 +11,13 @@ frappe.query_reports["Warehouse Wise Stock Balance"] = { "options": "Company", "reqd": 1, "default": frappe.defaults.get_user_default("Company") + }, + { + "fieldname":"show_disabled_warehouses", + "label": __("Show Disabled Warehouses"), + "fieldtype": "Check", + "default": 0 + } ], "initial_depth": 3, 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 d364b577a2..a0e994482f 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 @@ -11,6 +11,7 @@ from frappe.query_builder.functions import Sum class StockBalanceFilter(TypedDict): company: Optional[str] warehouse: Optional[str] + show_disabled_warehouses: Optional[int] SLEntry = Dict[str, Any] @@ -18,7 +19,7 @@ SLEntry = Dict[str, Any] def execute(filters=None): columns, data = [], [] - columns = get_columns() + columns = get_columns(filters) data = get_data(filters) return columns, data @@ -42,10 +43,14 @@ def get_warehouse_wise_balance(filters: StockBalanceFilter) -> List[SLEntry]: def get_warehouses(report_filters: StockBalanceFilter): + filters = {"company": report_filters.company, "disabled": 0} + if report_filters.get("show_disabled_warehouses"): + filters["disabled"] = ("in", [0, report_filters.show_disabled_warehouses]) + return frappe.get_all( "Warehouse", - fields=["name", "parent_warehouse", "is_group"], - filters={"company": report_filters.company}, + fields=["name", "parent_warehouse", "is_group", "disabled"], + filters=filters, order_by="lft", ) @@ -90,8 +95,8 @@ def set_balance_in_parent(warehouses): update_balance(warehouse, warehouse.stock_balance) -def get_columns(): - return [ +def get_columns(filters: StockBalanceFilter) -> List[Dict]: + columns = [ { "label": _("Warehouse"), "fieldname": "name", @@ -101,3 +106,15 @@ def get_columns(): }, {"label": _("Stock Balance"), "fieldname": "stock_balance", "fieldtype": "Float", "width": 150}, ] + + if filters.get("show_disabled_warehouses"): + columns.append( + { + "label": _("Warehouse Disabled?"), + "fieldname": "disabled", + "fieldtype": "Check", + "width": 200, + } + ) + + return columns From 6bccd8644e99b3750d643a7d98ee75e2f79cef9a Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Mon, 17 Apr 2023 14:22:27 +0530 Subject: [PATCH 16/46] fix: stock reco test case --- erpnext/stock/stock_ledger.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index 03c04a54ad..b638f08ed9 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -1451,6 +1451,8 @@ def get_next_stock_reco(kwargs): ) ) ) + .orderby(CombineDatetime(sle.posting_date, sle.posting_time)) + .orderby(sle.creation) ) if kwargs.get("batch_no"): From 534ea5ad2170bfd95a620c82fd700287404cda65 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 17 Apr 2023 16:35:22 +0530 Subject: [PATCH 17/46] fix: Add offers info to website item (#34873) * fix: Add offers info to website item * Revert "fix: Add offers info to website item" This reverts commit 88b598edb61f0ab97b751581cc2719964e169055. * fix: Add offer properties to website item --- erpnext/templates/generators/item/item_add_to_cart.html | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/erpnext/templates/generators/item/item_add_to_cart.html b/erpnext/templates/generators/item/item_add_to_cart.html index 8000a2446b..1381dfe3b7 100644 --- a/erpnext/templates/generators/item/item_add_to_cart.html +++ b/erpnext/templates/generators/item/item_add_to_cart.html @@ -11,7 +11,10 @@
- {{ price_info.formatted_price_sales_uom }} + + {{ price_info.formatted_price_sales_uom }} + {{ price_info.currency }} + {% if price_info.formatted_mrp %} From dd93ea067e1a147462fe1bc5aabb0ca63f3c89fc Mon Sep 17 00:00:00 2001 From: MohsinAli Date: Mon, 17 Apr 2023 17:00:52 +0530 Subject: [PATCH 18/46] fix: change discuss forum url (#34891) [skip ci] --- erpnext/setup/install.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/setup/install.py b/erpnext/setup/install.py index 088958d1b2..3e1e39410e 100644 --- a/erpnext/setup/install.py +++ b/erpnext/setup/install.py @@ -161,7 +161,7 @@ def add_standard_navbar_items(): { "item_label": "User Forum", "item_type": "Route", - "route": "https://discuss.erpnext.com", + "route": "https://discuss.frappe.io", "is_standard": 1, }, { From e4f152a41638ed91e505c3e48156cad6493d681f Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Tue, 18 Apr 2023 08:24:22 +0530 Subject: [PATCH 19/46] fix: whitelist doc method This should've been whitelisted, looks like it was missed out closes https://github.com/frappe/erpnext/issues/34898 --- erpnext/e_commerce/doctype/website_item/website_item.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/e_commerce/doctype/website_item/website_item.py b/erpnext/e_commerce/doctype/website_item/website_item.py index 3e5d5f768f..81b8ecab48 100644 --- a/erpnext/e_commerce/doctype/website_item/website_item.py +++ b/erpnext/e_commerce/doctype/website_item/website_item.py @@ -315,6 +315,7 @@ class WebsiteItem(WebsiteGenerator): self.item_code, skip_quotation_creation=True ) + @frappe.whitelist() def copy_specification_from_item_group(self): self.set("website_specifications", []) if self.item_group: From cc185bd2feb6aad356d858c3301ba434e7bff66b Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Tue, 18 Apr 2023 08:40:24 +0530 Subject: [PATCH 20/46] chore: update codeowners [skip ci] --- CODEOWNERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CODEOWNERS b/CODEOWNERS index 7f8c4d1ac8..540680c796 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -22,4 +22,4 @@ erpnext/controllers/ @deepeshgarg007 @rohitwaghchaure erpnext/patches/ @deepeshgarg007 .github/ @deepeshgarg007 -pyproject.toml @ankush +pyproject.toml @phot0n From a3e3fe149d910a7613d6be54a5528c76a5e38e14 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 13 Apr 2023 09:59:35 +0530 Subject: [PATCH 21/46] refactor: checkbox to toggle merging of JE account heads --- .../accounts_settings/accounts_settings.json | 16 +++++++++++++++- .../doctype/journal_entry/journal_entry.py | 10 +++++++++- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json index 5cecddd2a9..a354d7a36c 100644 --- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json +++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json @@ -19,6 +19,8 @@ "column_break_17", "enable_common_party_accounting", "allow_multi_currency_invoices_against_single_party_account", + "journals_section", + "merge_similar_account_heads", "report_setting_section", "use_custom_cash_flow", "deferred_accounting_settings_section", @@ -369,6 +371,18 @@ "fieldname": "book_tax_discount_loss", "fieldtype": "Check", "label": "Book Tax Loss on Early Payment Discount" + }, + { + "fieldname": "journals_section", + "fieldtype": "Section Break", + "label": "Journals" + }, + { + "default": "0", + "description": "Rows with Same Account heads will be merged on Ledger", + "fieldname": "merge_similar_account_heads", + "fieldtype": "Check", + "label": "Merge Similar Account Heads" } ], "icon": "icon-cog", @@ -376,7 +390,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2023-04-14 17:22:03.680886", + "modified": "2023-04-17 11:45:42.049247", "modified_by": "Administrator", "module": "Accounts", "name": "Accounts Settings", diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py index 68364beba2..0f8ae4f37d 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py @@ -885,6 +885,8 @@ class JournalEntry(AccountsController): def make_gl_entries(self, cancel=0, adv_adj=0): from erpnext.accounts.general_ledger import make_gl_entries + merge_entries = frappe.db.get_single_value("Accounts Settings", "merge_similar_account_heads") + gl_map = self.build_gl_map() if self.voucher_type in ("Deferred Revenue", "Deferred Expense"): update_outstanding = "No" @@ -892,7 +894,13 @@ class JournalEntry(AccountsController): update_outstanding = "Yes" if gl_map: - make_gl_entries(gl_map, cancel=cancel, adv_adj=adv_adj, update_outstanding=update_outstanding) + make_gl_entries( + gl_map, + cancel=cancel, + adv_adj=adv_adj, + merge_entries=merge_entries, + update_outstanding=update_outstanding, + ) @frappe.whitelist() def get_balance(self, difference_account=None): From 3f537d30bd484eb44c2ed8efb99267cd943555de Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 17 Apr 2023 17:18:07 +0530 Subject: [PATCH 22/46] chore(patch): by default ledger entries of JE's will not be merged --- erpnext/patches.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 3357b06fbb..74c8af1f0c 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -327,6 +327,7 @@ erpnext.patches.v15_0.update_asset_value_for_manual_depr_entries erpnext.patches.v15_0.update_gpa_and_ndb_for_assdeprsch erpnext.patches.v14_0.create_accounting_dimensions_for_closing_balance erpnext.patches.v14_0.update_closing_balances +execute:frappe.db.set_single_value("Accounts Settings", "merge_similar_account_heads", 0) # below migration patches should always run last erpnext.patches.v14_0.migrate_gl_to_payment_ledger execute:frappe.delete_doc_if_exists("Report", "Tax Detail") From 91a398a191a62ea9f7d5583c9fda6e7997337303 Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Tue, 18 Apr 2023 12:55:16 +0530 Subject: [PATCH 23/46] fix: use CombineDatetime instead of Timestamp in QB queries --- erpnext/stock/doctype/batch/batch.py | 5 +++-- .../incorrect_stock_value_report.py | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/erpnext/stock/doctype/batch/batch.py b/erpnext/stock/doctype/batch/batch.py index 3b9fe7b97c..e6f5527543 100644 --- a/erpnext/stock/doctype/batch/batch.py +++ b/erpnext/stock/doctype/batch/batch.py @@ -6,7 +6,7 @@ import frappe from frappe import _ from frappe.model.document import Document from frappe.model.naming import make_autoname, revert_series_if_last -from frappe.query_builder.functions import CurDate, Sum, Timestamp +from frappe.query_builder.functions import CombineDatetime, CurDate, Sum from frappe.utils import cint, flt, get_link_to_form, nowtime from frappe.utils.data import add_days from frappe.utils.jinja import render_template @@ -192,7 +192,8 @@ def get_batch_qty( posting_time = nowtime() query = query.where( - Timestamp(sle.posting_date, sle.posting_time) <= Timestamp(posting_date, posting_time) + CombineDatetime(sle.posting_date, sle.posting_time) + <= CombineDatetime(posting_date, posting_time) ) out = query.run(as_list=True)[0][0] or 0 diff --git a/erpnext/stock/report/incorrect_stock_value_report/incorrect_stock_value_report.py b/erpnext/stock/report/incorrect_stock_value_report/incorrect_stock_value_report.py index df01b14d11..e9c96084d9 100644 --- a/erpnext/stock/report/incorrect_stock_value_report/incorrect_stock_value_report.py +++ b/erpnext/stock/report/incorrect_stock_value_report/incorrect_stock_value_report.py @@ -5,7 +5,7 @@ import frappe from frappe import _ from frappe.query_builder import Field -from frappe.query_builder.functions import Min, Timestamp +from frappe.query_builder.functions import CombineDatetime, Min from frappe.utils import add_days, getdate, today import erpnext @@ -75,7 +75,7 @@ def get_data(report_filters): & (sle.company == report_filters.company) & (sle.is_cancelled == 0) ) - .orderby(Timestamp(sle.posting_date, sle.posting_time), sle.creation) + .orderby(CombineDatetime(sle.posting_date, sle.posting_time), sle.creation) ).run(as_dict=True) for d in data: From e91abbfbe30234a7c577bab13cacea9ab54b4a56 Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Tue, 18 Apr 2023 12:04:34 +0530 Subject: [PATCH 24/46] fix: add item-code filter for SCR supplied-items batch-no --- .../subcontracting_receipt/subcontracting_receipt.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js index 3a2c53f4e4..45289b1dab 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js @@ -67,6 +67,15 @@ frappe.ui.form.on('Subcontracting Receipt', { } }); + frm.set_query('batch_no', 'supplied_items', function(doc, cdt, cdn) { + var row = locals[cdt][cdn]; + return { + filters: { + item: row.rm_item_code + } + } + }); + let batch_no_field = frm.get_docfield("items", "batch_no"); if (batch_no_field) { batch_no_field.get_route_options_for_new_doc = function(row) { From 6fca9adcd4a881b09630f5e0ad875df136e1d6e0 Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Tue, 18 Apr 2023 18:38:28 +0530 Subject: [PATCH 25/46] fix: internal Purchase Receipt GL Entries --- .../doctype/purchase_receipt/purchase_receipt.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index d268cc1196..530427328a 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -380,7 +380,19 @@ class PurchaseReceipt(BuyingController): outgoing_amount = d.base_net_amount if self.is_internal_supplier and d.valuation_rate: - outgoing_amount = d.valuation_rate * d.stock_qty + outgoing_amount = abs( + frappe.db.get_value( + "Stock Ledger Entry", + { + "voucher_type": "Purchase Receipt", + "voucher_no": self.name, + "voucher_detail_no": d.name, + "warehouse": d.from_warehouse, + "is_cancelled": 0, + }, + "stock_value_difference", + ) + ) credit_amount = outgoing_amount if credit_amount: From 11cb2db3fee91877fc6c05ae692b8163ca61d84e Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Wed, 19 Apr 2023 11:51:08 +0530 Subject: [PATCH 26/46] refactor: move set_missing_ref_detials out of set_missing_values --- erpnext/accounts/doctype/payment_entry/payment_entry.py | 4 ++-- erpnext/accounts/utils.py | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 3583dc7a90..086e1e3c35 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -60,6 +60,7 @@ class PaymentEntry(AccountsController): def validate(self): self.setup_party_account_field() self.set_missing_values() + self.set_missing_ref_details() self.validate_payment_type() self.validate_party_details() self.set_exchange_rate() @@ -219,8 +220,6 @@ class PaymentEntry(AccountsController): else self.paid_to_account_currency ) - self.set_missing_ref_details() - def set_missing_ref_details(self, force=False): for d in self.get("references"): if d.allocated_amount: @@ -1811,6 +1810,7 @@ def get_payment_entry( pe.setup_party_account_field() pe.set_missing_values() + pe.set_missing_ref_details() update_accounting_dimensions(pe, doc) diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index 2ab9ef64b3..f10cff0686 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -646,6 +646,7 @@ def update_reference_in_payment_entry(d, payment_entry, do_not_save=False): payment_entry.flags.ignore_validate_update_after_submit = True payment_entry.setup_party_account_field() payment_entry.set_missing_values() + payment_entry.set_missing_ref_details() payment_entry.set_amounts() if not do_not_save: From c86c543fbfaa46c45f06a745a0841e821e14e712 Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Tue, 18 Apr 2023 21:03:23 +0530 Subject: [PATCH 27/46] test: add test case for internal PR GL Entries --- .../purchase_receipt/test_purchase_receipt.py | 83 +++++++++++++++++++ 1 file changed, 83 insertions(+) diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index 7567cfe98c..8af279a9c8 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -1610,6 +1610,89 @@ class TestPurchaseReceipt(FrappeTestCase): frappe.db.set_single_value("Stock Settings", "over_delivery_receipt_allowance", 0) + def test_internal_pr_gl_entries(self): + from erpnext.stock import get_warehouse_account_map + from erpnext.stock.doctype.delivery_note.delivery_note import make_inter_company_purchase_receipt + from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note + from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry + from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import ( + create_stock_reconciliation, + ) + + prepare_data_for_internal_transfer() + customer = "_Test Internal Customer 2" + company = "_Test Company with perpetual inventory" + from_warehouse = create_warehouse("_Test Internal From Warehouse New", company=company) + target_warehouse = create_warehouse("_Test Internal GIT Warehouse New", company=company) + to_warehouse = create_warehouse("_Test Internal To Warehouse New", company=company) + + item = make_item(properties={"is_stock_item": 1, "valuation_rate": 100}) + make_stock_entry( + purpose="Material Receipt", + item_code=item.name, + qty=10, + company=company, + to_warehouse=from_warehouse, + posting_date=add_days(today(), -3), + ) + + # Step - 1: Create Delivery Note with Internal Customer + dn = create_delivery_note( + item_code=item.name, + company=company, + customer=customer, + cost_center="Main - TCP1", + expense_account="Cost of Goods Sold - TCP1", + qty=10, + rate=100, + warehouse=from_warehouse, + target_warehouse=target_warehouse, + posting_date=add_days(today(), -2), + ) + + # Step - 2: Create Internal Purchase Receipt + pr = make_inter_company_purchase_receipt(dn.name) + pr.items[0].qty = 10 + pr.items[0].from_warehouse = target_warehouse + pr.items[0].warehouse = to_warehouse + pr.items[0].rejected_warehouse = from_warehouse + pr.save() + pr.submit() + + # Step - 3: Create back-date Stock Reconciliation [After DN and Before PR] + create_stock_reconciliation( + item_code=item, + warehouse=target_warehouse, + qty=10, + rate=50, + company=company, + posting_date=add_days(today(), -1), + ) + + warehouse_account = get_warehouse_account_map(company) + stock_account_value = frappe.db.get_value( + "GL Entry", + { + "account": warehouse_account[target_warehouse]["account"], + "voucher_type": "Purchase Receipt", + "voucher_no": pr.name, + "is_cancelled": 0, + }, + fieldname=["credit"], + ) + stock_diff = frappe.db.get_value( + "Stock Ledger Entry", + { + "voucher_type": "Purchase Receipt", + "voucher_no": pr.name, + "is_cancelled": 0, + }, + fieldname=["sum(stock_value_difference)"], + ) + + # Value of Stock Account should be equal to the sum of Stock Value Difference + self.assertEqual(stock_account_value, stock_diff) + def prepare_data_for_internal_transfer(): from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier From b7d6e30f63c70408fa29993e5cd958c26fb2e33c Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Wed, 19 Apr 2023 12:11:05 +0530 Subject: [PATCH 28/46] refactor: update ref details for selected references set_missing_ref_details can update only for selected references --- erpnext/accounts/doctype/payment_entry/payment_entry.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 086e1e3c35..ee4d4d29e2 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -220,9 +220,16 @@ class PaymentEntry(AccountsController): else self.paid_to_account_currency ) - def set_missing_ref_details(self, force=False): + def set_missing_ref_details( + self, force: bool = False, update_ref_details_only_for: list | None = None + ) -> None: for d in self.get("references"): if d.allocated_amount: + if update_ref_details_only_for and ( + not (d.reference_doctype, d.reference_name) in update_ref_details_only_for + ): + continue + ref_details = get_reference_details( d.reference_doctype, d.reference_name, self.party_account_currency ) From 59f3fedbf7b92161f842479d05563f723d61bd78 Mon Sep 17 00:00:00 2001 From: Vishal Dhayagude Date: Wed, 19 Apr 2023 15:57:28 +0530 Subject: [PATCH 29/46] fix: batch qty conversion factor issue fixed in pos transaction (#34917) --- erpnext/selling/page/point_of_sale/pos_controller.js | 6 ++++-- erpnext/stock/doctype/batch/batch.py | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/erpnext/selling/page/point_of_sale/pos_controller.js b/erpnext/selling/page/point_of_sale/pos_controller.js index 46320e5538..016ebf0cd1 100644 --- a/erpnext/selling/page/point_of_sale/pos_controller.js +++ b/erpnext/selling/page/point_of_sale/pos_controller.js @@ -559,8 +559,10 @@ erpnext.PointOfSale.Controller = class { item_row = this.frm.add_child('items', new_item); - if (field === 'qty' && value !== 0 && !this.allow_negative_stock) - await this.check_stock_availability(item_row, value, this.frm.doc.set_warehouse); + if (field === 'qty' && value !== 0 && !this.allow_negative_stock) { + const qty_needed = value * item_row.conversion_factor; + await this.check_stock_availability(item_row, qty_needed, this.frm.doc.set_warehouse); + } await this.trigger_new_item_events(item_row); diff --git a/erpnext/stock/doctype/batch/batch.py b/erpnext/stock/doctype/batch/batch.py index 3b9fe7b97c..d8f119a20c 100644 --- a/erpnext/stock/doctype/batch/batch.py +++ b/erpnext/stock/doctype/batch/batch.py @@ -376,7 +376,7 @@ def get_pos_reserved_batch_qty(filters): p = frappe.qb.DocType("POS Invoice").as_("p") item = frappe.qb.DocType("POS Invoice Item").as_("item") - sum_qty = frappe.query_builder.functions.Sum(item.qty).as_("qty") + sum_qty = frappe.query_builder.functions.Sum(item.stock_qty).as_("qty") reserved_batch_qty = ( frappe.qb.from_(p) From a77182645f734db83b728f90d4cfc55acf6e28cd Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Wed, 19 Apr 2023 20:35:14 +0530 Subject: [PATCH 30/46] fix: Incorrect difference value in Stock and Account Value Comparison report --- .../stock_and_account_value_comparison.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/stock/report/stock_and_account_value_comparison/stock_and_account_value_comparison.py b/erpnext/stock/report/stock_and_account_value_comparison/stock_and_account_value_comparison.py index 106e877c4c..5fb456502e 100644 --- a/erpnext/stock/report/stock_and_account_value_comparison/stock_and_account_value_comparison.py +++ b/erpnext/stock/report/stock_and_account_value_comparison/stock_and_account_value_comparison.py @@ -41,7 +41,7 @@ def get_data(report_filters): key = (d.voucher_type, d.voucher_no) gl_data = voucher_wise_gl_data.get(key) or {} d.account_value = gl_data.get("account_value", 0) - d.difference_value = d.stock_value - d.account_value + d.difference_value = abs(d.stock_value) - abs(d.account_value) if abs(d.difference_value) > 0.1: data.append(d) From fcfa8842a7d0413a865741be4f7904034b365411 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Thu, 20 Apr 2023 09:48:15 +0530 Subject: [PATCH 31/46] fix: limit stock reco issue --- erpnext/stock/stock_ledger.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index b638f08ed9..c197769d0a 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -1453,6 +1453,7 @@ def get_next_stock_reco(kwargs): ) .orderby(CombineDatetime(sle.posting_date, sle.posting_time)) .orderby(sle.creation) + .limit(1) ) if kwargs.get("batch_no"): From 11c8503180cda70b1aebce3711def05eec840133 Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Wed, 19 Apr 2023 16:01:45 +0530 Subject: [PATCH 32/46] fix(test): `test_backdated_stock_reco_cancellation_future_negative_stock` --- .../doctype/stock_reconciliation/test_stock_reconciliation.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py index 7d59441d8b..2e5d2c3aaf 100644 --- a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py @@ -530,7 +530,9 @@ class TestStockReconciliation(FrappeTestCase, StockTestMixin): # check if cancellation of stock reco is blocked self.assertRaises(NegativeStockError, sr.cancel) - repost_exists = bool(frappe.db.exists("Repost Item Valuation", {"voucher_no": sr.name})) + repost_exists = bool( + frappe.db.exists("Repost Item Valuation", {"voucher_no": sr.name, "status": "Queued"}) + ) self.assertFalse(repost_exists, msg="Negative stock validation not working on reco cancellation") def test_intermediate_sr_bin_update(self): From df0682fa8c85221e214fda0e05a1699377868fae Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 20 Apr 2023 10:39:15 +0530 Subject: [PATCH 33/46] fix: broken set exchagne gain/loss btn broken in payment entry --- erpnext/accounts/doctype/payment_entry/payment_entry.js | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js index ed6d0a710a..07761c7705 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.js +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js @@ -977,6 +977,7 @@ frappe.ui.form.on('Payment Entry', { precision("difference_amount")); const add_deductions = (details) => { + let row = null; if (!write_off_row.length && difference_amount) { row = frm.add_child("deductions"); row.account = details[account]; From ea6eeace8028ded8d19ae4795887ec9750298d89 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Thu, 20 Apr 2023 12:48:44 +0530 Subject: [PATCH 34/46] fix: filtering via batch no(#34950) * fix: filtering via batch no --- erpnext/stock/stock_ledger.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index c197769d0a..0f12987fbb 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -1457,7 +1457,7 @@ def get_next_stock_reco(kwargs): ) if kwargs.get("batch_no"): - query.where(sle.batch_no == kwargs.get("batch_no")) + query = query.where(sle.batch_no == kwargs.get("batch_no")) return query.run(as_dict=True) From b572bef71d88e604d730b70e59d0b219fac54c32 Mon Sep 17 00:00:00 2001 From: Nihantra Patel Date: Thu, 20 Apr 2023 14:32:32 +0530 Subject: [PATCH 35/46] fix: process_loss_percentage in BOM --- erpnext/manufacturing/doctype/bom/bom.js | 1 - 1 file changed, 1 deletion(-) diff --git a/erpnext/manufacturing/doctype/bom/bom.js b/erpnext/manufacturing/doctype/bom/bom.js index 4304193afa..7cdcef9c7a 100644 --- a/erpnext/manufacturing/doctype/bom/bom.js +++ b/erpnext/manufacturing/doctype/bom/bom.js @@ -411,7 +411,6 @@ frappe.ui.form.on("BOM", { } frm.set_value("process_loss_qty", qty); - frm.set_value("add_process_loss_cost_in_fg", qty ? 1: 0); } }); From 8108b2de0aa86b743c767808e38349644c2f9be9 Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Thu, 20 Apr 2023 12:39:30 +0530 Subject: [PATCH 36/46] fix: `PermissionError` in Work Order --- .../doctype/work_order/work_order.js | 20 +++++++++---------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/erpnext/manufacturing/doctype/work_order/work_order.js b/erpnext/manufacturing/doctype/work_order/work_order.js index 97480b2945..d0c9966f8b 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.js +++ b/erpnext/manufacturing/doctype/work_order/work_order.js @@ -625,20 +625,18 @@ erpnext.work_order = { // all materials transferred for manufacturing, make this primary finish_btn.addClass('btn-primary'); } - } else { - frappe.db.get_doc("Manufacturing Settings").then((doc) => { - let allowance_percentage = doc.overproduction_percentage_for_work_order; + } else if (frm.doc.__onload && frm.doc.__onload.overproduction_percentage) { + let allowance_percentage = frm.doc.__onload.overproduction_percentage; - if (allowance_percentage > 0) { - let allowed_qty = frm.doc.qty + ((allowance_percentage / 100) * frm.doc.qty); + if (allowance_percentage > 0) { + let allowed_qty = frm.doc.qty + ((allowance_percentage / 100) * frm.doc.qty); - if ((flt(doc.produced_qty) < allowed_qty)) { - frm.add_custom_button(__('Finish'), function() { - erpnext.work_order.make_se(frm, 'Manufacture'); - }); - } + if ((flt(doc.produced_qty) < allowed_qty)) { + frm.add_custom_button(__('Finish'), function() { + erpnext.work_order.make_se(frm, 'Manufacture'); + }); } - }); + } } } } else { From a90a5b4aa4c8a363f66496d29f23d183928453ae Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Thu, 20 Apr 2023 16:01:05 +0530 Subject: [PATCH 37/46] fix: removed depends on for the Employee Detail section --- erpnext/projects/doctype/timesheet/timesheet.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/erpnext/projects/doctype/timesheet/timesheet.json b/erpnext/projects/doctype/timesheet/timesheet.json index 468300661a..ba6262dc3d 100644 --- a/erpnext/projects/doctype/timesheet/timesheet.json +++ b/erpnext/projects/doctype/timesheet/timesheet.json @@ -96,7 +96,6 @@ "read_only": 1 }, { - "depends_on": "eval:!doc.work_order || doc.docstatus == 1", "fieldname": "employee_detail", "fieldtype": "Section Break", "label": "Employee Detail" @@ -311,7 +310,7 @@ "idx": 1, "is_submittable": 1, "links": [], - "modified": "2023-02-14 04:55:41.735991", + "modified": "2023-04-20 15:59:11.107831", "modified_by": "Administrator", "module": "Projects", "name": "Timesheet", From c3b5dcb7675b07979b2095e885ed12037ceac940 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Thu, 20 Apr 2023 16:36:10 +0530 Subject: [PATCH 38/46] fix: stock entry type issue --- erpnext/stock/doctype/material_request/material_request.py | 2 +- .../stock/doctype/material_request/test_material_request.py | 4 ++++ erpnext/stock/doctype/stock_entry/stock_entry.py | 2 +- erpnext/stock/doctype/stock_entry/test_stock_entry.py | 3 +++ 4 files changed, 9 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/doctype/material_request/material_request.py b/erpnext/stock/doctype/material_request/material_request.py index 8aeb7511f4..3967282358 100644 --- a/erpnext/stock/doctype/material_request/material_request.py +++ b/erpnext/stock/doctype/material_request/material_request.py @@ -616,7 +616,7 @@ def make_stock_entry(source_name, target_doc=None): target.set_transfer_qty() target.set_actual_qty() target.calculate_rate_and_amount(raise_error_if_no_rate=False) - target.set_stock_entry_type() + target.stock_entry_type = target.purpose target.set_job_card_data() doclist = get_mapped_doc( diff --git a/erpnext/stock/doctype/material_request/test_material_request.py b/erpnext/stock/doctype/material_request/test_material_request.py index a707c74c7d..03f58c664d 100644 --- a/erpnext/stock/doctype/material_request/test_material_request.py +++ b/erpnext/stock/doctype/material_request/test_material_request.py @@ -54,6 +54,8 @@ class TestMaterialRequest(FrappeTestCase): mr.submit() se = make_stock_entry(mr.name) + self.assertEqual(se.stock_entry_type, "Material Transfer") + self.assertEqual(se.purpose, "Material Transfer") self.assertEqual(se.doctype, "Stock Entry") self.assertEqual(len(se.get("items")), len(mr.get("items"))) @@ -69,6 +71,8 @@ class TestMaterialRequest(FrappeTestCase): in_transit_warehouse = get_in_transit_warehouse(mr.company) se = make_in_transit_stock_entry(mr.name, in_transit_warehouse) + self.assertEqual(se.stock_entry_type, "Material Transfer") + self.assertEqual(se.purpose, "Material Transfer") self.assertEqual(se.doctype, "Stock Entry") for row in se.get("items"): self.assertEqual(row.t_warehouse, in_transit_warehouse) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 36c875f308..b5e5299f87 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -2346,7 +2346,7 @@ def move_sample_to_retention_warehouse(company, items): @frappe.whitelist() def make_stock_in_entry(source_name, target_doc=None): def set_missing_values(source, target): - target.set_stock_entry_type() + target.stock_entry_type = "Material Transfer" target.set_missing_values() def update_item(source_doc, target_doc, source_parent): diff --git a/erpnext/stock/doctype/stock_entry/test_stock_entry.py b/erpnext/stock/doctype/stock_entry/test_stock_entry.py index cc06bd709a..c43a1b1b81 100644 --- a/erpnext/stock/doctype/stock_entry/test_stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/test_stock_entry.py @@ -202,6 +202,9 @@ class TestStockEntry(FrappeTestCase): ) end_transit_entry = make_stock_in_entry(transit_entry.name) + + self.assertEqual(end_transit_entry.stock_entry_type, "Material Transfer") + self.assertEqual(end_transit_entry.purpose, "Material Transfer") self.assertEqual(transit_entry.name, end_transit_entry.outgoing_stock_entry) self.assertEqual(transit_entry.name, end_transit_entry.items[0].against_stock_entry) self.assertEqual(transit_entry.items[0].name, end_transit_entry.items[0].ste_detail) From 17ef3c964f194816c60d49fa8ec471b184869d3e Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Tue, 4 Apr 2023 17:49:50 +0530 Subject: [PATCH 39/46] fix: set `frappe.flags.company` to call regional code accurately --- erpnext/accounts/party.py | 2 ++ erpnext/controllers/taxes_and_totals.py | 2 ++ 2 files changed, 4 insertions(+) diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py index ac9368e69c..7747042825 100644 --- a/erpnext/accounts/party.py +++ b/erpnext/accounts/party.py @@ -259,6 +259,8 @@ def set_address_details( ) if doctype in TRANSACTION_TYPES: + # required to set correct region + frappe.flags.company = company get_regional_address_details(party_details, doctype, company) return party_address, shipping_address diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py index 1edd7bf85e..4661c5ca7e 100644 --- a/erpnext/controllers/taxes_and_totals.py +++ b/erpnext/controllers/taxes_and_totals.py @@ -976,6 +976,8 @@ def get_itemised_tax_breakup_html(doc): @frappe.whitelist() def get_round_off_applicable_accounts(company, account_list): + # required to set correct region + frappe.flags.company = company account_list = get_regional_round_off_accounts(company, account_list) return account_list From 2fa641f86de8ad0be5f39575726d3672bfd885d4 Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Tue, 4 Apr 2023 17:50:31 +0530 Subject: [PATCH 40/46] fix: simplify `erpnext.get_region` --- erpnext/__init__.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/erpnext/__init__.py b/erpnext/__init__.py index e0f0c98e9c..5ccb1b942f 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -120,12 +120,14 @@ def get_region(company=None): You can also set global company flag in `frappe.flags.company` """ - if company or frappe.flags.company: - return frappe.get_cached_value("Company", company or frappe.flags.company, "country") - elif frappe.flags.country: - return frappe.flags.country - else: - return frappe.get_system_settings("country") + + if not company: + company = frappe.local.flags.company + + if company: + return frappe.get_cached_value("Company", company, "country") + + return frappe.flags.country or frappe.get_system_settings("country") def allow_regional(fn): From 776b56ccd13c45e9eda0a67e8c9f42e65acb3135 Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Tue, 4 Apr 2023 19:24:07 +0530 Subject: [PATCH 41/46] fix: use `functools.wraps` to preserve doc signature --- erpnext/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erpnext/__init__.py b/erpnext/__init__.py index 5ccb1b942f..c9c9c9c6df 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -1,3 +1,4 @@ +import functools import inspect import frappe @@ -138,6 +139,7 @@ def allow_regional(fn): def myfunction(): pass""" + @functools.wraps(fn) def caller(*args, **kwargs): overrides = frappe.get_hooks("regional_overrides", {}).get(get_region()) function_path = f"{inspect.getmodule(fn).__name__}.{fn.__name__}" From a02705ded7dfd8f2fbd1b4ad0ace5a8b8f3da45c Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 21 Apr 2023 13:25:32 +0530 Subject: [PATCH 42/46] chore: Move source and campaign to more info section (#34946) --- .../doctype/sales_order/sales_order.json | 18 +++--------------- .../doctype/delivery_note/delivery_note.json | 13 ++++--------- 2 files changed, 7 insertions(+), 24 deletions(-) diff --git a/erpnext/selling/doctype/sales_order/sales_order.json b/erpnext/selling/doctype/sales_order/sales_order.json index ccea8407ab..4f498fb20d 100644 --- a/erpnext/selling/doctype/sales_order/sales_order.json +++ b/erpnext/selling/doctype/sales_order/sales_order.json @@ -30,10 +30,6 @@ "cost_center", "dimension_col_break", "project", - "column_break_77", - "source", - "campaign", - "custom_dimensions_section", "currency_and_price_list", "currency", "conversion_rate", @@ -162,7 +158,9 @@ "is_internal_customer", "represents_company", "column_break_152", + "source", "inter_company_order_reference", + "campaign", "party_account_currency", "connections_tab" ], @@ -1164,12 +1162,6 @@ "print_hide": 1, "read_only": 1 }, - { - "fieldname": "column_break_77", - "fieldtype": "Column Break", - "hide_days": 1, - "hide_seconds": 1 - }, { "fieldname": "source", "fieldtype": "Link", @@ -1612,10 +1604,6 @@ "fieldname": "column_break_92", "fieldtype": "Column Break" }, - { - "fieldname": "custom_dimensions_section", - "fieldtype": "Section Break" - }, { "collapsible": 1, "fieldname": "additional_info_section", @@ -1643,7 +1631,7 @@ "idx": 105, "is_submittable": 1, "links": [], - "modified": "2022-12-12 18:34:00.681780", + "modified": "2023-04-20 11:14:01.036202", "modified_by": "Administrator", "module": "Selling", "name": "Sales Order", diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.json b/erpnext/stock/doctype/delivery_note/delivery_note.json index 0c1f82029e..2adf9c310f 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.json +++ b/erpnext/stock/doctype/delivery_note/delivery_note.json @@ -28,8 +28,6 @@ "column_break_18", "project", "dimension_col_break", - "campaign", - "source", "custom_dimensions_section", "currency_and_price_list", "currency", @@ -161,11 +159,12 @@ "inter_company_reference", "customer_group", "territory", + "source", + "campaign", "column_break5", "excise_page", "instructions", - "connections_tab", - "column_break_25" + "connections_tab" ], "fields": [ { @@ -1339,10 +1338,6 @@ "fieldname": "column_break_10", "fieldtype": "Column Break" }, - { - "fieldname": "column_break_25", - "fieldtype": "Column Break" - }, { "fieldname": "section_break_30", "fieldtype": "Section Break", @@ -1403,7 +1398,7 @@ "idx": 146, "is_submittable": 1, "links": [], - "modified": "2023-02-14 04:45:44.179670", + "modified": "2023-04-21 11:15:23.931084", "modified_by": "Administrator", "module": "Stock", "name": "Delivery Note", From af8da53cf4920688e8b756d25685964e0e57b9f3 Mon Sep 17 00:00:00 2001 From: HENRY Florian Date: Fri, 21 Apr 2023 09:56:32 +0200 Subject: [PATCH 43/46] fix: FEC report for France accountancy (#34781) * fix: FEC report for France Accountancy legal requirement * fix: FEC report for France Accountancy legal requirement * fix: change to query standard * fix: change to query standard * fix: columns to standard dict * fix: columns to standard dict * fix: columns to data * refactor: french report FEC * refactor: french report FEC (2) --------- Co-authored-by: barredterra <14891507+barredterra@users.noreply.github.com> --- .../fichier_des_ecritures_comptables_[fec].py | 305 ++++++++++++------ 1 file changed, 198 insertions(+), 107 deletions(-) diff --git a/erpnext/regional/report/fichier_des_ecritures_comptables_[fec]/fichier_des_ecritures_comptables_[fec].py b/erpnext/regional/report/fichier_des_ecritures_comptables_[fec]/fichier_des_ecritures_comptables_[fec].py index c75179ee5d..6717989008 100644 --- a/erpnext/regional/report/fichier_des_ecritures_comptables_[fec]/fichier_des_ecritures_comptables_[fec].py +++ b/erpnext/regional/report/fichier_des_ecritures_comptables_[fec]/fichier_des_ecritures_comptables_[fec].py @@ -1,31 +1,135 @@ # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt - import re import frappe from frappe import _ from frappe.utils import format_datetime +COLUMNS = [ + { + "label": "JournalCode", + "fieldname": "JournalCode", + "fieldtype": "Data", + "width": 90, + }, + { + "label": "JournalLib", + "fieldname": "JournalLib", + "fieldtype": "Data", + "width": 90, + }, + { + "label": "EcritureNum", + "fieldname": "EcritureNum", + "fieldtype": "Data", + "width": 90, + }, + { + "label": "EcritureDate", + "fieldname": "EcritureDate", + "fieldtype": "Data", + "width": 90, + }, + { + "label": "CompteNum", + "fieldname": "CompteNum", + "fieldtype": "Link", + "options": "Account", + "width": 100, + }, + { + "label": "CompteLib", + "fieldname": "CompteLib", + "fieldtype": "Link", + "options": "Account", + "width": 200, + }, + { + "label": "CompAuxNum", + "fieldname": "CompAuxNum", + "fieldtype": "Data", + "width": 90, + }, + { + "label": "CompAuxLib", + "fieldname": "CompAuxLib", + "fieldtype": "Data", + "width": 90, + }, + { + "label": "PieceRef", + "fieldname": "PieceRef", + "fieldtype": "Data", + "width": 90, + }, + { + "label": "PieceDate", + "fieldname": "PieceDate", + "fieldtype": "Data", + "width": 90, + }, + { + "label": "EcritureLib", + "fieldname": "EcritureLib", + "fieldtype": "Data", + "width": 90, + }, + { + "label": "Debit", + "fieldname": "Debit", + "fieldtype": "Data", + "width": 90, + }, + { + "label": "Credit", + "fieldname": "Credit", + "fieldtype": "Data", + "width": 90, + }, + { + "label": "EcritureLet", + "fieldname": "EcritureLet", + "fieldtype": "Data", + "width": 90, + }, + { + "label": "DateLet", + "fieldname": "DateLet", + "fieldtype": "Data", + "width": 90, + }, + { + "label": "ValidDate", + "fieldname": "ValidDate", + "fieldtype": "Data", + "width": 90, + }, + { + "label": "Montantdevise", + "fieldname": "Montantdevise", + "fieldtype": "Data", + "width": 90, + }, + { + "label": "Idevise", + "fieldname": "Idevise", + "fieldtype": "Data", + "width": 90, + }, +] + def execute(filters=None): - account_details = {} - for acc in frappe.db.sql("""select name, is_group from tabAccount""", as_dict=1): - account_details.setdefault(acc.name, acc) - - validate_filters(filters, account_details) - - filters = set_account_currency(filters) - - columns = get_columns(filters) - - res = get_result(filters) - - return columns, res + validate_filters(filters) + return COLUMNS, get_result( + company=filters["company"], + fiscal_year=filters["fiscal_year"], + ) -def validate_filters(filters, account_details): +def validate_filters(filters): if not filters.get("company"): frappe.throw(_("{0} is mandatory").format(_("Company"))) @@ -33,107 +137,96 @@ def validate_filters(filters, account_details): frappe.throw(_("{0} is mandatory").format(_("Fiscal Year"))) -def set_account_currency(filters): +def get_gl_entries(company, fiscal_year): + gle = frappe.qb.DocType("GL Entry") + sales_invoice = frappe.qb.DocType("Sales Invoice") + purchase_invoice = frappe.qb.DocType("Purchase Invoice") + journal_entry = frappe.qb.DocType("Journal Entry") + payment_entry = frappe.qb.DocType("Payment Entry") + customer = frappe.qb.DocType("Customer") + supplier = frappe.qb.DocType("Supplier") + employee = frappe.qb.DocType("Employee") - filters["company_currency"] = frappe.get_cached_value( - "Company", filters.company, "default_currency" + debit = frappe.query_builder.functions.Sum(gle.debit).as_("debit") + credit = frappe.query_builder.functions.Sum(gle.credit).as_("credit") + debit_currency = frappe.query_builder.functions.Sum(gle.debit_in_account_currency).as_( + "debitCurr" + ) + credit_currency = frappe.query_builder.functions.Sum(gle.credit_in_account_currency).as_( + "creditCurr" ) - return filters - - -def get_columns(filters): - columns = [ - "JournalCode" + "::90", - "JournalLib" + "::90", - "EcritureNum" + ":Dynamic Link:90", - "EcritureDate" + "::90", - "CompteNum" + ":Link/Account:100", - "CompteLib" + ":Link/Account:200", - "CompAuxNum" + "::90", - "CompAuxLib" + "::90", - "PieceRef" + "::90", - "PieceDate" + "::90", - "EcritureLib" + "::90", - "Debit" + "::90", - "Credit" + "::90", - "EcritureLet" + "::90", - "DateLet" + "::90", - "ValidDate" + "::90", - "Montantdevise" + "::90", - "Idevise" + "::90", - ] - - return columns - - -def get_result(filters): - gl_entries = get_gl_entries(filters) - - result = get_result_as_list(gl_entries, filters) - - return result - - -def get_gl_entries(filters): - - group_by_condition = ( - "group by voucher_type, voucher_no, account" - if filters.get("group_by_voucher") - else "group by gl.name" + query = ( + frappe.qb.from_(gle) + .left_join(sales_invoice) + .on(gle.voucher_no == sales_invoice.name) + .left_join(purchase_invoice) + .on(gle.voucher_no == purchase_invoice.name) + .left_join(journal_entry) + .on(gle.voucher_no == journal_entry.name) + .left_join(payment_entry) + .on(gle.voucher_no == payment_entry.name) + .left_join(customer) + .on(gle.party == customer.name) + .left_join(supplier) + .on(gle.party == supplier.name) + .left_join(employee) + .on(gle.party == employee.name) + .select( + gle.posting_date.as_("GlPostDate"), + gle.name.as_("GlName"), + gle.account, + gle.transaction_date, + debit, + credit, + debit_currency, + credit_currency, + gle.voucher_type, + gle.voucher_no, + gle.against_voucher_type, + gle.against_voucher, + gle.account_currency, + gle.against, + gle.party_type, + gle.party, + sales_invoice.name.as_("InvName"), + sales_invoice.title.as_("InvTitle"), + sales_invoice.posting_date.as_("InvPostDate"), + purchase_invoice.name.as_("PurName"), + purchase_invoice.title.as_("PurTitle"), + purchase_invoice.posting_date.as_("PurPostDate"), + journal_entry.cheque_no.as_("JnlRef"), + journal_entry.posting_date.as_("JnlPostDate"), + journal_entry.title.as_("JnlTitle"), + payment_entry.name.as_("PayName"), + payment_entry.posting_date.as_("PayPostDate"), + payment_entry.title.as_("PayTitle"), + customer.customer_name, + customer.name.as_("cusName"), + supplier.supplier_name, + supplier.name.as_("supName"), + employee.employee_name, + employee.name.as_("empName"), + ) + .where((gle.company == company) & (gle.fiscal_year == fiscal_year)) + .groupby(gle.voucher_type, gle.voucher_no, gle.account) + .orderby(gle.posting_date, gle.voucher_no) ) - gl_entries = frappe.db.sql( - """ - select - gl.posting_date as GlPostDate, gl.name as GlName, gl.account, gl.transaction_date, - sum(gl.debit) as debit, sum(gl.credit) as credit, - sum(gl.debit_in_account_currency) as debitCurr, sum(gl.credit_in_account_currency) as creditCurr, - gl.voucher_type, gl.voucher_no, gl.against_voucher_type, - gl.against_voucher, gl.account_currency, gl.against, - gl.party_type, gl.party, - inv.name as InvName, inv.title as InvTitle, inv.posting_date as InvPostDate, - pur.name as PurName, pur.title as PurTitle, pur.posting_date as PurPostDate, - jnl.cheque_no as JnlRef, jnl.posting_date as JnlPostDate, jnl.title as JnlTitle, - pay.name as PayName, pay.posting_date as PayPostDate, pay.title as PayTitle, - cus.customer_name, cus.name as cusName, - sup.supplier_name, sup.name as supName, - emp.employee_name, emp.name as empName, - stu.title as student_name, stu.name as stuName, - member_name, mem.name as memName - - from `tabGL Entry` gl - left join `tabSales Invoice` inv on gl.voucher_no = inv.name - left join `tabPurchase Invoice` pur on gl.voucher_no = pur.name - left join `tabJournal Entry` jnl on gl.voucher_no = jnl.name - left join `tabPayment Entry` pay on gl.voucher_no = pay.name - left join `tabCustomer` cus on gl.party = cus.name - left join `tabSupplier` sup on gl.party = sup.name - left join `tabEmployee` emp on gl.party = emp.name - left join `tabStudent` stu on gl.party = stu.name - left join `tabMember` mem on gl.party = mem.name - where gl.company=%(company)s and gl.fiscal_year=%(fiscal_year)s - {group_by_condition} - order by GlPostDate, voucher_no""".format( - group_by_condition=group_by_condition - ), - filters, - as_dict=1, - ) - - return gl_entries + return query.run(as_dict=True) -def get_result_as_list(data, filters): +def get_result(company, fiscal_year): + data = get_gl_entries(company, fiscal_year) + result = [] - company_currency = frappe.get_cached_value("Company", filters.company, "default_currency") + company_currency = frappe.get_cached_value("Company", company, "default_currency") accounts = frappe.get_all( - "Account", filters={"Company": filters.company}, fields=["name", "account_number"] + "Account", filters={"Company": company}, fields=["name", "account_number"] ) for d in data: - JournalCode = re.split("-|/|[0-9]", d.get("voucher_no"))[0] if d.get("voucher_no").startswith("{0}-".format(JournalCode)) or d.get("voucher_no").startswith( @@ -141,9 +234,7 @@ def get_result_as_list(data, filters): ): EcritureNum = re.split("-|/", d.get("voucher_no"))[1] else: - EcritureNum = re.search( - r"{0}(\d+)".format(JournalCode), d.get("voucher_no"), re.IGNORECASE - ).group(1) + EcritureNum = re.search(r"{0}(\d+)".format(JournalCode), d.get("voucher_no"), re.IGNORECASE)[1] EcritureDate = format_datetime(d.get("GlPostDate"), "yyyyMMdd") @@ -185,7 +276,7 @@ def get_result_as_list(data, filters): ValidDate = format_datetime(d.get("GlPostDate"), "yyyyMMdd") - PieceRef = d.get("voucher_no") if d.get("voucher_no") else "Sans Reference" + PieceRef = d.get("voucher_no") or "Sans Reference" # EcritureLib is the reference title unless it is an opening entry if d.get("is_opening") == "Yes": From 19911b48fdb89ca943ce1e5131eb465ebe12547c Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Fri, 21 Apr 2023 16:49:48 +0530 Subject: [PATCH 44/46] fix: validation for internal transfer entry --- .../doctype/sales_invoice/sales_invoice.js | 1 + erpnext/controllers/accounts_controller.py | 11 +++- .../purchase_receipt/test_purchase_receipt.py | 58 +++++++++++++++++++ 3 files changed, 69 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js index 56e412b297..8cb29505eb 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js @@ -334,6 +334,7 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends e } make_inter_company_invoice() { + let me = this; frappe.model.open_mapped_doc({ method: "erpnext.accounts.doctype.sales_invoice.sales_invoice.make_inter_company_purchase_invoice", frm: me.frm diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index c7416228ee..642d51c325 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -5,7 +5,7 @@ import json import frappe -from frappe import _, throw +from frappe import _, bold, throw from frappe.model.workflow import get_workflow_name, is_transition_condition_satisfied from frappe.query_builder.functions import Abs, Sum from frappe.utils import ( @@ -405,6 +405,15 @@ class AccountsController(TransactionBase): msg += _("Please create purchase from internal sale or delivery document itself") frappe.throw(msg, title=_("Internal Sales Reference Missing")) + label = "Delivery Note Item" if self.doctype == "Purchase Receipt" else "Sales Invoice Item" + + field = frappe.scrub(label) + + for row in self.get("items"): + if not row.get(field): + msg = f"At Row {row.idx}: The field {bold(label)} is mandatory for internal transfer" + frappe.throw(_(msg), title=_("Internal Transfer Reference Missing")) + def disable_pricing_rule_on_internal_transfer(self): if not self.get("ignore_pricing_rule") and self.is_internal_transfer(): self.ignore_pricing_rule = 1 diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index 8af279a9c8..c34f9daeef 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -1693,6 +1693,64 @@ class TestPurchaseReceipt(FrappeTestCase): # Value of Stock Account should be equal to the sum of Stock Value Difference self.assertEqual(stock_account_value, stock_diff) + def test_internal_pr_reference(self): + item = make_item(properties={"is_stock_item": 1, "valuation_rate": 100}) + customer = "_Test Internal Customer 2" + company = "_Test Company with perpetual inventory" + from_warehouse = create_warehouse("_Test Internal From Warehouse New 1", company=company) + target_warehouse = create_warehouse("_Test Internal GIT Warehouse New 1", company=company) + to_warehouse = create_warehouse("_Test Internal To Warehouse New 1", company=company) + + # Step 2: Create Stock Entry (Material Receipt) + from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry + + make_stock_entry( + purpose="Material Receipt", + item_code=item.name, + qty=15, + company=company, + to_warehouse=from_warehouse, + ) + + # Step 3: Create Delivery Note with Internal Customer + from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note + + dn = create_delivery_note( + item_code=item.name, + company=company, + customer=customer, + cost_center="Main - TCP1", + expense_account="Cost of Goods Sold - TCP1", + qty=10, + rate=100, + warehouse=from_warehouse, + target_warehouse=target_warehouse, + ) + + # Step 4: Create Internal Purchase Receipt + from erpnext.controllers.status_updater import OverAllowanceError + from erpnext.stock.doctype.delivery_note.delivery_note import make_inter_company_purchase_receipt + + pr = make_inter_company_purchase_receipt(dn.name) + pr.inter_company_reference = "" + self.assertRaises(frappe.ValidationError, pr.save) + + pr.inter_company_reference = dn.name + pr.items[0].qty = 10 + pr.items[0].from_warehouse = target_warehouse + pr.items[0].warehouse = to_warehouse + pr.items[0].rejected_warehouse = from_warehouse + pr.save() + + delivery_note_item = pr.items[0].delivery_note_item + pr.items[0].delivery_note_item = "" + + self.assertRaises(frappe.ValidationError, pr.save) + + pr.load_from_db() + pr.items[0].delivery_note_item = delivery_note_item + pr.save() + def prepare_data_for_internal_transfer(): from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier From 6a0b7c9e8cc84ba0196440a5a0486323c54c720d Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Fri, 21 Apr 2023 17:34:22 +0530 Subject: [PATCH 45/46] fix: added validation for extra job card --- .../doctype/job_card/job_card.py | 31 +++++++++++ .../doctype/work_order/test_work_order.py | 51 +++++++++++++++++++ 2 files changed, 82 insertions(+) diff --git a/erpnext/manufacturing/doctype/job_card/job_card.py b/erpnext/manufacturing/doctype/job_card/job_card.py index e82f37977c..f89951619e 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.py +++ b/erpnext/manufacturing/doctype/job_card/job_card.py @@ -74,6 +74,37 @@ class JobCard(Document): self.update_sub_operation_status() self.validate_work_order() + def on_update(self): + self.validate_job_card_qty() + + def validate_job_card_qty(self): + if not (self.operation_id and self.work_order): + return + + wo_qty = flt(frappe.get_cached_value("Work Order", self.work_order, "qty")) + + completed_qty = flt( + frappe.db.get_value("Work Order Operation", self.operation_id, "completed_qty") + ) + + job_card_qty = frappe.get_all( + "Job Card", + fields=["sum(for_quantity)"], + filters={ + "work_order": self.work_order, + "operation_id": self.operation_id, + "docstatus": ["!=", 2], + }, + as_list=1, + ) + + job_card_qty = flt(job_card_qty[0][0]) if job_card_qty else 0 + + if job_card_qty and ((job_card_qty - completed_qty) > wo_qty): + msg = f"""Job Card quantity cannot be greater than + Work Order quantity for the operation {self.operation}""" + frappe.throw(_(msg), title=_("Extra Job Card Quantity")) + def set_sub_operations(self): if not self.sub_operations and self.operation: self.sub_operations = [] diff --git a/erpnext/manufacturing/doctype/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py index 729ed42f51..540b7dc9ea 100644 --- a/erpnext/manufacturing/doctype/work_order/test_work_order.py +++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py @@ -1598,6 +1598,57 @@ class TestWorkOrder(FrappeTestCase): self.assertEqual(row.to_time, add_to_date(planned_start_date, minutes=30)) self.assertEqual(row.workstation, workstations_to_check[index]) + def test_job_card_extra_qty(self): + items = [ + "Test FG Item for Scrap Item Test 1", + "Test RM Item 1 for Scrap Item Test 1", + "Test RM Item 2 for Scrap Item Test 1", + ] + + company = "_Test Company with perpetual inventory" + for item_code in items: + create_item( + item_code=item_code, + is_stock_item=1, + is_purchase_item=1, + opening_stock=100, + valuation_rate=10, + company=company, + warehouse="Stores - TCP1", + ) + + item = "Test FG Item for Scrap Item Test 1" + raw_materials = ["Test RM Item 1 for Scrap Item Test 1", "Test RM Item 2 for Scrap Item Test 1"] + if not frappe.db.get_value("BOM", {"item": item}): + bom = make_bom( + item=item, source_warehouse="Stores - TCP1", raw_materials=raw_materials, do_not_save=True + ) + bom.with_operations = 1 + bom.append( + "operations", + { + "operation": "_Test Operation 1", + "workstation": "_Test Workstation 1", + "hour_rate": 20, + "time_in_mins": 60, + }, + ) + + bom.submit() + + wo_order = make_wo_order_test_record( + item=item, + company=company, + planned_start_date=now(), + qty=20, + ) + job_card = frappe.db.get_value("Job Card", {"work_order": wo_order.name}, "name") + job_card_doc = frappe.get_doc("Job Card", job_card) + + # Make another Job Card for the same Work Order + job_card2 = frappe.copy_doc(job_card_doc) + self.assertRaises(frappe.ValidationError, job_card2.save) + def prepare_data_for_workstation_type_check(): from erpnext.manufacturing.doctype.operation.test_operation import make_operation From ac871797b28d87a09c5ca26e618fdf01b087f9de Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Fri, 21 Apr 2023 18:05:29 +0530 Subject: [PATCH 46/46] fix: SLA permissions (#34981) --- .../service_level_agreement.json | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/erpnext/support/doctype/service_level_agreement/service_level_agreement.json b/erpnext/support/doctype/service_level_agreement/service_level_agreement.json index 1698e2380f..1c6f24b23c 100644 --- a/erpnext/support/doctype/service_level_agreement/service_level_agreement.json +++ b/erpnext/support/doctype/service_level_agreement/service_level_agreement.json @@ -192,7 +192,7 @@ } ], "links": [], - "modified": "2021-11-26 15:45:33.289911", + "modified": "2023-04-21 17:16:56.192560", "modified_by": "Administrator", "module": "Support", "name": "Service Level Agreement", @@ -212,19 +212,12 @@ "write": 1 }, { - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "print": 1, "read": 1, - "report": 1, - "role": "All", - "share": 1, - "write": 1 + "role": "All" } ], "sort_field": "modified", "sort_order": "DESC", + "states": [], "track_changes": 1 } \ No newline at end of file