From bf3d68e76dc0ef67f55192867a4f93afbf15273b Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Thu, 30 Jan 2025 15:53:40 +0530 Subject: [PATCH 01/28] fix: posting_date to posting_datetime in stock related queries (cherry picked from commit e61ab481451f5bf6753462d976fb1312685eae4a) # Conflicts: # erpnext/stock/report/batch_wise_balance_history/batch_wise_balance_history.py # erpnext/stock/report/stock_ledger/stock_ledger.py --- .../batch_wise_balance_history.py | 58 +++++++++++++++++++ .../stock/report/stock_ageing/stock_ageing.py | 7 ++- .../stock/report/stock_ledger/stock_ledger.py | 16 +++-- 3 files changed, 72 insertions(+), 9 deletions(-) diff --git a/erpnext/stock/report/batch_wise_balance_history/batch_wise_balance_history.py b/erpnext/stock/report/batch_wise_balance_history/batch_wise_balance_history.py index 7c23e4c8f1e..3ebea0f72af 100644 --- a/erpnext/stock/report/batch_wise_balance_history/batch_wise_balance_history.py +++ b/erpnext/stock/report/batch_wise_balance_history/batch_wise_balance_history.py @@ -134,7 +134,65 @@ def get_stock_ledger_entries(filters): if filters.get(field): query = query.where(sle[field] == filters.get(field)) +<<<<<<< HEAD return query +======= + if filters.start_from: + query = query.where(sle.posting_datetime > get_datetime(filters.start_from)) + + return query.run(as_dict=True) or [] + + +def get_stock_ledger_entries_for_batch_bundle(filters): + sle = frappe.qb.DocType("Stock Ledger Entry") + batch_package = frappe.qb.DocType("Serial and Batch Entry") + + to_date = get_datetime(filters.to_date + " 23:59:59") + + query = ( + frappe.qb.from_(sle) + .inner_join(batch_package) + .on(batch_package.parent == sle.serial_and_batch_bundle) + .select( + sle.item_code, + sle.warehouse, + batch_package.batch_no, + sle.posting_date, + fn.Sum(batch_package.qty).as_("actual_qty"), + ) + .where( + (sle.docstatus < 2) + & (sle.is_cancelled == 0) + & (sle.has_batch_no == 1) + & (sle.posting_datetime <= to_date) + ) + .groupby(sle.voucher_no, batch_package.batch_no, batch_package.warehouse) + .orderby(sle.item_code, sle.warehouse) + ) + + query = apply_warehouse_filter(query, sle, filters) + if filters.warehouse_type and not filters.warehouse: + warehouses = frappe.get_all( + "Warehouse", + filters={"warehouse_type": filters.warehouse_type, "is_group": 0}, + pluck="name", + ) + + if warehouses: + query = query.where(sle.warehouse.isin(warehouses)) + + for field in ["item_code", "batch_no", "company"]: + if filters.get(field): + if field == "batch_no": + query = query.where(batch_package[field] == filters.get(field)) + else: + query = query.where(sle[field] == filters.get(field)) + + if filters.start_from: + query = query.where(sle.posting_date > getdate(filters.start_from)) + + return query.run(as_dict=True) or [] +>>>>>>> e61ab48145 (fix: posting_date to posting_datetime in stock related queries) def get_item_warehouse_batch_map(filters, float_precision): diff --git a/erpnext/stock/report/stock_ageing/stock_ageing.py b/erpnext/stock/report/stock_ageing/stock_ageing.py index c09137e645b..ea94c15c0e8 100644 --- a/erpnext/stock/report/stock_ageing/stock_ageing.py +++ b/erpnext/stock/report/stock_ageing/stock_ageing.py @@ -6,7 +6,7 @@ from operator import itemgetter import frappe from frappe import _ -from frappe.utils import cint, date_diff, flt +from frappe.utils import cint, date_diff, flt, get_datetime from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos @@ -387,6 +387,7 @@ class FIFOSlots: def __get_stock_ledger_entries(self) -> list[dict]: sle = frappe.qb.DocType("Stock Ledger Entry") item = self.__get_item_query() # used as derived table in sle query + to_date = get_datetime(self.filters.get("to_date") + " 23:59:59") sle_query = ( frappe.qb.from_(sle) @@ -411,7 +412,7 @@ class FIFOSlots: .where( (sle.item_code == item.name) & (sle.company == self.filters.get("company")) - & (sle.posting_date <= self.filters.get("to_date")) + & (sle.posting_datetime <= to_date) & (sle.is_cancelled != 1) ) ) @@ -428,7 +429,7 @@ class FIFOSlots: if warehouses: sle_query = sle_query.where(sle.warehouse.isin(warehouses)) - sle_query = sle_query.orderby(sle.posting_date, sle.posting_time, sle.creation, sle.actual_qty) + sle_query = sle_query.orderby(sle.posting_datetime, sle.creation) return sle_query.run(as_dict=True, as_iterator=True) diff --git a/erpnext/stock/report/stock_ledger/stock_ledger.py b/erpnext/stock/report/stock_ledger/stock_ledger.py index bde1434e600..13f546d950b 100644 --- a/erpnext/stock/report/stock_ledger/stock_ledger.py +++ b/erpnext/stock/report/stock_ledger/stock_ledger.py @@ -4,8 +4,13 @@ import frappe from frappe import _ +<<<<<<< HEAD from frappe.query_builder.functions import CombineDatetime from frappe.utils import cint, flt +======= +from frappe.query_builder.functions import CombineDatetime, Sum +from frappe.utils import cint, flt, get_datetime +>>>>>>> e61ab48145 (fix: posting_date to posting_datetime in stock related queries) from erpnext.stock.doctype.inventory_dimension.inventory_dimension import get_inventory_dimensions from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos @@ -264,6 +269,9 @@ def get_columns(filters): def get_stock_ledger_entries(filters, items): + from_date = get_datetime(filters.from_date + " 00:00:00") + to_date = get_datetime(filters.to_date + " 23:59:59") + sle = frappe.qb.DocType("Stock Ledger Entry") query = ( frappe.qb.from_(sle) @@ -286,12 +294,8 @@ def get_stock_ledger_entries(filters, items): sle.serial_no, sle.project, ) - .where( - (sle.docstatus < 2) - & (sle.is_cancelled == 0) - & (sle.posting_date[filters.from_date : filters.to_date]) - ) - .orderby(CombineDatetime(sle.posting_date, sle.posting_time)) + .where((sle.docstatus < 2) & (sle.is_cancelled == 0) & (sle.posting_datetime[from_date:to_date])) + .orderby(sle.posting_datetime) .orderby(sle.creation) ) From 9217e919c33169cfc43ec280a53f594128e92a75 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Fri, 31 Jan 2025 10:36:25 +0530 Subject: [PATCH 02/28] chore: fix conflicts --- .../batch_wise_balance_history.py | 65 ++----------------- 1 file changed, 5 insertions(+), 60 deletions(-) diff --git a/erpnext/stock/report/batch_wise_balance_history/batch_wise_balance_history.py b/erpnext/stock/report/batch_wise_balance_history/batch_wise_balance_history.py index 3ebea0f72af..757059f7976 100644 --- a/erpnext/stock/report/batch_wise_balance_history/batch_wise_balance_history.py +++ b/erpnext/stock/report/batch_wise_balance_history/batch_wise_balance_history.py @@ -4,7 +4,7 @@ import frappe from frappe import _ -from frappe.utils import cint, flt, get_table_name, getdate +from frappe.utils import cint, flt, get_table_name, getdate, get_datetime from pypika import functions as fn from erpnext.stock.doctype.warehouse.warehouse import apply_warehouse_filter @@ -99,6 +99,8 @@ def get_stock_ledger_entries(filters): if not filters.get("to_date"): frappe.throw(_("'To Date' is required")) + to_date = get_datetime(filters.get("to_date") + " 23:59:59") + sle = frappe.qb.DocType("Stock Ledger Entry") query = ( frappe.qb.from_(sle) @@ -113,7 +115,7 @@ def get_stock_ledger_entries(filters): (sle.docstatus < 2) & (sle.is_cancelled == 0) & (fn.IfNull(sle.batch_no, "") != "") - & (sle.posting_date <= filters["to_date"]) + & (sle.posting_datetime <= to_date) ) .groupby(sle.voucher_no, sle.batch_no, sle.item_code, sle.warehouse) .orderby(sle.item_code, sle.warehouse) @@ -134,65 +136,8 @@ def get_stock_ledger_entries(filters): if filters.get(field): query = query.where(sle[field] == filters.get(field)) -<<<<<<< HEAD + return query -======= - if filters.start_from: - query = query.where(sle.posting_datetime > get_datetime(filters.start_from)) - - return query.run(as_dict=True) or [] - - -def get_stock_ledger_entries_for_batch_bundle(filters): - sle = frappe.qb.DocType("Stock Ledger Entry") - batch_package = frappe.qb.DocType("Serial and Batch Entry") - - to_date = get_datetime(filters.to_date + " 23:59:59") - - query = ( - frappe.qb.from_(sle) - .inner_join(batch_package) - .on(batch_package.parent == sle.serial_and_batch_bundle) - .select( - sle.item_code, - sle.warehouse, - batch_package.batch_no, - sle.posting_date, - fn.Sum(batch_package.qty).as_("actual_qty"), - ) - .where( - (sle.docstatus < 2) - & (sle.is_cancelled == 0) - & (sle.has_batch_no == 1) - & (sle.posting_datetime <= to_date) - ) - .groupby(sle.voucher_no, batch_package.batch_no, batch_package.warehouse) - .orderby(sle.item_code, sle.warehouse) - ) - - query = apply_warehouse_filter(query, sle, filters) - if filters.warehouse_type and not filters.warehouse: - warehouses = frappe.get_all( - "Warehouse", - filters={"warehouse_type": filters.warehouse_type, "is_group": 0}, - pluck="name", - ) - - if warehouses: - query = query.where(sle.warehouse.isin(warehouses)) - - for field in ["item_code", "batch_no", "company"]: - if filters.get(field): - if field == "batch_no": - query = query.where(batch_package[field] == filters.get(field)) - else: - query = query.where(sle[field] == filters.get(field)) - - if filters.start_from: - query = query.where(sle.posting_date > getdate(filters.start_from)) - - return query.run(as_dict=True) or [] ->>>>>>> e61ab48145 (fix: posting_date to posting_datetime in stock related queries) def get_item_warehouse_batch_map(filters, float_precision): From dee6e2b6977f8ba82fc34d0a453f99e5f58fc785 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Fri, 31 Jan 2025 10:38:08 +0530 Subject: [PATCH 03/28] chore: fix conflicts --- erpnext/stock/report/stock_ledger/stock_ledger.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/erpnext/stock/report/stock_ledger/stock_ledger.py b/erpnext/stock/report/stock_ledger/stock_ledger.py index 13f546d950b..4341feec985 100644 --- a/erpnext/stock/report/stock_ledger/stock_ledger.py +++ b/erpnext/stock/report/stock_ledger/stock_ledger.py @@ -4,13 +4,8 @@ import frappe from frappe import _ -<<<<<<< HEAD from frappe.query_builder.functions import CombineDatetime -from frappe.utils import cint, flt -======= -from frappe.query_builder.functions import CombineDatetime, Sum from frappe.utils import cint, flt, get_datetime ->>>>>>> e61ab48145 (fix: posting_date to posting_datetime in stock related queries) from erpnext.stock.doctype.inventory_dimension.inventory_dimension import get_inventory_dimensions from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos From 50aa4ed55a0b6e4a0b7a78822aa4d9e839a538f4 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Fri, 31 Jan 2025 12:01:45 +0530 Subject: [PATCH 04/28] chore: fix conflicts --- .../batch_wise_balance_history/batch_wise_balance_history.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/erpnext/stock/report/batch_wise_balance_history/batch_wise_balance_history.py b/erpnext/stock/report/batch_wise_balance_history/batch_wise_balance_history.py index 757059f7976..b56d2f99328 100644 --- a/erpnext/stock/report/batch_wise_balance_history/batch_wise_balance_history.py +++ b/erpnext/stock/report/batch_wise_balance_history/batch_wise_balance_history.py @@ -4,7 +4,7 @@ import frappe from frappe import _ -from frappe.utils import cint, flt, get_table_name, getdate, get_datetime +from frappe.utils import cint, flt, get_datetime, get_table_name, getdate from pypika import functions as fn from erpnext.stock.doctype.warehouse.warehouse import apply_warehouse_filter @@ -136,7 +136,6 @@ def get_stock_ledger_entries(filters): if filters.get(field): query = query.where(sle[field] == filters.get(field)) - return query From 9f5d7e41ec76a783b86dc1b343c530e8d5771624 Mon Sep 17 00:00:00 2001 From: ljain112 Date: Thu, 30 Jan 2025 17:27:44 +0530 Subject: [PATCH 05/28] fix: correct error message in payment entry (cherry picked from commit 592704cfd08890931301a8c5cf361ca45a59414d) --- erpnext/accounts/doctype/payment_entry/payment_entry.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 5224db65f03..c14473a7ecd 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -465,7 +465,7 @@ class PaymentEntry(AccountsController): if d.reference_doctype not in valid_reference_doctypes: frappe.throw( _("Reference Doctype must be one of {0}").format( - comma_or(_(d) for d in valid_reference_doctypes) + comma_or([_(d) for d in valid_reference_doctypes]) ) ) From 00866567484a475937fc352ba4c7ba1f2d909c27 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Fri, 31 Jan 2025 16:10:25 +0530 Subject: [PATCH 06/28] fix: Gross Profit Report with Correct Totals and Gross Margin (backport #45548) (#45597) * fix: Gross Profit Report with Correct Totals and Gross Margin (#45548) Co-authored-by: Sanket322 (cherry picked from commit aaf720ab61f8a8b3e3d03513dd4c650b6106413d) # Conflicts: # erpnext/accounts/report/gross_profit/test_gross_profit.py # erpnext/patches.txt * fix: conflicts --------- Co-authored-by: Sanket Shah <113279972+Sanket322@users.noreply.github.com> Co-authored-by: ljain112 --- .../report/gross_profit/gross_profit.json | 4 +-- .../report/gross_profit/gross_profit.py | 28 +++++++++++++++++ .../report/gross_profit/test_gross_profit.py | 30 +++++++++++++++++++ erpnext/patches.txt | 2 ++ .../v14_0/disable_add_row_in_gross_profit.py | 5 ++++ 5 files changed, 67 insertions(+), 2 deletions(-) create mode 100644 erpnext/patches/v14_0/disable_add_row_in_gross_profit.py diff --git a/erpnext/accounts/report/gross_profit/gross_profit.json b/erpnext/accounts/report/gross_profit/gross_profit.json index 0730ffd77e5..dfb7a991e3e 100644 --- a/erpnext/accounts/report/gross_profit/gross_profit.json +++ b/erpnext/accounts/report/gross_profit/gross_profit.json @@ -1,5 +1,5 @@ { - "add_total_row": 1, + "add_total_row": 0, "columns": [], "creation": "2013-02-25 17:03:34", "disable_prepared_report": 0, @@ -9,7 +9,7 @@ "filters": [], "idx": 3, "is_standard": "Yes", - "modified": "2022-02-11 10:18:36.956558", + "modified": "2025-01-27 18:40:24.493829", "modified_by": "Administrator", "module": "Accounts", "name": "Gross Profit", diff --git a/erpnext/accounts/report/gross_profit/gross_profit.py b/erpnext/accounts/report/gross_profit/gross_profit.py index 5539954231a..07d85983a84 100644 --- a/erpnext/accounts/report/gross_profit/gross_profit.py +++ b/erpnext/accounts/report/gross_profit/gross_profit.py @@ -166,7 +166,14 @@ def get_data_when_grouped_by_invoice(columns, gross_profit_data, filters, group_ # removing Item Code and Item Name columns del columns[4:6] + total_base_amount = 0 + total_buying_amount = 0 + for src in gross_profit_data.si_list: + if src.indent == 1: + total_base_amount += src.base_amount or 0.0 + total_buying_amount += src.buying_amount or 0.0 + row = frappe._dict() row.indent = src.indent row.parent_invoice = src.parent_invoice @@ -177,6 +184,27 @@ def get_data_when_grouped_by_invoice(columns, gross_profit_data, filters, group_ data.append(row) + total_gross_profit = total_base_amount - total_buying_amount + data.append( + frappe._dict( + { + "sales_invoice": "Total", + "qty": None, + "avg._selling_rate": None, + "valuation_rate": None, + "selling_amount": total_base_amount, + "buying_amount": total_buying_amount, + "gross_profit": total_gross_profit, + "gross_profit_%": flt( + (total_gross_profit / total_base_amount) * 100.0, + cint(frappe.db.get_default("currency_precision")) or 3, + ) + if total_base_amount + else 0, + } + ) + ) + def get_data_when_not_grouped_by_invoice(gross_profit_data, filters, group_wise_columns, data): for src in gross_profit_data.grouped_data: diff --git a/erpnext/accounts/report/gross_profit/test_gross_profit.py b/erpnext/accounts/report/gross_profit/test_gross_profit.py index 8b1621ae782..94e44b76fcd 100644 --- a/erpnext/accounts/report/gross_profit/test_gross_profit.py +++ b/erpnext/accounts/report/gross_profit/test_gross_profit.py @@ -558,3 +558,33 @@ class TestGrossProfit(FrappeTestCase): } gp_entry = [x for x in data if x.parent_invoice == sinv.name] self.assertDictContainsSubset(expected_entry, gp_entry[0]) + + def test_gross_profit_groupby_invoices(self): + create_sales_invoice( + qty=1, + rate=100, + company=self.company, + customer=self.customer, + item_code=self.item, + item_name=self.item, + cost_center=self.cost_center, + warehouse=self.warehouse, + debit_to=self.debit_to, + parent_cost_center=self.cost_center, + update_stock=0, + currency="INR", + income_account=self.income_account, + expense_account=self.expense_account, + ) + + filters = frappe._dict( + company=self.company, from_date=nowdate(), to_date=nowdate(), group_by="Invoice" + ) + + _, data = execute(filters=filters) + total = data[-1] + + self.assertEqual(total.selling_amount, 100.0) + self.assertEqual(total.buying_amount, 0.0) + self.assertEqual(total.gross_profit, 100.0) + self.assertEqual(total.get("gross_profit_%"), 100.0) diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 8cc7ada03c2..46fccd1bb6f 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -368,3 +368,5 @@ erpnext.patches.v14_0.remove_cancelled_asset_capitalization_from_asset erpnext.patches.v14_0.enable_set_priority_for_pricing_rules #1 erpnext.patches.v14_0.update_currency_exchange_settings_for_frankfurter erpnext.patches.v14_0.update_stock_uom_in_work_order_item +erpnext.patches.v14_0.disable_add_row_in_gross_profit + diff --git a/erpnext/patches/v14_0/disable_add_row_in_gross_profit.py b/erpnext/patches/v14_0/disable_add_row_in_gross_profit.py new file mode 100644 index 00000000000..d95503bef0a --- /dev/null +++ b/erpnext/patches/v14_0/disable_add_row_in_gross_profit.py @@ -0,0 +1,5 @@ +import frappe + + +def execute(): + frappe.db.set_value("Report", "Gross Profit", "add_total_row", 0) From 83bce785ff84f58ec60a34830ccd734effb62f3e Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Thu, 30 Jan 2025 17:29:31 +0530 Subject: [PATCH 07/28] fix: bind this to function (cherry picked from commit 41649cf52de0fb50b6370d915ea41b7ad8fc9971) # Conflicts: # erpnext/buying/doctype/purchase_order/purchase_order.js --- erpnext/buying/doctype/purchase_order/purchase_order.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.js b/erpnext/buying/doctype/purchase_order/purchase_order.js index d6fd53dc778..2de1546a1ba 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.js +++ b/erpnext/buying/doctype/purchase_order/purchase_order.js @@ -205,9 +205,18 @@ erpnext.buying.PurchaseOrderController = class PurchaseOrderController extends e } } +<<<<<<< HEAD if(is_drop_ship && doc.status!="Delivered") { this.frm.add_custom_button(__('Delivered'), this.delivered_by_supplier, __("Status")); +======= + if (is_drop_ship && doc.status != "Delivered") { + this.frm.add_custom_button( + __("Delivered"), + this.delivered_by_supplier.bind(this), + __("Status") + ); +>>>>>>> 41649cf52d (fix: bind this to function) this.frm.page.set_inner_btn_group_as_primary(__("Status")); } From 7b3c35c167ad1012830e9f185372a4ebe6b162ee Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Mon, 3 Feb 2025 11:58:10 +0530 Subject: [PATCH 08/28] chore: fix conflicts --- erpnext/buying/doctype/purchase_order/purchase_order.js | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.js b/erpnext/buying/doctype/purchase_order/purchase_order.js index 2de1546a1ba..20dec47faa9 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.js +++ b/erpnext/buying/doctype/purchase_order/purchase_order.js @@ -205,18 +205,12 @@ erpnext.buying.PurchaseOrderController = class PurchaseOrderController extends e } } -<<<<<<< HEAD - if(is_drop_ship && doc.status!="Delivered") { - this.frm.add_custom_button(__('Delivered'), - this.delivered_by_supplier, __("Status")); -======= if (is_drop_ship && doc.status != "Delivered") { this.frm.add_custom_button( __("Delivered"), this.delivered_by_supplier.bind(this), __("Status") ); ->>>>>>> 41649cf52d (fix: bind this to function) this.frm.page.set_inner_btn_group_as_primary(__("Status")); } @@ -591,4 +585,4 @@ frappe.ui.form.on("Purchase Order", "is_subcontracted", function(frm) { if (frm.doc.is_old_subcontracting_flow) { erpnext.buying.get_default_bom(frm); } -}); \ No newline at end of file +}); From dac53074f27bab4dadc23a051025297e920ba9b9 Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Thu, 30 Jan 2025 12:50:01 +0530 Subject: [PATCH 09/28] feat: set bank account of company to default company bank account from masters (cherry picked from commit ce7702cc199e4bef4d1c46a0450cb613392cfcba) --- erpnext/accounts/doctype/payment_entry/payment_entry.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index c14473a7ecd..25b67e2db31 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -2481,6 +2481,7 @@ def get_payment_entry( pe.paid_amount = paid_amount pe.received_amount = received_amount pe.letter_head = doc.get("letter_head") + pe.bank_account = frappe.db.get_value("Bank Account", {"is_company_account": 1, "is_default": 1}, "name") if dt in ["Purchase Order", "Sales Order", "Sales Invoice", "Purchase Invoice"]: pe.project = doc.get("project") or reduce( From b5637c43faa3df575a051d191910031e6919e05b Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 19 Dec 2024 13:40:03 +0530 Subject: [PATCH 10/28] refactor: configurable posting date for Exc Gain / Loss journal (cherry picked from commit 3fbd2ca0d9d9cef759669050964e2faa63af2429) # Conflicts: # erpnext/accounts/doctype/accounts_settings/accounts_settings.json # erpnext/accounts/doctype/accounts_settings/accounts_settings.py --- .../accounts_settings/accounts_settings.json | 81 +++++++++++++++++++ .../accounts_settings/accounts_settings.py | 56 +++++++++++++ .../payment_reconciliation.py | 5 ++ 3 files changed, 142 insertions(+) diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json index 5581ab038c5..cb93720589d 100644 --- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json +++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json @@ -43,7 +43,14 @@ "allow_stale", "section_break_jpd0", "auto_reconcile_payments", +<<<<<<< HEAD "stale_days", +======= + "auto_reconciliation_job_trigger", + "reconciliation_queue_size", + "column_break_resa", + "exchange_gain_loss_posting_date", +>>>>>>> 3fbd2ca0d9 (refactor: configurable posting date for Exc Gain / Loss journal) "invoicing_settings_tab", "accounts_transactions_settings_section", "over_billing_allowance", @@ -462,6 +469,76 @@ "fieldname": "remarks_section", "fieldtype": "Section Break", "label": "Remarks Column Length" +<<<<<<< HEAD +======= + }, + { + "default": "0", + "description": "On enabling this cancellation entries will be posted on the actual cancellation date and reports will consider cancelled entries as well", + "fieldname": "enable_immutable_ledger", + "fieldtype": "Check", + "label": "Enable Immutable Ledger" + }, + { + "fieldname": "column_break_gjcc", + "fieldtype": "Column Break" + }, + { + "default": "0", + "description": "Enable this option to calculate daily depreciation by considering the total number of days in the entire depreciation period, (including leap years) while using daily pro-rata based depreciation", + "fieldname": "calculate_depr_using_total_days", + "fieldtype": "Check", + "label": "Calculate daily depreciation using total days in depreciation period" + }, + { + "description": "Payment Request created from Sales Order or Purchase Order will be in Draft status. When disabled document will be in unsaved state.", + "fieldname": "payment_request_settings", + "fieldtype": "Tab Break", + "label": "Payment Request" + }, + { + "default": "1", + "fieldname": "create_pr_in_draft_status", + "fieldtype": "Check", + "label": "Create in Draft Status" + }, + { + "fieldname": "column_break_yuug", + "fieldtype": "Column Break" + }, + { + "fieldname": "column_break_resa", + "fieldtype": "Column Break" + }, + { + "default": "15", + "description": "Interval should be between 1 to 59 MInutes", + "fieldname": "auto_reconciliation_job_trigger", + "fieldtype": "Int", + "label": "Auto Reconciliation Job Trigger" + }, + { + "default": "5", + "description": "Documents Processed on each trigger. Queue Size should be between 5 and 100", + "fieldname": "reconciliation_queue_size", + "fieldtype": "Int", + "label": "Reconciliation Queue Size" + }, + { + "default": "0", + "description": "Ignores legacy Is Opening field in GL Entry that allows adding opening balance post the system is in use while generating reports", + "fieldname": "ignore_is_opening_check_for_reporting", + "fieldtype": "Check", + "label": "Ignore Is Opening check for reporting" + }, + { + "default": "Payment", + "description": "Only applies for Normal Payments", + "fieldname": "exchange_gain_loss_posting_date", + "fieldtype": "Select", + "label": "Posting Date Inheritance for Exchange Gain / Loss", + "options": "Invoice\nPayment" +>>>>>>> 3fbd2ca0d9 (refactor: configurable posting date for Exc Gain / Loss journal) } ], "icon": "icon-cog", @@ -469,7 +546,11 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], +<<<<<<< HEAD "modified": "2024-01-22 12:10:10.151819", +======= + "modified": "2025-01-22 17:53:47.968079", +>>>>>>> 3fbd2ca0d9 (refactor: configurable posting date for Exc Gain / Loss journal) "modified_by": "Administrator", "module": "Accounts", "name": "Accounts Settings", diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.py b/erpnext/accounts/doctype/accounts_settings/accounts_settings.py index ac3d44bb5e7..94361f23b8c 100644 --- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.py +++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.py @@ -14,6 +14,62 @@ from erpnext.stock.utils import check_pending_reposting class AccountsSettings(Document): +<<<<<<< HEAD +======= + # begin: auto-generated types + # This code is auto-generated. Do not modify anything in this block. + + from typing import TYPE_CHECKING + + if TYPE_CHECKING: + from frappe.types import DF + + acc_frozen_upto: DF.Date | None + add_taxes_from_item_tax_template: DF.Check + allow_multi_currency_invoices_against_single_party_account: DF.Check + allow_stale: DF.Check + auto_reconcile_payments: DF.Check + auto_reconciliation_job_trigger: DF.Int + automatically_fetch_payment_terms: DF.Check + automatically_process_deferred_accounting_entry: DF.Check + book_asset_depreciation_entry_automatically: DF.Check + book_deferred_entries_based_on: DF.Literal["Days", "Months"] + book_deferred_entries_via_journal_entry: DF.Check + book_tax_discount_loss: DF.Check + calculate_depr_using_total_days: DF.Check + check_supplier_invoice_uniqueness: DF.Check + create_pr_in_draft_status: DF.Check + credit_controller: DF.Link | None + delete_linked_ledger_entries: DF.Check + determine_address_tax_category_from: DF.Literal["Billing Address", "Shipping Address"] + enable_common_party_accounting: DF.Check + enable_fuzzy_matching: DF.Check + enable_immutable_ledger: DF.Check + enable_party_matching: DF.Check + exchange_gain_loss_posting_date: DF.Literal["Invoice", "Payment"] + frozen_accounts_modifier: DF.Link | None + general_ledger_remarks_length: DF.Int + ignore_account_closing_balance: DF.Check + ignore_is_opening_check_for_reporting: DF.Check + make_payment_via_journal_entry: DF.Check + merge_similar_account_heads: DF.Check + over_billing_allowance: DF.Currency + post_change_gl_entries: DF.Check + receivable_payable_remarks_length: DF.Int + reconciliation_queue_size: DF.Int + role_allowed_to_over_bill: DF.Link | None + round_row_wise_tax: DF.Check + show_balance_in_coa: DF.Check + show_inclusive_tax_in_print: DF.Check + show_payment_schedule_in_print: DF.Check + show_taxes_as_table_in_print: DF.Check + stale_days: DF.Int + submit_journal_entries: DF.Check + unlink_advance_payment_on_cancelation_of_order: DF.Check + unlink_payment_on_cancellation_of_invoice: DF.Check + # end: auto-generated types + +>>>>>>> 3fbd2ca0d9 (refactor: configurable posting date for Exc Gain / Loss journal) def validate(self): old_doc = self.get_doc_before_save() clear_cache = False diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py index e452d729ccf..aa629ac7dcf 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py +++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py @@ -354,6 +354,9 @@ class PaymentReconciliation(Document): def allocate_entries(self, args): self.validate_entries() + exc_gain_loss_posting_date = frappe.db.get_single_value( + "Accounts Settings", "exchange_gain_loss_posting_date", cache=True + ) invoice_exchange_map = self.get_invoice_exchange_map(args.get("invoices"), args.get("payments")) default_exchange_gain_loss_account = frappe.get_cached_value( "Company", self.company, "exchange_gain_loss_account" @@ -380,6 +383,8 @@ class PaymentReconciliation(Document): res.difference_account = default_exchange_gain_loss_account res.exchange_rate = inv.get("exchange_rate") res.update({"gain_loss_posting_date": pay.get("posting_date")}) + if exc_gain_loss_posting_date == "Invoice": + res.update({"gain_loss_posting_date": inv.get("invoice_date")}) if pay.get("amount") == 0: entries.append(res) From 035139d4c79963468bc09985ed2c6990046527f3 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 19 Dec 2024 13:40:03 +0530 Subject: [PATCH 11/28] refactor: configurable posting date for Exc Gain / Loss journal (cherry picked from commit 5257413a932f4a9eb1331d5fca6110390531fadf) --- .../accounts/doctype/accounts_settings/accounts_settings.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json index cb93720589d..ddee7670ea1 100644 --- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json +++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json @@ -390,7 +390,7 @@ { "fieldname": "section_break_jpd0", "fieldtype": "Section Break", - "label": "Payment Reconciliations" + "label": "Payment Reconciliation Settings" }, { "default": "0", From 5a62bd6e85273ad9dad9175a77b1d4c57166ef2e Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 23 Jan 2025 13:48:09 +0530 Subject: [PATCH 12/28] refactor: allow reconciliation date for exchange gain / loss (cherry picked from commit 95af63e305c51a19bfea43c7f9580c68fb93433e) # Conflicts: # erpnext/accounts/doctype/accounts_settings/accounts_settings.json --- .../doctype/accounts_settings/accounts_settings.json | 10 +++++++++- .../doctype/accounts_settings/accounts_settings.py | 2 +- .../payment_reconciliation/payment_reconciliation.py | 2 ++ 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json index ddee7670ea1..b1393d5de9f 100644 --- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json +++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json @@ -537,8 +537,12 @@ "fieldname": "exchange_gain_loss_posting_date", "fieldtype": "Select", "label": "Posting Date Inheritance for Exchange Gain / Loss", +<<<<<<< HEAD "options": "Invoice\nPayment" >>>>>>> 3fbd2ca0d9 (refactor: configurable posting date for Exc Gain / Loss journal) +======= + "options": "Invoice\nPayment\nReconciliation Date" +>>>>>>> 95af63e305 (refactor: allow reconciliation date for exchange gain / loss) } ], "icon": "icon-cog", @@ -546,11 +550,15 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], +<<<<<<< HEAD <<<<<<< HEAD "modified": "2024-01-22 12:10:10.151819", ======= "modified": "2025-01-22 17:53:47.968079", >>>>>>> 3fbd2ca0d9 (refactor: configurable posting date for Exc Gain / Loss journal) +======= + "modified": "2025-01-23 13:15:44.077853", +>>>>>>> 95af63e305 (refactor: allow reconciliation date for exchange gain / loss) "modified_by": "Administrator", "module": "Accounts", "name": "Accounts Settings", @@ -579,4 +587,4 @@ "sort_order": "ASC", "states": [], "track_changes": 1 -} +} \ No newline at end of file diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.py b/erpnext/accounts/doctype/accounts_settings/accounts_settings.py index 94361f23b8c..ca3b18671a6 100644 --- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.py +++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.py @@ -46,7 +46,7 @@ class AccountsSettings(Document): enable_fuzzy_matching: DF.Check enable_immutable_ledger: DF.Check enable_party_matching: DF.Check - exchange_gain_loss_posting_date: DF.Literal["Invoice", "Payment"] + exchange_gain_loss_posting_date: DF.Literal["Invoice", "Payment", "Reconciliation Date"] frozen_accounts_modifier: DF.Link | None general_ledger_remarks_length: DF.Int ignore_account_closing_balance: DF.Check diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py index aa629ac7dcf..e9ed8c4a34b 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py +++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py @@ -385,6 +385,8 @@ class PaymentReconciliation(Document): res.update({"gain_loss_posting_date": pay.get("posting_date")}) if exc_gain_loss_posting_date == "Invoice": res.update({"gain_loss_posting_date": inv.get("invoice_date")}) + elif exc_gain_loss_posting_date == "Reconciliation Date": + res.update({"gain_loss_posting_date": nowdate()}) if pay.get("amount") == 0: entries.append(res) From ef6e26488732663c13bfd4b590c2d0bef6ad474d Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 23 Jan 2025 14:10:15 +0530 Subject: [PATCH 13/28] refactor: only apply configuration on normal payments patch to update default value (cherry picked from commit b2c3da135ea85fe245ec9c6066a8e68b42b64f7f) # Conflicts: # erpnext/controllers/accounts_controller.py # erpnext/patches.txt --- .../payment_reconciliation.py | 10 ++- erpnext/controllers/accounts_controller.py | 82 +++++++++++++++++++ erpnext/patches.txt | 11 +++ 3 files changed, 99 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py index e9ed8c4a34b..3aee5c89c9d 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py +++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py @@ -270,6 +270,7 @@ class PaymentReconciliation(Document): for payment in non_reconciled_payments: row = self.append("payments", {}) row.update(payment) + row.is_advance = payment.book_advance_payments_in_separate_party_account def get_invoice_entries(self): # Fetch JVs, Sales and Purchase Invoices for 'invoices' to reconcile against @@ -383,10 +384,11 @@ class PaymentReconciliation(Document): res.difference_account = default_exchange_gain_loss_account res.exchange_rate = inv.get("exchange_rate") res.update({"gain_loss_posting_date": pay.get("posting_date")}) - if exc_gain_loss_posting_date == "Invoice": - res.update({"gain_loss_posting_date": inv.get("invoice_date")}) - elif exc_gain_loss_posting_date == "Reconciliation Date": - res.update({"gain_loss_posting_date": nowdate()}) + if not pay.get("is_advance"): + if exc_gain_loss_posting_date == "Invoice": + res.update({"gain_loss_posting_date": inv.get("invoice_date")}) + elif exc_gain_loss_posting_date == "Reconciliation Date": + res.update({"gain_loss_posting_date": nowdate()}) if pay.get("amount") == 0: entries.append(res) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 160c478f299..07f6ae62746 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -2767,6 +2767,7 @@ def get_advance_payment_entries( condition.append(pe.name.like(f"%%{payment_name}%%")) if order_list or against_all_orders: +<<<<<<< HEAD orders_condition = [] if order_list: orders_condition.append(per.reference_name.isin(order_list)) @@ -2784,6 +2785,87 @@ def get_advance_payment_entries( pe.posting_date, pe[currency_field].as_("currency"), pe[exchange_rate_field].as_("exchange_rate"), +======= + q = get_common_query( + party_type, + party, + party_account, + default_advance_account, + limit, + condition, + ) + payment_ref = frappe.qb.DocType("Payment Entry Reference") + + q = q.inner_join(payment_ref).on(payment_entry.name == payment_ref.parent) + q = q.select( + (payment_ref.allocated_amount).as_("amount"), + (payment_ref.name).as_("reference_row"), + (payment_ref.reference_name).as_("against_order"), + (payment_entry.book_advance_payments_in_separate_party_account), + ) + + q = q.where(payment_ref.reference_doctype == order_doctype) + if order_list: + q = q.where(payment_ref.reference_name.isin(order_list)) + + allocated = list(q.run(as_dict=True)) + payment_entries += allocated + if include_unallocated: + q = get_common_query( + party_type, + party, + party_account, + default_advance_account, + limit, + condition, + ) + q = q.select((payment_entry.unallocated_amount).as_("amount")) + q = q.where(payment_entry.unallocated_amount > 0) + + unallocated = list(q.run(as_dict=True)) + payment_entries += unallocated + return payment_entries + + +def get_common_query( + party_type, + party, + party_account, + default_advance_account, + limit, + condition, +): + account_type = frappe.db.get_value("Party Type", party_type, "account_type") + payment_type = "Receive" if account_type == "Receivable" else "Pay" + payment_entry = frappe.qb.DocType("Payment Entry") + + q = ( + frappe.qb.from_(payment_entry) + .select( + ConstantColumn("Payment Entry").as_("reference_type"), + (payment_entry.name).as_("reference_name"), + payment_entry.posting_date, + (payment_entry.remarks).as_("remarks"), + (payment_entry.book_advance_payments_in_separate_party_account), + ) + .where(payment_entry.payment_type == payment_type) + .where(payment_entry.party_type == party_type) + .where(payment_entry.party == party) + .where(payment_entry.docstatus == 1) + ) + + field = "paid_from" if payment_type == "Receive" else "paid_to" + + q = q.select((payment_entry[f"{field}_account_currency"]).as_("currency")) + q = q.select(payment_entry[field]) + account_condition = payment_entry[field].isin(party_account) + if default_advance_account: + q = q.where( + account_condition + | ( + (payment_entry[field] == default_advance_account) + & (payment_entry.book_advance_payments_in_separate_party_account == 1) +>>>>>>> b2c3da135e (refactor: only apply configuration on normal payments) ) .where( (pe[party_account_field] == party_account) diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 46fccd1bb6f..93e49b023b1 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -368,5 +368,16 @@ erpnext.patches.v14_0.remove_cancelled_asset_capitalization_from_asset erpnext.patches.v14_0.enable_set_priority_for_pricing_rules #1 erpnext.patches.v14_0.update_currency_exchange_settings_for_frankfurter erpnext.patches.v14_0.update_stock_uom_in_work_order_item +<<<<<<< HEAD erpnext.patches.v14_0.disable_add_row_in_gross_profit +======= +erpnext.patches.v15_0.enable_allow_existing_serial_no +erpnext.patches.v15_0.update_cc_in_process_statement_of_accounts +erpnext.patches.v15_0.refactor_closing_stock_balance #5 +erpnext.patches.v15_0.update_asset_status_to_work_in_progress +erpnext.patches.v15_0.rename_manufacturing_settings_field +erpnext.patches.v15_0.migrate_checkbox_to_select_for_reconciliation_effect +erpnext.patches.v15_0.sync_auto_reconcile_config +execute:frappe.db.set_single_value("Accounts Settings", "exchange_gain_loss_posting_date", "Payment") +>>>>>>> b2c3da135e (refactor: only apply configuration on normal payments) From 620cdc24891932b1e4d2fd7da3031c51c46e66a6 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 23 Jan 2025 14:44:40 +0530 Subject: [PATCH 14/28] test: exc gain/loss posting date based on configuration (cherry picked from commit 2f3281579a5b4393ff336e7e2ff274d0b60c5d66) # Conflicts: # erpnext/controllers/tests/test_accounts_controller.py --- .../tests/test_accounts_controller.py | 130 ++++++++++++++++++ 1 file changed, 130 insertions(+) diff --git a/erpnext/controllers/tests/test_accounts_controller.py b/erpnext/controllers/tests/test_accounts_controller.py index 1e0f4cce27b..bd32cbafccf 100644 --- a/erpnext/controllers/tests/test_accounts_controller.py +++ b/erpnext/controllers/tests/test_accounts_controller.py @@ -9,6 +9,7 @@ from frappe import qb from frappe.query_builder.functions import Sum from frappe.tests.utils import FrappeTestCase from frappe.utils import add_days, getdate, nowdate +from frappe.utils.data import getdate as convert_to_date from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry from erpnext.accounts.doctype.payment_entry.test_payment_entry import create_payment_entry @@ -707,6 +708,135 @@ class TestAccountsController(FrappeTestCase): self.assertEqual(exc_je_for_si, []) self.assertEqual(exc_je_for_pe, []) +<<<<<<< HEAD +======= + @IntegrationTestCase.change_settings( + "Stock Settings", {"allow_internal_transfer_at_arms_length_price": 1} + ) + def test_16_internal_transfer_at_arms_length_price(self): + from erpnext.accounts.doctype.sales_invoice.sales_invoice import make_inter_company_purchase_invoice + from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse + + prepare_data_for_internal_transfer() + company = "_Test Company with perpetual inventory" + target_warehouse = create_warehouse("_Test Internal Warehouse New 1", company=company) + warehouse = create_warehouse("_Test Internal Warehouse New 2", company=company) + arms_length_price = 40 + + si = create_sales_invoice( + company=company, + customer="_Test Internal Customer 2", + debit_to="Debtors - TCP1", + target_warehouse=target_warehouse, + warehouse=warehouse, + income_account="Sales - TCP1", + expense_account="Cost of Goods Sold - TCP1", + cost_center="Main - TCP1", + update_stock=True, + do_not_save=True, + do_not_submit=True, + ) + + si.items[0].rate = arms_length_price + si.save() + # rate should not reset to incoming rate + self.assertEqual(si.items[0].rate, arms_length_price) + + frappe.db.set_single_value("Stock Settings", "allow_internal_transfer_at_arms_length_price", 0) + si.items[0].rate = arms_length_price + si.save() + # rate should reset to incoming rate + self.assertEqual(si.items[0].rate, 100) + + si.update_stock = 0 + si.save() + si.submit() + + pi = make_inter_company_purchase_invoice(si.name) + pi.update_stock = 1 + pi.items[0].rate = arms_length_price + pi.items[0].warehouse = target_warehouse + pi.items[0].from_warehouse = warehouse + pi.save() + + self.assertEqual(pi.items[0].rate, 100) + self.assertEqual(pi.items[0].valuation_rate, 100) + + frappe.db.set_single_value("Stock Settings", "allow_internal_transfer_at_arms_length_price", 1) + pi = make_inter_company_purchase_invoice(si.name) + pi.update_stock = 1 + pi.items[0].rate = arms_length_price + pi.items[0].warehouse = target_warehouse + pi.items[0].from_warehouse = warehouse + pi.save() + + self.assertEqual(pi.items[0].rate, arms_length_price) + self.assertEqual(pi.items[0].valuation_rate, 100) + + @IntegrationTestCase.change_settings( + "Accounts Settings", {"exchange_gain_loss_posting_date": "Reconciliation Date"} + ) + def test_17_gain_loss_posting_date_for_normal_payment(self): + # Sales Invoice in Foreign Currency + rate = 80 + rate_in_account_currency = 1 + + adv_date = convert_to_date(add_days(nowdate(), -2)) + inv_date = convert_to_date(add_days(nowdate(), -1)) + + si = self.create_sales_invoice(posting_date=inv_date, qty=1, rate=rate_in_account_currency) + + # Test payments with different exchange rates + pe = self.create_payment_entry(posting_date=adv_date, amount=1, source_exc_rate=75.1).save().submit() + + pr = self.create_payment_reconciliation() + pr.from_invoice_date = add_days(nowdate(), -1) + pr.to_invoice_date = nowdate() + pr.from_payment_date = add_days(nowdate(), -2) + pr.to_payment_date = nowdate() + + pr.get_unreconciled_entries() + self.assertEqual(len(pr.invoices), 1) + self.assertEqual(len(pr.payments), 1) + invoices = [x.as_dict() for x in pr.invoices] + payments = [x.as_dict() for x in pr.payments] + pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments})) + pr.reconcile() + self.assertEqual(len(pr.invoices), 0) + self.assertEqual(len(pr.payments), 0) + + # Outstanding in both currencies should be '0' + si.reload() + self.assertEqual(si.outstanding_amount, 0) + self.assert_ledger_outstanding(si.doctype, si.name, 0.0, 0.0) + + # Exchange Gain/Loss Journal should've been created. + exc_je_for_si = self.get_journals_for(si.doctype, si.name) + exc_je_for_pe = self.get_journals_for(pe.doctype, pe.name) + self.assertNotEqual(exc_je_for_si, []) + self.assertEqual(len(exc_je_for_si), 1) + self.assertEqual(len(exc_je_for_pe), 1) + self.assertEqual(exc_je_for_si[0], exc_je_for_pe[0]) + + self.assertEqual( + getdate(nowdate()), frappe.db.get_value("Journal Entry", exc_je_for_pe[0].parent, "posting_date") + ) + # Cancel Payment + pe.reload() + pe.cancel() + + # outstanding should be same as grand total + si.reload() + self.assertEqual(si.outstanding_amount, rate_in_account_currency) + self.assert_ledger_outstanding(si.doctype, si.name, rate, rate_in_account_currency) + + # Exchange Gain/Loss Journal should've been cancelled + exc_je_for_si = self.get_journals_for(si.doctype, si.name) + exc_je_for_pe = self.get_journals_for(pe.doctype, pe.name) + self.assertEqual(exc_je_for_si, []) + self.assertEqual(exc_je_for_pe, []) + +>>>>>>> 2f3281579a (test: exc gain/loss posting date based on configuration) def test_20_journal_against_sales_invoice(self): # Invoice in Foreign Currency si = self.create_sales_invoice(qty=1, conversion_rate=80, rate=1) From cc275318e33600cddfe1cc08119838d7345ececc Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 23 Jan 2025 16:09:54 +0530 Subject: [PATCH 15/28] refactor: support JE posting date in semi-auto reconciilation tool (cherry picked from commit a71718883e933c7eadc15842cae2dd59c6b1d005) # Conflicts: # erpnext/accounts/doctype/process_payment_reconciliation_log_allocations/process_payment_reconciliation_log_allocations.json # erpnext/accounts/doctype/process_payment_reconciliation_log_allocations/process_payment_reconciliation_log_allocations.py --- ...ayment_reconciliation_log_allocations.json | 10 ++++++ ..._payment_reconciliation_log_allocations.py | 31 +++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/erpnext/accounts/doctype/process_payment_reconciliation_log_allocations/process_payment_reconciliation_log_allocations.json b/erpnext/accounts/doctype/process_payment_reconciliation_log_allocations/process_payment_reconciliation_log_allocations.json index b97d73886a9..5c771c54f52 100644 --- a/erpnext/accounts/doctype/process_payment_reconciliation_log_allocations/process_payment_reconciliation_log_allocations.json +++ b/erpnext/accounts/doctype/process_payment_reconciliation_log_allocations/process_payment_reconciliation_log_allocations.json @@ -20,6 +20,7 @@ "is_advance", "section_break_5", "difference_amount", + "gain_loss_posting_date", "column_break_7", "difference_account", "exchange_rate", @@ -153,11 +154,20 @@ "fieldtype": "Check", "in_list_view": 1, "label": "Reconciled" + }, + { + "fieldname": "gain_loss_posting_date", + "fieldtype": "Date", + "label": "Difference Posting Date" } ], "istable": 1, "links": [], +<<<<<<< HEAD "modified": "2023-03-20 21:05:43.121945", +======= + "modified": "2025-01-23 16:09:01.058574", +>>>>>>> a71718883e (refactor: support JE posting date in semi-auto reconciilation tool) "modified_by": "Administrator", "module": "Accounts", "name": "Process Payment Reconciliation Log Allocations", diff --git a/erpnext/accounts/doctype/process_payment_reconciliation_log_allocations/process_payment_reconciliation_log_allocations.py b/erpnext/accounts/doctype/process_payment_reconciliation_log_allocations/process_payment_reconciliation_log_allocations.py index c3e43297d08..265ba4ff664 100644 --- a/erpnext/accounts/doctype/process_payment_reconciliation_log_allocations/process_payment_reconciliation_log_allocations.py +++ b/erpnext/accounts/doctype/process_payment_reconciliation_log_allocations/process_payment_reconciliation_log_allocations.py @@ -6,4 +6,35 @@ from frappe.model.document import Document class ProcessPaymentReconciliationLogAllocations(Document): +<<<<<<< HEAD +======= + # begin: auto-generated types + # This code is auto-generated. Do not modify anything in this block. + + from typing import TYPE_CHECKING + + if TYPE_CHECKING: + from frappe.types import DF + + allocated_amount: DF.Currency + amount: DF.Currency + currency: DF.Link | None + difference_account: DF.Link | None + difference_amount: DF.Currency + exchange_rate: DF.Float + gain_loss_posting_date: DF.Date | None + invoice_number: DF.DynamicLink + invoice_type: DF.Link + is_advance: DF.Data | None + parent: DF.Data + parentfield: DF.Data + parenttype: DF.Data + reconciled: DF.Check + reference_name: DF.DynamicLink + reference_row: DF.Data | None + reference_type: DF.Link + unreconciled_amount: DF.Currency + # end: auto-generated types + +>>>>>>> a71718883e (refactor: support JE posting date in semi-auto reconciilation tool) pass From 0eddd1e2d7226c2832c5c483d309267526bc5944 Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Fri, 31 Jan 2025 13:41:06 +0530 Subject: [PATCH 16/28] fix: renamed Commments Tab to Notes tab in Lead doctype (cherry picked from commit 018df3135a89b413101aad084413349eeeb735ee) # Conflicts: # erpnext/crm/doctype/lead/lead.json --- erpnext/crm/doctype/lead/lead.json | 50 +++++++++++++++++++++++++++++- 1 file changed, 49 insertions(+), 1 deletion(-) diff --git a/erpnext/crm/doctype/lead/lead.json b/erpnext/crm/doctype/lead/lead.json index b7292a77f7d..f05a1759e71 100644 --- a/erpnext/crm/doctype/lead/lead.json +++ b/erpnext/crm/doctype/lead/lead.json @@ -375,7 +375,7 @@ "depends_on": "eval:!doc.__islocal", "fieldname": "notes_tab", "fieldtype": "Tab Break", - "label": "Comments" + "label": "Notes" }, { "collapsible": 1, @@ -508,13 +508,61 @@ "fieldtype": "Tab Break", "label": "Connections", "show_dashboard": 1 +<<<<<<< HEAD +======= + }, + { + "fieldname": "column_break_gkxo", + "fieldtype": "Column Break" + }, + { + "fieldname": "column_break_gqka", + "fieldtype": "Column Break" + }, + { + "fieldname": "utm_content", + "fieldtype": "Data", + "label": "Content", + "print_hide": 1 + }, + { + "fieldname": "utm_source", + "fieldtype": "Link", + "label": "Source", + "oldfieldname": "source", + "oldfieldtype": "Select", + "options": "UTM Source" + }, + { + "fieldname": "utm_medium", + "fieldtype": "Link", + "label": "Medium", + "options": "UTM Medium" + }, + { + "fieldname": "utm_campaign", + "fieldtype": "Link", + "label": "Campaign", + "oldfieldname": "campaign_name", + "oldfieldtype": "Link", + "options": "UTM Campaign" + }, + { + "fieldname": "section_break_analytics", + "fieldtype": "Section Break", + "label": "Analytics" +>>>>>>> 018df3135a (fix: renamed Commments Tab to Notes tab in Lead doctype) } ], "icon": "fa fa-user", "idx": 5, "image_field": "image", "links": [], +<<<<<<< HEAD "modified": "2023-12-01 18:46:49.468526", +======= + "modified": "2025-01-31 13:40:08.094759", +>>>>>>> 018df3135a (fix: renamed Commments Tab to Notes tab in Lead doctype) "modified_by": "Administrator", "module": "CRM", "name": "Lead", From e9d934d378cb63d9416cf4578cad3675fc68489c Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 3 Feb 2025 13:22:24 +0530 Subject: [PATCH 17/28] chore: resolve conflicts --- .../accounts_settings/accounts_settings.json | 80 ------------------ .../accounts_settings/accounts_settings.py | 56 ------------- ...ayment_reconciliation_log_allocations.json | 4 - ..._payment_reconciliation_log_allocations.py | 31 ------- erpnext/controllers/accounts_controller.py | 82 ------------------- .../tests/test_accounts_controller.py | 72 +--------------- erpnext/patches.txt | 11 --- 7 files changed, 2 insertions(+), 334 deletions(-) diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json index b1393d5de9f..16c5479c210 100644 --- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json +++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json @@ -43,14 +43,8 @@ "allow_stale", "section_break_jpd0", "auto_reconcile_payments", -<<<<<<< HEAD "stale_days", -======= - "auto_reconciliation_job_trigger", - "reconciliation_queue_size", - "column_break_resa", "exchange_gain_loss_posting_date", ->>>>>>> 3fbd2ca0d9 (refactor: configurable posting date for Exc Gain / Loss journal) "invoicing_settings_tab", "accounts_transactions_settings_section", "over_billing_allowance", @@ -469,67 +463,6 @@ "fieldname": "remarks_section", "fieldtype": "Section Break", "label": "Remarks Column Length" -<<<<<<< HEAD -======= - }, - { - "default": "0", - "description": "On enabling this cancellation entries will be posted on the actual cancellation date and reports will consider cancelled entries as well", - "fieldname": "enable_immutable_ledger", - "fieldtype": "Check", - "label": "Enable Immutable Ledger" - }, - { - "fieldname": "column_break_gjcc", - "fieldtype": "Column Break" - }, - { - "default": "0", - "description": "Enable this option to calculate daily depreciation by considering the total number of days in the entire depreciation period, (including leap years) while using daily pro-rata based depreciation", - "fieldname": "calculate_depr_using_total_days", - "fieldtype": "Check", - "label": "Calculate daily depreciation using total days in depreciation period" - }, - { - "description": "Payment Request created from Sales Order or Purchase Order will be in Draft status. When disabled document will be in unsaved state.", - "fieldname": "payment_request_settings", - "fieldtype": "Tab Break", - "label": "Payment Request" - }, - { - "default": "1", - "fieldname": "create_pr_in_draft_status", - "fieldtype": "Check", - "label": "Create in Draft Status" - }, - { - "fieldname": "column_break_yuug", - "fieldtype": "Column Break" - }, - { - "fieldname": "column_break_resa", - "fieldtype": "Column Break" - }, - { - "default": "15", - "description": "Interval should be between 1 to 59 MInutes", - "fieldname": "auto_reconciliation_job_trigger", - "fieldtype": "Int", - "label": "Auto Reconciliation Job Trigger" - }, - { - "default": "5", - "description": "Documents Processed on each trigger. Queue Size should be between 5 and 100", - "fieldname": "reconciliation_queue_size", - "fieldtype": "Int", - "label": "Reconciliation Queue Size" - }, - { - "default": "0", - "description": "Ignores legacy Is Opening field in GL Entry that allows adding opening balance post the system is in use while generating reports", - "fieldname": "ignore_is_opening_check_for_reporting", - "fieldtype": "Check", - "label": "Ignore Is Opening check for reporting" }, { "default": "Payment", @@ -537,12 +470,7 @@ "fieldname": "exchange_gain_loss_posting_date", "fieldtype": "Select", "label": "Posting Date Inheritance for Exchange Gain / Loss", -<<<<<<< HEAD - "options": "Invoice\nPayment" ->>>>>>> 3fbd2ca0d9 (refactor: configurable posting date for Exc Gain / Loss journal) -======= "options": "Invoice\nPayment\nReconciliation Date" ->>>>>>> 95af63e305 (refactor: allow reconciliation date for exchange gain / loss) } ], "icon": "icon-cog", @@ -550,15 +478,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], -<<<<<<< HEAD -<<<<<<< HEAD - "modified": "2024-01-22 12:10:10.151819", -======= - "modified": "2025-01-22 17:53:47.968079", ->>>>>>> 3fbd2ca0d9 (refactor: configurable posting date for Exc Gain / Loss journal) -======= "modified": "2025-01-23 13:15:44.077853", ->>>>>>> 95af63e305 (refactor: allow reconciliation date for exchange gain / loss) "modified_by": "Administrator", "module": "Accounts", "name": "Accounts Settings", diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.py b/erpnext/accounts/doctype/accounts_settings/accounts_settings.py index ca3b18671a6..ac3d44bb5e7 100644 --- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.py +++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.py @@ -14,62 +14,6 @@ from erpnext.stock.utils import check_pending_reposting class AccountsSettings(Document): -<<<<<<< HEAD -======= - # begin: auto-generated types - # This code is auto-generated. Do not modify anything in this block. - - from typing import TYPE_CHECKING - - if TYPE_CHECKING: - from frappe.types import DF - - acc_frozen_upto: DF.Date | None - add_taxes_from_item_tax_template: DF.Check - allow_multi_currency_invoices_against_single_party_account: DF.Check - allow_stale: DF.Check - auto_reconcile_payments: DF.Check - auto_reconciliation_job_trigger: DF.Int - automatically_fetch_payment_terms: DF.Check - automatically_process_deferred_accounting_entry: DF.Check - book_asset_depreciation_entry_automatically: DF.Check - book_deferred_entries_based_on: DF.Literal["Days", "Months"] - book_deferred_entries_via_journal_entry: DF.Check - book_tax_discount_loss: DF.Check - calculate_depr_using_total_days: DF.Check - check_supplier_invoice_uniqueness: DF.Check - create_pr_in_draft_status: DF.Check - credit_controller: DF.Link | None - delete_linked_ledger_entries: DF.Check - determine_address_tax_category_from: DF.Literal["Billing Address", "Shipping Address"] - enable_common_party_accounting: DF.Check - enable_fuzzy_matching: DF.Check - enable_immutable_ledger: DF.Check - enable_party_matching: DF.Check - exchange_gain_loss_posting_date: DF.Literal["Invoice", "Payment", "Reconciliation Date"] - frozen_accounts_modifier: DF.Link | None - general_ledger_remarks_length: DF.Int - ignore_account_closing_balance: DF.Check - ignore_is_opening_check_for_reporting: DF.Check - make_payment_via_journal_entry: DF.Check - merge_similar_account_heads: DF.Check - over_billing_allowance: DF.Currency - post_change_gl_entries: DF.Check - receivable_payable_remarks_length: DF.Int - reconciliation_queue_size: DF.Int - role_allowed_to_over_bill: DF.Link | None - round_row_wise_tax: DF.Check - show_balance_in_coa: DF.Check - show_inclusive_tax_in_print: DF.Check - show_payment_schedule_in_print: DF.Check - show_taxes_as_table_in_print: DF.Check - stale_days: DF.Int - submit_journal_entries: DF.Check - unlink_advance_payment_on_cancelation_of_order: DF.Check - unlink_payment_on_cancellation_of_invoice: DF.Check - # end: auto-generated types - ->>>>>>> 3fbd2ca0d9 (refactor: configurable posting date for Exc Gain / Loss journal) def validate(self): old_doc = self.get_doc_before_save() clear_cache = False diff --git a/erpnext/accounts/doctype/process_payment_reconciliation_log_allocations/process_payment_reconciliation_log_allocations.json b/erpnext/accounts/doctype/process_payment_reconciliation_log_allocations/process_payment_reconciliation_log_allocations.json index 5c771c54f52..d7ea1c2ca68 100644 --- a/erpnext/accounts/doctype/process_payment_reconciliation_log_allocations/process_payment_reconciliation_log_allocations.json +++ b/erpnext/accounts/doctype/process_payment_reconciliation_log_allocations/process_payment_reconciliation_log_allocations.json @@ -163,11 +163,7 @@ ], "istable": 1, "links": [], -<<<<<<< HEAD - "modified": "2023-03-20 21:05:43.121945", -======= "modified": "2025-01-23 16:09:01.058574", ->>>>>>> a71718883e (refactor: support JE posting date in semi-auto reconciilation tool) "modified_by": "Administrator", "module": "Accounts", "name": "Process Payment Reconciliation Log Allocations", diff --git a/erpnext/accounts/doctype/process_payment_reconciliation_log_allocations/process_payment_reconciliation_log_allocations.py b/erpnext/accounts/doctype/process_payment_reconciliation_log_allocations/process_payment_reconciliation_log_allocations.py index 265ba4ff664..c3e43297d08 100644 --- a/erpnext/accounts/doctype/process_payment_reconciliation_log_allocations/process_payment_reconciliation_log_allocations.py +++ b/erpnext/accounts/doctype/process_payment_reconciliation_log_allocations/process_payment_reconciliation_log_allocations.py @@ -6,35 +6,4 @@ from frappe.model.document import Document class ProcessPaymentReconciliationLogAllocations(Document): -<<<<<<< HEAD -======= - # begin: auto-generated types - # This code is auto-generated. Do not modify anything in this block. - - from typing import TYPE_CHECKING - - if TYPE_CHECKING: - from frappe.types import DF - - allocated_amount: DF.Currency - amount: DF.Currency - currency: DF.Link | None - difference_account: DF.Link | None - difference_amount: DF.Currency - exchange_rate: DF.Float - gain_loss_posting_date: DF.Date | None - invoice_number: DF.DynamicLink - invoice_type: DF.Link - is_advance: DF.Data | None - parent: DF.Data - parentfield: DF.Data - parenttype: DF.Data - reconciled: DF.Check - reference_name: DF.DynamicLink - reference_row: DF.Data | None - reference_type: DF.Link - unreconciled_amount: DF.Currency - # end: auto-generated types - ->>>>>>> a71718883e (refactor: support JE posting date in semi-auto reconciilation tool) pass diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 07f6ae62746..160c478f299 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -2767,7 +2767,6 @@ def get_advance_payment_entries( condition.append(pe.name.like(f"%%{payment_name}%%")) if order_list or against_all_orders: -<<<<<<< HEAD orders_condition = [] if order_list: orders_condition.append(per.reference_name.isin(order_list)) @@ -2785,87 +2784,6 @@ def get_advance_payment_entries( pe.posting_date, pe[currency_field].as_("currency"), pe[exchange_rate_field].as_("exchange_rate"), -======= - q = get_common_query( - party_type, - party, - party_account, - default_advance_account, - limit, - condition, - ) - payment_ref = frappe.qb.DocType("Payment Entry Reference") - - q = q.inner_join(payment_ref).on(payment_entry.name == payment_ref.parent) - q = q.select( - (payment_ref.allocated_amount).as_("amount"), - (payment_ref.name).as_("reference_row"), - (payment_ref.reference_name).as_("against_order"), - (payment_entry.book_advance_payments_in_separate_party_account), - ) - - q = q.where(payment_ref.reference_doctype == order_doctype) - if order_list: - q = q.where(payment_ref.reference_name.isin(order_list)) - - allocated = list(q.run(as_dict=True)) - payment_entries += allocated - if include_unallocated: - q = get_common_query( - party_type, - party, - party_account, - default_advance_account, - limit, - condition, - ) - q = q.select((payment_entry.unallocated_amount).as_("amount")) - q = q.where(payment_entry.unallocated_amount > 0) - - unallocated = list(q.run(as_dict=True)) - payment_entries += unallocated - return payment_entries - - -def get_common_query( - party_type, - party, - party_account, - default_advance_account, - limit, - condition, -): - account_type = frappe.db.get_value("Party Type", party_type, "account_type") - payment_type = "Receive" if account_type == "Receivable" else "Pay" - payment_entry = frappe.qb.DocType("Payment Entry") - - q = ( - frappe.qb.from_(payment_entry) - .select( - ConstantColumn("Payment Entry").as_("reference_type"), - (payment_entry.name).as_("reference_name"), - payment_entry.posting_date, - (payment_entry.remarks).as_("remarks"), - (payment_entry.book_advance_payments_in_separate_party_account), - ) - .where(payment_entry.payment_type == payment_type) - .where(payment_entry.party_type == party_type) - .where(payment_entry.party == party) - .where(payment_entry.docstatus == 1) - ) - - field = "paid_from" if payment_type == "Receive" else "paid_to" - - q = q.select((payment_entry[f"{field}_account_currency"]).as_("currency")) - q = q.select(payment_entry[field]) - account_condition = payment_entry[field].isin(party_account) - if default_advance_account: - q = q.where( - account_condition - | ( - (payment_entry[field] == default_advance_account) - & (payment_entry.book_advance_payments_in_separate_party_account == 1) ->>>>>>> b2c3da135e (refactor: only apply configuration on normal payments) ) .where( (pe[party_account_field] == party_account) diff --git a/erpnext/controllers/tests/test_accounts_controller.py b/erpnext/controllers/tests/test_accounts_controller.py index bd32cbafccf..7470f4eda25 100644 --- a/erpnext/controllers/tests/test_accounts_controller.py +++ b/erpnext/controllers/tests/test_accounts_controller.py @@ -7,7 +7,7 @@ from datetime import datetime import frappe from frappe import qb from frappe.query_builder.functions import Sum -from frappe.tests.utils import FrappeTestCase +from frappe.tests.utils import FrappeTestCase, change_settings from frappe.utils import add_days, getdate, nowdate from frappe.utils.data import getdate as convert_to_date @@ -708,74 +708,7 @@ class TestAccountsController(FrappeTestCase): self.assertEqual(exc_je_for_si, []) self.assertEqual(exc_je_for_pe, []) -<<<<<<< HEAD -======= - @IntegrationTestCase.change_settings( - "Stock Settings", {"allow_internal_transfer_at_arms_length_price": 1} - ) - def test_16_internal_transfer_at_arms_length_price(self): - from erpnext.accounts.doctype.sales_invoice.sales_invoice import make_inter_company_purchase_invoice - from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse - - prepare_data_for_internal_transfer() - company = "_Test Company with perpetual inventory" - target_warehouse = create_warehouse("_Test Internal Warehouse New 1", company=company) - warehouse = create_warehouse("_Test Internal Warehouse New 2", company=company) - arms_length_price = 40 - - si = create_sales_invoice( - company=company, - customer="_Test Internal Customer 2", - debit_to="Debtors - TCP1", - target_warehouse=target_warehouse, - warehouse=warehouse, - income_account="Sales - TCP1", - expense_account="Cost of Goods Sold - TCP1", - cost_center="Main - TCP1", - update_stock=True, - do_not_save=True, - do_not_submit=True, - ) - - si.items[0].rate = arms_length_price - si.save() - # rate should not reset to incoming rate - self.assertEqual(si.items[0].rate, arms_length_price) - - frappe.db.set_single_value("Stock Settings", "allow_internal_transfer_at_arms_length_price", 0) - si.items[0].rate = arms_length_price - si.save() - # rate should reset to incoming rate - self.assertEqual(si.items[0].rate, 100) - - si.update_stock = 0 - si.save() - si.submit() - - pi = make_inter_company_purchase_invoice(si.name) - pi.update_stock = 1 - pi.items[0].rate = arms_length_price - pi.items[0].warehouse = target_warehouse - pi.items[0].from_warehouse = warehouse - pi.save() - - self.assertEqual(pi.items[0].rate, 100) - self.assertEqual(pi.items[0].valuation_rate, 100) - - frappe.db.set_single_value("Stock Settings", "allow_internal_transfer_at_arms_length_price", 1) - pi = make_inter_company_purchase_invoice(si.name) - pi.update_stock = 1 - pi.items[0].rate = arms_length_price - pi.items[0].warehouse = target_warehouse - pi.items[0].from_warehouse = warehouse - pi.save() - - self.assertEqual(pi.items[0].rate, arms_length_price) - self.assertEqual(pi.items[0].valuation_rate, 100) - - @IntegrationTestCase.change_settings( - "Accounts Settings", {"exchange_gain_loss_posting_date": "Reconciliation Date"} - ) + @change_settings("Accounts Settings", {"exchange_gain_loss_posting_date": "Reconciliation Date"}) def test_17_gain_loss_posting_date_for_normal_payment(self): # Sales Invoice in Foreign Currency rate = 80 @@ -836,7 +769,6 @@ class TestAccountsController(FrappeTestCase): self.assertEqual(exc_je_for_si, []) self.assertEqual(exc_je_for_pe, []) ->>>>>>> 2f3281579a (test: exc gain/loss posting date based on configuration) def test_20_journal_against_sales_invoice(self): # Invoice in Foreign Currency si = self.create_sales_invoice(qty=1, conversion_rate=80, rate=1) diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 93e49b023b1..94e48ef135d 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -368,16 +368,5 @@ erpnext.patches.v14_0.remove_cancelled_asset_capitalization_from_asset erpnext.patches.v14_0.enable_set_priority_for_pricing_rules #1 erpnext.patches.v14_0.update_currency_exchange_settings_for_frankfurter erpnext.patches.v14_0.update_stock_uom_in_work_order_item -<<<<<<< HEAD erpnext.patches.v14_0.disable_add_row_in_gross_profit - -======= -erpnext.patches.v15_0.enable_allow_existing_serial_no -erpnext.patches.v15_0.update_cc_in_process_statement_of_accounts -erpnext.patches.v15_0.refactor_closing_stock_balance #5 -erpnext.patches.v15_0.update_asset_status_to_work_in_progress -erpnext.patches.v15_0.rename_manufacturing_settings_field -erpnext.patches.v15_0.migrate_checkbox_to_select_for_reconciliation_effect -erpnext.patches.v15_0.sync_auto_reconcile_config execute:frappe.db.set_single_value("Accounts Settings", "exchange_gain_loss_posting_date", "Payment") ->>>>>>> b2c3da135e (refactor: only apply configuration on normal payments) From c6ed82a3043fc92423e08901249bdd5fecd9094a Mon Sep 17 00:00:00 2001 From: Shariq Ansari <30859809+shariquerik@users.noreply.github.com> Date: Mon, 3 Feb 2025 13:25:10 +0530 Subject: [PATCH 18/28] chore: resolved conflict --- erpnext/crm/doctype/lead/lead.json | 48 ------------------------------ 1 file changed, 48 deletions(-) diff --git a/erpnext/crm/doctype/lead/lead.json b/erpnext/crm/doctype/lead/lead.json index f05a1759e71..e5ae004df1b 100644 --- a/erpnext/crm/doctype/lead/lead.json +++ b/erpnext/crm/doctype/lead/lead.json @@ -508,61 +508,13 @@ "fieldtype": "Tab Break", "label": "Connections", "show_dashboard": 1 -<<<<<<< HEAD -======= - }, - { - "fieldname": "column_break_gkxo", - "fieldtype": "Column Break" - }, - { - "fieldname": "column_break_gqka", - "fieldtype": "Column Break" - }, - { - "fieldname": "utm_content", - "fieldtype": "Data", - "label": "Content", - "print_hide": 1 - }, - { - "fieldname": "utm_source", - "fieldtype": "Link", - "label": "Source", - "oldfieldname": "source", - "oldfieldtype": "Select", - "options": "UTM Source" - }, - { - "fieldname": "utm_medium", - "fieldtype": "Link", - "label": "Medium", - "options": "UTM Medium" - }, - { - "fieldname": "utm_campaign", - "fieldtype": "Link", - "label": "Campaign", - "oldfieldname": "campaign_name", - "oldfieldtype": "Link", - "options": "UTM Campaign" - }, - { - "fieldname": "section_break_analytics", - "fieldtype": "Section Break", - "label": "Analytics" ->>>>>>> 018df3135a (fix: renamed Commments Tab to Notes tab in Lead doctype) } ], "icon": "fa fa-user", "idx": 5, "image_field": "image", "links": [], -<<<<<<< HEAD - "modified": "2023-12-01 18:46:49.468526", -======= "modified": "2025-01-31 13:40:08.094759", ->>>>>>> 018df3135a (fix: renamed Commments Tab to Notes tab in Lead doctype) "modified_by": "Administrator", "module": "CRM", "name": "Lead", From 43a5c33dbf9a89dc76b9f042a1f9bdc74fa5b4f8 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Mon, 3 Feb 2025 14:21:50 +0530 Subject: [PATCH 19/28] fix: track employee changes (#45674) closes https://github.com/frappe/erpnext/issues/45571 (cherry picked from commit 827afbfa2e71acf26cdc301bf39aec426093295b) --- erpnext/setup/doctype/employee/employee.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/erpnext/setup/doctype/employee/employee.json b/erpnext/setup/doctype/employee/employee.json index daf2df5a590..8948211b2e5 100644 --- a/erpnext/setup/doctype/employee/employee.json +++ b/erpnext/setup/doctype/employee/employee.json @@ -871,5 +871,6 @@ "sort_field": "modified", "sort_order": "DESC", "states": [], - "title_field": "employee_name" -} \ No newline at end of file + "title_field": "employee_name", + "track_changes": 1 +} From 7823f1b06f8ead723c8bc395743f322a0092717a Mon Sep 17 00:00:00 2001 From: Aayush Dalal Date: Tue, 4 Feb 2025 17:14:01 +0530 Subject: [PATCH 20/28] fix: handling company in bank reconciliation tool (#45582) Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com> (cherry picked from commit d1c927530edff40763c78d81579bb83a60a7a00c) --- .../bank_reconciliation_tool.js | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.js b/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.js index 10c0781e1d3..47f1b869df5 100644 --- a/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.js +++ b/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.js @@ -19,10 +19,15 @@ frappe.ui.form.on("Bank Reconciliation Tool", { }, onload: function (frm) { + if (!frm.doc.company) { + frm.set_value("company", frappe.defaults.get_default("company")); + } + // Set default filter dates let today = frappe.datetime.get_today(); frm.doc.bank_statement_from_date = frappe.datetime.add_months(today, -1); frm.doc.bank_statement_to_date = today; + frm.trigger("bank_account"); }, @@ -94,7 +99,7 @@ frappe.ui.form.on("Bank Reconciliation Tool", { make_reconciliation_tool(frm) { frm.get_field("reconciliation_tool_cards").$wrapper.empty(); - if (frm.doc.bank_account && frm.doc.bank_statement_to_date) { + if (frm.doc.company && frm.doc.bank_account && frm.doc.bank_statement_to_date) { frm.trigger("get_cleared_balance").then(() => { if ( frm.doc.bank_account && @@ -110,7 +115,7 @@ frappe.ui.form.on("Bank Reconciliation Tool", { }, get_account_opening_balance(frm) { - if (frm.doc.bank_account && frm.doc.bank_statement_from_date) { + if (frm.doc.company && frm.doc.bank_account && frm.doc.bank_statement_from_date) { frappe.call({ method: "erpnext.accounts.doctype.bank_reconciliation_tool.bank_reconciliation_tool.get_account_balance", args: { @@ -125,7 +130,7 @@ frappe.ui.form.on("Bank Reconciliation Tool", { }, get_cleared_balance(frm) { - if (frm.doc.bank_account && frm.doc.bank_statement_to_date) { + if (frm.doc.company && frm.doc.bank_account && frm.doc.bank_statement_to_date) { return frappe.call({ method: "erpnext.accounts.doctype.bank_reconciliation_tool.bank_reconciliation_tool.get_account_balance", args: { From 3f6beebeec6724d00f00e360f80265e697651c4a Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Wed, 5 Feb 2025 05:29:23 +0530 Subject: [PATCH 21/28] fix: closing stock balance report not generating (cherry picked from commit 47d1c3b5a386f8abf464460522f07754a26d4948) --- .../stock/doctype/closing_stock_balance/closing_stock_balance.py | 1 - erpnext/stock/report/stock_balance/stock_balance.py | 1 - 2 files changed, 2 deletions(-) diff --git a/erpnext/stock/doctype/closing_stock_balance/closing_stock_balance.py b/erpnext/stock/doctype/closing_stock_balance/closing_stock_balance.py index eda768b1c56..38e15887e44 100644 --- a/erpnext/stock/doctype/closing_stock_balance/closing_stock_balance.py +++ b/erpnext/stock/doctype/closing_stock_balance/closing_stock_balance.py @@ -95,7 +95,6 @@ class ClosingStockBalance(Document): "item_group": self.item_group, "warehouse_type": self.warehouse_type, "include_uom": self.include_uom, - "ignore_closing_balance": 1, "show_variant_attributes": 1, "show_stock_ageing_data": 1, } diff --git a/erpnext/stock/report/stock_balance/stock_balance.py b/erpnext/stock/report/stock_balance/stock_balance.py index eee582ac054..78112682a7e 100644 --- a/erpnext/stock/report/stock_balance/stock_balance.py +++ b/erpnext/stock/report/stock_balance/stock_balance.py @@ -289,7 +289,6 @@ class StockBalanceReport: .where((sle.docstatus < 2) & (sle.is_cancelled == 0)) .orderby(sle.posting_datetime) .orderby(sle.creation) - .orderby(sle.actual_qty) ) query = self.apply_inventory_dimensions_filters(query, sle) From 9510758ce4b1c4af07d4b7e087fbde376cf91e65 Mon Sep 17 00:00:00 2001 From: Sudharsanan11 Date: Mon, 3 Feb 2025 18:27:32 +0530 Subject: [PATCH 22/28] fix: allow multiple email ids (cherry picked from commit 423decb93c4249c979e078e1c342ce068e012fad) --- .../process_statement_of_accounts.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py index 06c037dba84..78991cb4e2f 100644 --- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py +++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py @@ -262,9 +262,11 @@ def get_recipients_and_cc(customer, doc): recipients = [] for clist in doc.customers: if clist.customer == customer: - recipients.append(clist.billing_email) + for email in clist.billing_email.split(","): + recipients.append(email.strip()) if doc.primary_mandatory and clist.primary_email: - recipients.append(clist.primary_email) + for email in clist.primary_email.split(","): + recipients.append(email.strip()) cc = [] if doc.cc_to != "": try: From 2f33f6bdf53a80f4799f5da06bb6869ea889d85f Mon Sep 17 00:00:00 2001 From: Sudharsanan11 Date: Tue, 4 Feb 2025 10:59:10 +0530 Subject: [PATCH 23/28] fix: check billing address (cherry picked from commit 9950e4aa0c53ce8fb128d534c0438f7dea78fea0) --- .../process_statement_of_accounts.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py index 78991cb4e2f..a6a2b2410c9 100644 --- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py +++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py @@ -262,8 +262,9 @@ def get_recipients_and_cc(customer, doc): recipients = [] for clist in doc.customers: if clist.customer == customer: - for email in clist.billing_email.split(","): - recipients.append(email.strip()) + if clist.billing_email: + for email in clist.billing_email.split(","): + recipients.append(email.strip()) if doc.primary_mandatory and clist.primary_email: for email in clist.primary_email.split(","): recipients.append(email.strip()) From 3c6ed0a565aa1f3371feeba02cb46d0ba9ad7627 Mon Sep 17 00:00:00 2001 From: venkat102 Date: Sat, 25 Jan 2025 12:59:04 +0530 Subject: [PATCH 24/28] fix(pos): add item in the existing item row when discount is applied (cherry picked from commit bee2c04d0bd882f32f417a3664e5a7586383a239) --- erpnext/selling/page/point_of_sale/pos_controller.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/selling/page/point_of_sale/pos_controller.js b/erpnext/selling/page/point_of_sale/pos_controller.js index 35622a77e2c..a81bef3db32 100644 --- a/erpnext/selling/page/point_of_sale/pos_controller.js +++ b/erpnext/selling/page/point_of_sale/pos_controller.js @@ -635,7 +635,7 @@ erpnext.PointOfSale.Controller = class { i.item_code === item_code && (!has_batch_no || (has_batch_no && i.batch_no === batch_no)) && i.uom === uom && - i.rate == rate + i.price_list_rate === flt(rate) ); } From 82cfafb6100e7fbb4605f0999839a65fd97d60d2 Mon Sep 17 00:00:00 2001 From: Ejaaz Khan Date: Tue, 4 Feb 2025 23:39:29 +0530 Subject: [PATCH 25/28] fix: default payment terms template selected while duplicating (cherry picked from commit 18127603fe837af8856565a2deddc9d3e42ef243) --- erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js index 4a8c8ad82a7..4972a452d9b 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js @@ -311,7 +311,9 @@ erpnext.accounts.PurchaseInvoice = class PurchaseInvoice extends erpnext.buying. account: this.frm.doc.credit_to, price_list: this.frm.doc.buying_price_list, fetch_payment_terms_template: cint( - (this.frm.doc.is_return == 0) & !this.frm.doc.ignore_default_payment_terms_template + (this.frm.doc.is_return == 0) & + !this.frm.doc.ignore_default_payment_terms_template & + (this.frm.doc.__run_link_triggers !== false) ), }, function () { From d0ff91b0e040d0ff31c795cc3709ffa48d8a2c2f Mon Sep 17 00:00:00 2001 From: Ejaaz Khan Date: Wed, 5 Feb 2025 00:06:48 +0530 Subject: [PATCH 26/28] fix: payment schedule table is empty while duplicating record (cherry picked from commit fb3f08a44139d12b0d205e3f019cedcca923397a) # Conflicts: # erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js --- .../purchase_invoice/purchase_invoice.js | 23 ++++++++++++++++--- erpnext/public/js/controllers/transaction.js | 2 ++ 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js index 4972a452d9b..e0a09f4f1de 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js @@ -302,7 +302,15 @@ erpnext.accounts.PurchaseInvoice = class PurchaseInvoice extends erpnext.buying. if (this.frm.doc.__onload && this.frm.doc.__onload.load_after_mapping) return; +<<<<<<< HEAD erpnext.utils.get_party_details(this.frm, "erpnext.accounts.party.get_party_details", +======= + let payment_terms_template = this.frm.doc.payment_terms_template; + + erpnext.utils.get_party_details( + this.frm, + "erpnext.accounts.party.get_party_details", +>>>>>>> fb3f08a441 (fix: payment schedule table is empty while duplicating record) { posting_date: this.frm.doc.posting_date, bill_date: this.frm.doc.bill_date, @@ -311,9 +319,7 @@ erpnext.accounts.PurchaseInvoice = class PurchaseInvoice extends erpnext.buying. account: this.frm.doc.credit_to, price_list: this.frm.doc.buying_price_list, fetch_payment_terms_template: cint( - (this.frm.doc.is_return == 0) & - !this.frm.doc.ignore_default_payment_terms_template & - (this.frm.doc.__run_link_triggers !== false) + (this.frm.doc.is_return == 0) & !this.frm.doc.ignore_default_payment_terms_template ), }, function () { @@ -322,7 +328,18 @@ erpnext.accounts.PurchaseInvoice = class PurchaseInvoice extends erpnext.buying. me.frm.doc.tax_withholding_category = me.frm.supplier_tds; me.frm.set_df_property("apply_tds", "read_only", me.frm.supplier_tds ? 0 : 1); me.frm.set_df_property("tax_withholding_category", "hidden", me.frm.supplier_tds ? 0 : 1); +<<<<<<< HEAD }) +======= + + // while duplicating, don't change payment terms + if (me.frm.doc.__run_link_triggers === false) { + me.frm.set_value("payment_terms_template", payment_terms_template); + me.frm.refresh_field("payment_terms_template"); + } + } + ); +>>>>>>> fb3f08a441 (fix: payment schedule table is empty while duplicating record) } apply_tds(frm) { diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index a424d01489d..623f331d5d0 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -2217,6 +2217,8 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe } payment_terms_template() { + console.log("payment_terms_template outside"); + var me = this; const doc = this.frm.doc; if(doc.payment_terms_template && doc.doctype !== 'Delivery Note' && !doc.is_return) { From 95903c9f968b4ce62a982b2d9706ee33c869505a Mon Sep 17 00:00:00 2001 From: Ejaaz Khan Date: Wed, 5 Feb 2025 00:09:16 +0530 Subject: [PATCH 27/28] refactor: remove log (cherry picked from commit bfc01441a073c9e5658c2723b76235f5e8ed237f) --- erpnext/public/js/controllers/transaction.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 623f331d5d0..a424d01489d 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -2217,8 +2217,6 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe } payment_terms_template() { - console.log("payment_terms_template outside"); - var me = this; const doc = this.frm.doc; if(doc.payment_terms_template && doc.doctype !== 'Delivery Note' && !doc.is_return) { From 49787b6d84e41487330296889dad86dba51364d9 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Wed, 5 Feb 2025 15:22:13 +0530 Subject: [PATCH 28/28] chore: resolve conflicts --- .../accounts/doctype/purchase_invoice/purchase_invoice.js | 8 -------- 1 file changed, 8 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js index e0a09f4f1de..11bfc5e8dde 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js @@ -302,15 +302,11 @@ erpnext.accounts.PurchaseInvoice = class PurchaseInvoice extends erpnext.buying. if (this.frm.doc.__onload && this.frm.doc.__onload.load_after_mapping) return; -<<<<<<< HEAD - erpnext.utils.get_party_details(this.frm, "erpnext.accounts.party.get_party_details", -======= let payment_terms_template = this.frm.doc.payment_terms_template; erpnext.utils.get_party_details( this.frm, "erpnext.accounts.party.get_party_details", ->>>>>>> fb3f08a441 (fix: payment schedule table is empty while duplicating record) { posting_date: this.frm.doc.posting_date, bill_date: this.frm.doc.bill_date, @@ -328,9 +324,6 @@ erpnext.accounts.PurchaseInvoice = class PurchaseInvoice extends erpnext.buying. me.frm.doc.tax_withholding_category = me.frm.supplier_tds; me.frm.set_df_property("apply_tds", "read_only", me.frm.supplier_tds ? 0 : 1); me.frm.set_df_property("tax_withholding_category", "hidden", me.frm.supplier_tds ? 0 : 1); -<<<<<<< HEAD - }) -======= // while duplicating, don't change payment terms if (me.frm.doc.__run_link_triggers === false) { @@ -339,7 +332,6 @@ erpnext.accounts.PurchaseInvoice = class PurchaseInvoice extends erpnext.buying. } } ); ->>>>>>> fb3f08a441 (fix: payment schedule table is empty while duplicating record) } apply_tds(frm) {