From 78ab44ce1a598f128b31582ed83435227f5f10b9 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 27 Nov 2024 17:09:09 +0100 Subject: [PATCH 01/26] feat: add Company Contact Person in selling transactions (backport #44362) (#44397) * feat: add Company Contact Person in selling transactions (#44362) (cherry picked from commit f6776c7d6b37ce2766244dfeaf5939bda1a183ba) * chore: resolve conflicts --------- Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com> --- .../accounts/doctype/pos_invoice/pos_invoice.json | 10 +++++++++- .../doctype/sales_invoice/sales_invoice.json | 12 ++++++++++-- erpnext/public/js/queries.js | 11 +++++++++++ erpnext/selling/doctype/quotation/quotation.json | 12 ++++++++++-- .../selling/doctype/sales_order/sales_order.json | 14 +++++++++++--- erpnext/selling/sales_common.js | 1 + .../stock/doctype/delivery_note/delivery_note.json | 12 ++++++++++-- 7 files changed, 62 insertions(+), 10 deletions(-) diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.json b/erpnext/accounts/doctype/pos_invoice/pos_invoice.json index 854523f1009..f5a9d17f088 100644 --- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.json +++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.json @@ -48,6 +48,7 @@ "shipping_address", "company_address", "company_address_display", + "company_contact_person", "currency_and_price_list", "currency", "conversion_rate", @@ -1557,12 +1558,19 @@ "fieldname": "update_billed_amount_in_delivery_note", "fieldtype": "Check", "label": "Update Billed Amount in Delivery Note" + }, + { + "fieldname": "company_contact_person", + "fieldtype": "Link", + "label": "Company Contact Person", + "options": "Contact", + "print_hide": 1 } ], "icon": "fa fa-file-text", "is_submittable": 1, "links": [], - "modified": "2024-03-20 16:00:34.268756", + "modified": "2024-11-26 13:10:50.309570", "modified_by": "Administrator", "module": "Accounts", "name": "POS Invoice", diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json index 632392511bd..b58c7750d78 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json @@ -160,8 +160,9 @@ "dispatch_address", "company_address_section", "company_address", - "company_addr_col_break", "company_address_display", + "company_addr_col_break", + "company_contact_person", "terms_tab", "payment_schedule_section", "ignore_default_payment_terms_template", @@ -2171,6 +2172,13 @@ "label": "Update Outstanding for Self", "no_copy": 1, "print_hide": 1 + }, + { + "fieldname": "company_contact_person", + "fieldtype": "Link", + "label": "Company Contact Person", + "options": "Contact", + "print_hide": 1 } ], "icon": "fa fa-file-text", @@ -2183,7 +2191,7 @@ "link_fieldname": "consolidated_invoice" } ], - "modified": "2024-07-18 15:30:39.428519", + "modified": "2024-11-26 12:34:09.110690", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice", diff --git a/erpnext/public/js/queries.js b/erpnext/public/js/queries.js index 4bdd463f047..f014e6303ff 100644 --- a/erpnext/public/js/queries.js +++ b/erpnext/public/js/queries.js @@ -64,6 +64,17 @@ $.extend(erpnext.queries, { } }, + company_contact_query: function (doc) { + if (!doc.company) { + frappe.throw(__("Please set {0}", [__(frappe.meta.get_label(doc.doctype, "company", doc.name))])); + } + + return { + query: "frappe.contacts.doctype.contact.contact.contact_query", + filters: { link_doctype: "Company", link_name: doc.company }, + }; + }, + address_query: function (doc) { if (frappe.dynamic_link) { if (!doc[frappe.dynamic_link.fieldname]) { diff --git a/erpnext/selling/doctype/quotation/quotation.json b/erpnext/selling/doctype/quotation/quotation.json index 40fa986951e..ee37c7843d3 100644 --- a/erpnext/selling/doctype/quotation/quotation.json +++ b/erpnext/selling/doctype/quotation/quotation.json @@ -95,8 +95,9 @@ "shipping_address", "company_address_section", "company_address", - "column_break_87", "company_address_display", + "column_break_87", + "company_contact_person", "terms_tab", "payment_schedule_section", "payment_terms_template", @@ -1066,13 +1067,20 @@ "fieldname": "named_place", "fieldtype": "Data", "label": "Named Place" + }, + { + "fieldname": "company_contact_person", + "fieldtype": "Link", + "label": "Company Contact Person", + "options": "Contact", + "print_hide": 1 } ], "icon": "fa fa-shopping-cart", "idx": 82, "is_submittable": 1, "links": [], - "modified": "2024-03-20 16:04:21.567847", + "modified": "2024-11-26 12:43:29.293637", "modified_by": "Administrator", "module": "Selling", "name": "Quotation", diff --git a/erpnext/selling/doctype/sales_order/sales_order.json b/erpnext/selling/doctype/sales_order/sales_order.json index 43e8e41f7f3..d4ab0978584 100644 --- a/erpnext/selling/doctype/sales_order/sales_order.json +++ b/erpnext/selling/doctype/sales_order/sales_order.json @@ -112,8 +112,9 @@ "dispatch_address", "col_break46", "company_address", - "column_break_92", "company_address_display", + "column_break_92", + "company_contact_person", "payment_schedule_section", "payment_terms_section", "payment_terms_template", @@ -1626,13 +1627,20 @@ "fieldname": "named_place", "fieldtype": "Data", "label": "Named Place" + }, + { + "fieldname": "company_contact_person", + "fieldtype": "Link", + "label": "Company Contact Person", + "options": "Contact", + "print_hide": 1 } ], "icon": "fa fa-file-text", "idx": 105, "is_submittable": 1, "links": [], - "modified": "2024-05-23 16:35:54.905804", + "modified": "2024-11-26 12:42:06.872527", "modified_by": "Administrator", "module": "Selling", "name": "Sales Order", @@ -1711,4 +1719,4 @@ "title_field": "customer_name", "track_changes": 1, "track_seen": 1 -} +} \ No newline at end of file diff --git a/erpnext/selling/sales_common.js b/erpnext/selling/sales_common.js index 0293f37d0ad..d56e0aa9829 100644 --- a/erpnext/selling/sales_common.js +++ b/erpnext/selling/sales_common.js @@ -41,6 +41,7 @@ erpnext.selling.SellingController = class SellingController extends erpnext.Tran me.frm.set_query('shipping_address_name', erpnext.queries.address_query); me.frm.set_query('dispatch_address_name', erpnext.queries.dispatch_address_query); me.frm.set_query('company_address', erpnext.queries.company_address_query); + me.frm.set_query('company_contact_person', erpnext.queries.company_contact_query); erpnext.accounts.dimensions.setup_dimension_filters(me.frm, me.frm.doctype); diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.json b/erpnext/stock/doctype/delivery_note/delivery_note.json index 60e69599dae..36e83f377a3 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.json +++ b/erpnext/stock/doctype/delivery_note/delivery_note.json @@ -109,8 +109,9 @@ "dispatch_address", "company_address_section", "company_address", - "column_break_101", "company_address_display", + "column_break_101", + "company_contact_person", "terms_tab", "tc_name", "terms", @@ -1395,13 +1396,20 @@ "fieldname": "named_place", "fieldtype": "Data", "label": "Named Place" + }, + { + "fieldname": "company_contact_person", + "fieldtype": "Link", + "label": "Company Contact Person", + "options": "Contact", + "print_hide": 1 } ], "icon": "fa fa-truck", "idx": 146, "is_submittable": 1, "links": [], - "modified": "2024-03-20 16:05:02.854990", + "modified": "2024-11-26 12:44:28.258215", "modified_by": "Administrator", "module": "Stock", "name": "Delivery Note", From 9ae04dfed36383f7867c0bd11ac30621987a49b2 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 27 Nov 2024 23:52:12 +0100 Subject: [PATCH 02/26] fix: show "Send SMS" only when enabled (backport #43941) (#43969) fix: show "Send SMS" only when enabled (#43941) (cherry picked from commit 65088cbb1b7cf54858805b5a5bd1b38ff3e0e29d) Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com> --- erpnext/public/js/controllers/transaction.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 7e2d5e63ec3..f99820af562 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -421,7 +421,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe setup_sms() { var me = this; let blacklist = ['Purchase Invoice', 'BOM']; - if(this.frm.doc.docstatus===1 && !["Lost", "Stopped", "Closed"].includes(this.frm.doc.status) + if(frappe.boot.sms_gateway_enabled && this.frm.doc.docstatus===1 && !["Lost", "Stopped", "Closed"].includes(this.frm.doc.status) && !blacklist.includes(this.frm.doctype)) { this.frm.page.add_menu_item(__('Send SMS'), function() { me.send_sms(); }); } From 147eb047aabb6d79a0232e0ea68809cf88e16579 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 28 Nov 2024 14:41:26 +0530 Subject: [PATCH 03/26] fix: typeerror on transaction.js (cherry picked from commit 46ce8780f2c7284ae6149a9b4a82e58fa1b017d7) --- erpnext/public/js/controllers/transaction.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index f99820af562..9f8aa84f836 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -990,7 +990,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe apply_discount_on_item(doc, cdt, cdn, field) { var item = frappe.get_doc(cdt, cdn); - if(!item.price_list_rate) { + if(item && !item.price_list_rate) { item[field] = 0.0; } else { this.price_list_rate(doc, cdt, cdn); From 8529eb4573aeab38e17883c9285d31c78d911ba2 Mon Sep 17 00:00:00 2001 From: Ninad Parikh <109862100+Ninad1306@users.noreply.github.com> Date: Thu, 28 Nov 2024 15:59:52 +0530 Subject: [PATCH 04/26] fix: Data Should be Computed in Backend to Maintain Consistent Behaviour (#44195) (cherry picked from commit 69bd90b038e08b2348a100445acefb0d7fe7fecc) --- .../report/balance_sheet/balance_sheet.py | 4 ++ .../accounts/report/financial_statements.py | 65 +++++++++++++++++++ .../profit_and_loss_statement.py | 8 +++ erpnext/public/js/financial_statements.js | 39 ++++------- 4 files changed, 91 insertions(+), 25 deletions(-) diff --git a/erpnext/accounts/report/balance_sheet/balance_sheet.py b/erpnext/accounts/report/balance_sheet/balance_sheet.py index baee062cf84..50f6f3b03c5 100644 --- a/erpnext/accounts/report/balance_sheet/balance_sheet.py +++ b/erpnext/accounts/report/balance_sheet/balance_sheet.py @@ -7,6 +7,7 @@ from frappe import _ from frappe.utils import cint, flt from erpnext.accounts.report.financial_statements import ( + compute_growth_view_data, get_columns, get_data, get_filtered_list_for_consolidated_report, @@ -101,6 +102,9 @@ def execute(filters=None): period_list, asset, liability, equity, provisional_profit_loss, currency, filters ) + if filters.get("selected_view") == "Growth": + compute_growth_view_data(data, period_list) + return columns, data, message, chart, report_summary, primitive_summary diff --git a/erpnext/accounts/report/financial_statements.py b/erpnext/accounts/report/financial_statements.py index 56f3bd1caa4..16886cd939a 100644 --- a/erpnext/accounts/report/financial_statements.py +++ b/erpnext/accounts/report/financial_statements.py @@ -2,6 +2,7 @@ # License: GNU General Public License v3. See license.txt +import copy import functools import math import re @@ -653,3 +654,67 @@ def get_filtered_list_for_consolidated_report(filters, period_list): filtered_summary_list.append(period) return filtered_summary_list + + +def compute_growth_view_data(data, columns): + data_copy = copy.deepcopy(data) + + for row_idx in range(len(data_copy)): + for column_idx in range(1, len(columns)): + previous_period_key = columns[column_idx - 1].get("key") + current_period_key = columns[column_idx].get("key") + current_period_value = data_copy[row_idx].get(current_period_key) + previous_period_value = data_copy[row_idx].get(previous_period_key) + annual_growth = 0 + + if current_period_value is None: + data[row_idx][current_period_key] = None + continue + + if previous_period_value == 0 and current_period_value > 0: + annual_growth = 1 + + elif previous_period_value > 0: + annual_growth = (current_period_value - previous_period_value) / previous_period_value + + growth_percent = round(annual_growth * 100, 2) + + data[row_idx][current_period_key] = growth_percent + + +def compute_margin_view_data(data, columns, accumulated_values): + if not columns: + return + + if not accumulated_values: + columns.append({"key": "total"}) + + data_copy = copy.deepcopy(data) + + base_row = None + for row in data_copy: + if row.get("account_name") == _("Income"): + base_row = row + break + + if not base_row: + return + + for row_idx in range(len(data_copy)): + # Taking the total income from each column (for all the financial years) as the base (100%) + row = data_copy[row_idx] + if not row: + continue + + for column in columns: + curr_period = column.get("key") + base_value = base_row[curr_period] + curr_value = row[curr_period] + + if curr_value is None or base_value <= 0: + data[row_idx][curr_period] = None + continue + + margin_percent = round((curr_value / base_value) * 100, 2) + + data[row_idx][curr_period] = margin_percent diff --git a/erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.py b/erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.py index da50668f630..bcdda154202 100644 --- a/erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.py +++ b/erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.py @@ -7,6 +7,8 @@ from frappe import _ from frappe.utils import flt from erpnext.accounts.report.financial_statements import ( + compute_growth_view_data, + compute_margin_view_data, get_columns, get_data, get_filtered_list_for_consolidated_report, @@ -68,6 +70,12 @@ def execute(filters=None): period_list, filters.periodicity, income, expense, net_profit_loss, currency, filters ) + if filters.get("selected_view") == "Growth": + compute_growth_view_data(data, period_list) + + if filters.get("selected_view") == "Margin": + compute_margin_view_data(data, period_list, filters.accumulated_values) + return columns, data, None, chart, report_summary, primitive_summary diff --git a/erpnext/public/js/financial_statements.js b/erpnext/public/js/financial_statements.js index 25620de834d..a23a841dbfa 100644 --- a/erpnext/public/js/financial_statements.js +++ b/erpnext/public/js/financial_statements.js @@ -9,40 +9,29 @@ erpnext.financial_statements = { data && column.colIndex >= 3 ) { - //Assuming that the first three columns are s.no, account name and the very first year of the accounting values, to calculate the relative percentage values of the successive columns. - const lastAnnualValue = row[column.colIndex - 1].content; - const currentAnnualvalue = data[column.fieldname]; - if (currentAnnualvalue == undefined) return "NA"; //making this not applicable for undefined/null values - let annualGrowth = 0; - if (lastAnnualValue == 0 && currentAnnualvalue > 0) { - //If the previous year value is 0 and the current value is greater than 0 - annualGrowth = 1; - } else if (lastAnnualValue > 0) { - annualGrowth = (currentAnnualvalue - lastAnnualValue) / lastAnnualValue; - } + const growthPercent = data[column.fieldname]; - const growthPercent = Math.round(annualGrowth * 10000) / 100; //calculating the rounded off percentage + if (growthPercent == undefined) return "NA"; //making this not applicable for undefined/null values - value = $(`${(growthPercent >= 0 ? "+" : "") + growthPercent + "%"}`); - if (growthPercent < 0) { - value = $(value).addClass("text-danger"); + if (column.fieldname === "total") { + value = $(`${growthPercent}`); } else { - value = $(value).addClass("text-success"); + value = $(`${(growthPercent >= 0 ? "+" : "") + growthPercent + "%"}`); + + if (growthPercent < 0) { + value = $(value).addClass("text-danger"); + } else { + value = $(value).addClass("text-success"); + } } value = $(value).wrap("

").parent().html(); return value; } else if (frappe.query_report.get_filter_value("selected_view") == "Margin" && data) { - if (column.fieldname == "account" && data.account_name == __("Income")) { - //Taking the total income from each column (for all the financial years) as the base (100%) - this.baseData = row; - } if (column.colIndex >= 2) { - //Assuming that the first two columns are s.no and account name, to calculate the relative percentage values of the successive columns. - const currentAnnualvalue = data[column.fieldname]; - const baseValue = this.baseData[column.colIndex].content; - if (currentAnnualvalue == undefined || baseValue <= 0) return "NA"; - const marginPercent = Math.round((currentAnnualvalue / baseValue) * 10000) / 100; + const marginPercent = data[column.fieldname]; + + if (marginPercent == undefined) return "NA"; //making this not applicable for undefined/null values value = $(`${marginPercent + "%"}`); if (marginPercent < 0) value = $(value).addClass("text-danger"); From e2728db5c2a765bb2ee86fa006e9297717f12a76 Mon Sep 17 00:00:00 2001 From: Abdeali Chharchhoda Date: Sat, 23 Nov 2024 14:47:31 +0530 Subject: [PATCH 05/26] refactor: Used object to get payment request status indicator (cherry picked from commit e1c4d6e1e666ee539caf746ed303003ac89f8024) # Conflicts: # erpnext/accounts/doctype/payment_request/payment_request_list.js --- .../payment_request/payment_request_list.js | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/erpnext/accounts/doctype/payment_request/payment_request_list.js b/erpnext/accounts/doctype/payment_request/payment_request_list.js index 183ca7c4584..a1e1549e9d9 100644 --- a/erpnext/accounts/doctype/payment_request/payment_request_list.js +++ b/erpnext/accounts/doctype/payment_request/payment_request_list.js @@ -1,6 +1,17 @@ +const INDICATORS = { + "Partially Paid": "orange", + Cancelled: "red", + Draft: "gray", + Failed: "red", + Initiated: "green", + Paid: "blue", + Requested: "green", +}; + frappe.listview_settings["Payment Request"] = { add_fields: ["status"], get_indicator: function (doc) { +<<<<<<< HEAD if (doc.status == "Draft") { return [__("Draft"), "gray", "status,=,Draft"]; } @@ -15,5 +26,8 @@ frappe.listview_settings["Payment Request"] = { } else if (doc.status == "Cancelled") { return [__("Cancelled"), "red", "status,=,Cancelled"]; } +======= + return [__(doc.status), INDICATORS[doc.status] || "gray", `status,=,${doc.status}`]; +>>>>>>> e1c4d6e1e6 (refactor: Used object to get payment request status indicator) }, }; From c911a70a84eecd7f9f4c25468f9342a18757c1c7 Mon Sep 17 00:00:00 2001 From: Abdeali Chharchhoda Date: Sat, 23 Nov 2024 15:10:23 +0530 Subject: [PATCH 06/26] fix: Dashboard for `Payment Request` (cherry picked from commit 91955e27c38338ff72ae4d19d8f7e26880c5eb3a) --- .../payment_request/payment_request_dashboard.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 erpnext/accounts/doctype/payment_request/payment_request_dashboard.py diff --git a/erpnext/accounts/doctype/payment_request/payment_request_dashboard.py b/erpnext/accounts/doctype/payment_request/payment_request_dashboard.py new file mode 100644 index 00000000000..02ad5684792 --- /dev/null +++ b/erpnext/accounts/doctype/payment_request/payment_request_dashboard.py @@ -0,0 +1,14 @@ +from frappe import _ + + +def get_data(): + return { + "fieldname": "payment_request", + "internal_links": { + "Payment Entry": ["references", "payment_request"], + "Payment Order": ["references", "payment_order"], + }, + "transactions": [ + {"label": _("Payment"), "items": ["Payment Entry", "Payment Order"]}, + ], + } From 9baaaca92455765115610c2cfbd0816963bae605 Mon Sep 17 00:00:00 2001 From: Abdeali Chharchhoda Date: Tue, 26 Nov 2024 12:10:05 +0530 Subject: [PATCH 07/26] revert: remove default `Payment Request` indicator color (cherry picked from commit 37ceb09955c4e5a12714d6422c6092a271382e4f) # Conflicts: # erpnext/accounts/doctype/payment_request/payment_request_list.js --- .../doctype/payment_request/payment_request_list.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/erpnext/accounts/doctype/payment_request/payment_request_list.js b/erpnext/accounts/doctype/payment_request/payment_request_list.js index a1e1549e9d9..61dae1451f9 100644 --- a/erpnext/accounts/doctype/payment_request/payment_request_list.js +++ b/erpnext/accounts/doctype/payment_request/payment_request_list.js @@ -11,6 +11,7 @@ const INDICATORS = { frappe.listview_settings["Payment Request"] = { add_fields: ["status"], get_indicator: function (doc) { +<<<<<<< HEAD <<<<<<< HEAD if (doc.status == "Draft") { return [__("Draft"), "gray", "status,=,Draft"]; @@ -29,5 +30,10 @@ frappe.listview_settings["Payment Request"] = { ======= return [__(doc.status), INDICATORS[doc.status] || "gray", `status,=,${doc.status}`]; >>>>>>> e1c4d6e1e6 (refactor: Used object to get payment request status indicator) +======= + if (!doc.status || !INDICATORS[doc.status]) return; + + return [__(doc.status), INDICATORS[doc.status], `status,=,${doc.status}`]; +>>>>>>> 37ceb09955 (revert: remove default `Payment Request` indicator color) }, }; From eb809847f16719b53ecd94e0b2bb98f972c2ebec Mon Sep 17 00:00:00 2001 From: Abdeali Chharchhoda Date: Wed, 27 Nov 2024 15:52:45 +0530 Subject: [PATCH 08/26] refactor: Move `PR` link filters to client side (cherry picked from commit 2db2c8bce1c1f453818e7e693ded0c0eec8053ec) --- erpnext/accounts/doctype/payment_entry/payment_entry.js | 4 ++++ .../accounts/doctype/payment_request/payment_request.py | 7 +------ 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js index bbbbe0b6f9d..5da1c0d35d9 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.js +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js @@ -165,6 +165,10 @@ frappe.ui.form.on('Payment Entry', { filters: { reference_doctype: row.reference_doctype, reference_name: row.reference_name, + company: doc.company, + status: ["!=", "Paid"], + outstanding_amount: [">", 0], // for compatibility with old data + docstatus: 1, }, }; }); diff --git a/erpnext/accounts/doctype/payment_request/payment_request.py b/erpnext/accounts/doctype/payment_request/payment_request.py index d949f7857c1..68055546c24 100644 --- a/erpnext/accounts/doctype/payment_request/payment_request.py +++ b/erpnext/accounts/doctype/payment_request/payment_request.py @@ -848,12 +848,7 @@ def get_open_payment_requests_query(doctype, txt, searchfield, start, page_len, open_payment_requests = frappe.get_list( "Payment Request", - filters={ - **filters, - "status": ["!=", "Paid"], - "outstanding_amount": ["!=", 0], # for compatibility with old data - "docstatus": 1, - }, + filters=filters, fields=["name", "grand_total", "outstanding_amount"], order_by="transaction_date ASC,creation ASC", ) From 7e847f27ddf6b2c198b535a81cb51ad909ddcd5a Mon Sep 17 00:00:00 2001 From: Abdeali Chharchhoda Date: Wed, 27 Nov 2024 17:18:10 +0530 Subject: [PATCH 09/26] fix: Add filter for `outstanding_amount` to fetch open PRs (cherry picked from commit 214dfab2697ec13e70262cb4af92ca812a2dcb80) --- 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 1fda91185a7..5224db65f03 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -2605,6 +2605,7 @@ def get_open_payment_requests_for_references(references=None): .where(Tuple(PR.reference_doctype, PR.reference_name).isin(list(refs))) .where(PR.status != "Paid") .where(PR.docstatus == 1) + .where(PR.outstanding_amount > 0) # to avoid old PRs with 0 outstanding amount .orderby(Coalesce(PR.transaction_date, PR.creation), order=frappe.qb.asc) ).run(as_dict=True) From a8ef90b40ce83d2fce21466422dad665a6ed8939 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 29 Nov 2024 14:51:46 +0530 Subject: [PATCH 10/26] chore: resolve conflict --- .../payment_request/payment_request_list.js | 21 ------------------- 1 file changed, 21 deletions(-) diff --git a/erpnext/accounts/doctype/payment_request/payment_request_list.js b/erpnext/accounts/doctype/payment_request/payment_request_list.js index 61dae1451f9..1027385aaaf 100644 --- a/erpnext/accounts/doctype/payment_request/payment_request_list.js +++ b/erpnext/accounts/doctype/payment_request/payment_request_list.js @@ -11,29 +11,8 @@ const INDICATORS = { frappe.listview_settings["Payment Request"] = { add_fields: ["status"], get_indicator: function (doc) { -<<<<<<< HEAD -<<<<<<< HEAD - if (doc.status == "Draft") { - return [__("Draft"), "gray", "status,=,Draft"]; - } - if (doc.status == "Requested") { - return [__("Requested"), "green", "status,=,Requested"]; - } else if (doc.status == "Initiated") { - return [__("Initiated"), "green", "status,=,Initiated"]; - } else if (doc.status == "Partially Paid") { - return [__("Partially Paid"), "orange", "status,=,Partially Paid"]; - } else if (doc.status == "Paid") { - return [__("Paid"), "blue", "status,=,Paid"]; - } else if (doc.status == "Cancelled") { - return [__("Cancelled"), "red", "status,=,Cancelled"]; - } -======= - return [__(doc.status), INDICATORS[doc.status] || "gray", `status,=,${doc.status}`]; ->>>>>>> e1c4d6e1e6 (refactor: Used object to get payment request status indicator) -======= if (!doc.status || !INDICATORS[doc.status]) return; return [__(doc.status), INDICATORS[doc.status], `status,=,${doc.status}`]; ->>>>>>> 37ceb09955 (revert: remove default `Payment Request` indicator color) }, }; From de5b253215e8bb1b1dca1c8e62124e7d5b19b171 Mon Sep 17 00:00:00 2001 From: Abdeali Chharchhoda Date: Wed, 27 Nov 2024 16:53:50 +0530 Subject: [PATCH 11/26] fix: Add translation for showing mandatory fields in error msg (cherry picked from commit f42ec6a124d272dec0ac9671b7eef13f55d81de2) --- erpnext/selling/doctype/quotation/quotation.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/erpnext/selling/doctype/quotation/quotation.py b/erpnext/selling/doctype/quotation/quotation.py index bd23440aaf0..48ff2756253 100644 --- a/erpnext/selling/doctype/quotation/quotation.py +++ b/erpnext/selling/doctype/quotation/quotation.py @@ -455,7 +455,9 @@ def _make_customer(source_name, ignore_permissions=False): raise except frappe.MandatoryError as e: mandatory_fields = e.args[0].split(":")[1].split(",") - mandatory_fields = [customer.meta.get_label(field.strip()) for field in mandatory_fields] + mandatory_fields = [ + _(customer.meta.get_label(field.strip())) for field in mandatory_fields + ] frappe.local.message_log = [] lead_link = frappe.utils.get_link_to_form("Lead", lead_name) From cbce4e72d9f5b4e547c5b7c42c084fe93db26b17 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Fri, 29 Nov 2024 18:13:25 +0530 Subject: [PATCH 12/26] fix: source warehouse not set in required items of WO (backport #44426) (#44433) * fix: source warehouse not set in required items of WO (#44426) fix: source warehouse not set in required items of WO on data import (cherry picked from commit 4050ea07eb7c45b3a9babe9472faf2fa89f47a9c) # Conflicts: # erpnext/manufacturing/doctype/work_order/work_order.py * chore: fix conflicts --------- Co-authored-by: rohitwaghchaure --- erpnext/manufacturing/doctype/work_order/work_order.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py index 6bb03310389..5971587da56 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.py +++ b/erpnext/manufacturing/doctype/work_order/work_order.py @@ -89,10 +89,18 @@ class WorkOrder(Document): self.status = self.get_status() self.validate_workstation_type() + if self.source_warehouse: + self.set_warehouses() + validate_uom_is_integer(self, "stock_uom", ["qty", "produced_qty"]) self.set_required_items(reset_only_qty=len(self.get("required_items"))) + def set_warehouses(self): + for row in self.required_items: + if not row.source_warehouse: + row.source_warehouse = self.source_warehouse + def validate_workstation_type(self): for row in self.operations: if not row.workstation and not row.workstation_type: From df0dac56104bd8ec2c5e60e130bf838a43519461 Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Fri, 29 Nov 2024 22:51:32 +0530 Subject: [PATCH 13/26] perf: cache product bundle items at document level (#44440) (cherry picked from commit 6de7320ef40f218cf6aa3b01d0dc1c0c431e68ed) --- erpnext/controllers/selling_controller.py | 32 ++++++++++++++++++----- 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py index cba38e46b8c..44fb15e89be 100644 --- a/erpnext/controllers/selling_controller.py +++ b/erpnext/controllers/selling_controller.py @@ -365,12 +365,32 @@ class SellingController(StockController): return il def has_product_bundle(self, item_code): - product_bundle = frappe.qb.DocType("Product Bundle") - return ( - frappe.qb.from_(product_bundle) - .select(product_bundle.name) - .where((product_bundle.new_item_code == item_code) & (product_bundle.disabled == 0)) - ).run() + product_bundle_items = getattr(self, "_product_bundle_items", None) + if product_bundle_items is None: + self._product_bundle_items = product_bundle_items = {} + + if item_code not in product_bundle_items: + self._fetch_product_bundle_items(item_code) + + return product_bundle_items[item_code] + + def _fetch_product_bundle_items(self, item_code): + product_bundle_items = self._product_bundle_items + items_to_fetch = {row.item_code for row in self.items if row.item_code not in product_bundle_items} + # fetch for requisite item_code even if it is not in items + items_to_fetch.add(item_code) + + items_with_product_bundle = { + row.new_item_code + for row in frappe.get_all( + "Product Bundle", + filters={"new_item_code": ("in", items_to_fetch), "disabled": 0}, + fields="new_item_code", + ) + } + + for item_code in items_to_fetch: + product_bundle_items[item_code] = item_code in items_with_product_bundle def get_already_delivered_qty(self, current_docname, so, so_detail): delivered_via_dn = frappe.db.sql( From d7caa8d51b4071e491663f12a13cfd5d055bf960 Mon Sep 17 00:00:00 2001 From: Abdeali Chharchhoda Date: Fri, 29 Nov 2024 18:13:03 +0530 Subject: [PATCH 14/26] fix: added fieldname to avoid fieldname to translate (cherry picked from commit b80022133c0043d2ccfdcfef8770c2970adc9f0c) --- .../report/accounts_receivable/accounts_receivable.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py index a5c90a37d4d..ffa1da3653c 100644 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py @@ -1004,7 +1004,7 @@ class ReceivablePayableReport: def get_columns(self): self.columns = [] - self.add_column(_("Posting Date"), fieldtype="Date") + self.add_column(_("Posting Date"), fieldname="posting_date", fieldtype="Date") self.add_column( label=_("Party Type"), fieldname="party_type", @@ -1057,7 +1057,7 @@ class ReceivablePayableReport: width=180, ) - self.add_column(label=_("Due Date"), fieldtype="Date") + self.add_column(label=_("Due Date"), fieldname="due_date", fieldtype="Date") if self.account_type == "Payable": self.add_column(label=_("Bill No"), fieldname="bill_no", fieldtype="Data") From f884cf8a5e36544665564a0e25159bdbad6c9b74 Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Sat, 30 Nov 2024 00:11:03 +0530 Subject: [PATCH 15/26] perf: reduce queries during transaction save (cherry picked from commit b6b8a06fda0ffaffe3e163af77772520d8c3be1c) --- erpnext/accounts/party.py | 14 +++++++------- erpnext/controllers/selling_controller.py | 8 +------- erpnext/utilities/transaction_base.py | 10 +++++----- 3 files changed, 13 insertions(+), 19 deletions(-) diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py index a19eedd8b72..ceb3a7f784f 100644 --- a/erpnext/accounts/party.py +++ b/erpnext/accounts/party.py @@ -29,6 +29,12 @@ from erpnext.accounts.utils import get_fiscal_year from erpnext.exceptions import InvalidAccountCurrency, PartyDisabled, PartyFrozen from erpnext.utilities.regional import temporary_flag +try: + from frappe.contacts.doctype.address.address import render_address as _render_address +except ImportError: + # Older frappe versions where this function is not available + from frappe.contacts.doctype.address.address import get_address_display as _render_address + PURCHASE_TRANSACTION_TYPES = { "Supplier Quotation", "Purchase Order", @@ -985,10 +991,4 @@ def add_party_account(party_type, party, company, account): def render_address(address, check_permissions=True): - try: - from frappe.contacts.doctype.address.address import render_address as _render - except ImportError: - # Older frappe versions where this function is not available - from frappe.contacts.doctype.address.address import get_address_display as _render - - return frappe.call(_render, address, check_permissions=check_permissions) + return frappe.call(_render_address, address, check_permissions=check_permissions) diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py index 44fb15e89be..1cd98508e0b 100644 --- a/erpnext/controllers/selling_controller.py +++ b/erpnext/controllers/selling_controller.py @@ -68,19 +68,13 @@ class SellingController(StockController): if customer: from erpnext.accounts.party import _get_party_details - fetch_payment_terms_template = False - if self.get("__islocal") or self.company != frappe.db.get_value( - self.doctype, self.name, "company" - ): - fetch_payment_terms_template = True - party_details = _get_party_details( customer, ignore_permissions=self.flags.ignore_permissions, doctype=self.doctype, company=self.company, posting_date=self.get("posting_date"), - fetch_payment_terms_template=fetch_payment_terms_template, + fetch_payment_terms_template=self.has_value_changed("company"), party_address=self.customer_address, shipping_address=self.shipping_address_name, company_address=self.get("company_address"), diff --git a/erpnext/utilities/transaction_base.py b/erpnext/utilities/transaction_base.py index 3b7812f96c2..fe0d1f84ad3 100644 --- a/erpnext/utilities/transaction_base.py +++ b/erpnext/utilities/transaction_base.py @@ -194,11 +194,11 @@ def validate_uom_is_integer(doc, uom_field, qty_fields, child_dt=None): if isinstance(qty_fields, str): qty_fields = [qty_fields] - distinct_uoms = list(set(d.get(uom_field) for d in doc.get_all_children())) - integer_uoms = list( - filter( - lambda uom: frappe.db.get_value("UOM", uom, "must_be_whole_number", cache=True) or None, - distinct_uoms, + distinct_uoms = tuple(set(uom for uom in (d.get(uom_field) for d in doc.get_all_children()) if uom)) + integer_uoms = set( + d[0] + for d in frappe.db.get_values( + "UOM", (("name", "in", distinct_uoms), ("must_be_whole_number", "=", 1)), cache=True ) ) From 2061c7ca4668db77d5dde58317626990a84204a5 Mon Sep 17 00:00:00 2001 From: Abdeali Chharchhoda Date: Fri, 29 Nov 2024 17:42:11 +0530 Subject: [PATCH 16/26] fix: Added translation for `Account` column (cherry picked from commit de6cbd382f7ff93adff53ad598c2d76361fab4a5) --- .../accounts/report/accounts_receivable/accounts_receivable.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py index ffa1da3653c..e1c91f0f564 100644 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py @@ -1019,7 +1019,7 @@ class ReceivablePayableReport: width=180, ) self.add_column( - label=self.account_type + " Account", + label=_(self.account_type + " Account"), fieldname="party_account", fieldtype="Link", options="Account", From c4103d26be02b8670a56241fd831dd8fe229dda3 Mon Sep 17 00:00:00 2001 From: Abdeali Chharchhoda Date: Mon, 2 Dec 2024 11:23:10 +0530 Subject: [PATCH 17/26] fix: Translate `Party Account` column label (cherry picked from commit a4f8315602ad5ebfd3805b986f7186648d05313c) --- .../report/accounts_receivable/accounts_receivable.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py index e1c91f0f564..7868b06fe33 100644 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py @@ -1018,8 +1018,15 @@ class ReceivablePayableReport: options="party_type", width=180, ) + if self.account_type == "Receivable": + label = _("Receivable Account") + elif self.account_type == "Payable": + label = _("Payable Account") + else: + label = _("Party Account") + self.add_column( - label=_(self.account_type + " Account"), + label=label, fieldname="party_account", fieldtype="Link", options="Account", From bed6dabecb017564d29c11fb0f5dba7089170b25 Mon Sep 17 00:00:00 2001 From: ljain112 Date: Thu, 28 Nov 2024 19:50:01 +0530 Subject: [PATCH 18/26] fix: handle multi currency in common party journal entry (cherry picked from commit e371f68d66700a1641bb36da533385682feac076) # Conflicts: # erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py --- .../sales_invoice/test_sales_invoice.py | 92 +++++++++++++++++++ erpnext/controllers/accounts_controller.py | 67 ++++++++++++-- 2 files changed, 149 insertions(+), 10 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 9b303430455..eb383beee78 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -3760,6 +3760,98 @@ class TestSalesInvoice(FrappeTestCase): self.assertTrue(jv) self.assertEqual(jv[0], si.grand_total) +<<<<<<< HEAD +======= + @IntegrationTestCase.change_settings("Accounts Settings", {"enable_common_party_accounting": True}) + def test_common_party_with_different_currency_in_debtor_and_creditor(self): + from erpnext.accounts.doctype.account.test_account import create_account + from erpnext.accounts.doctype.opening_invoice_creation_tool.test_opening_invoice_creation_tool import ( + make_customer, + ) + from erpnext.accounts.doctype.party_link.party_link import create_party_link + from erpnext.buying.doctype.supplier.test_supplier import create_supplier + from erpnext.setup.utils import get_exchange_rate + + creditors = create_account( + account_name="Creditors INR", + parent_account="Accounts Payable - _TC", + company="_Test Company", + account_currency="INR", + account_type="Payable", + ) + debtors = create_account( + account_name="Debtors USD", + parent_account="Accounts Receivable - _TC", + company="_Test Company", + account_currency="USD", + account_type="Receivable", + ) + + # create a customer + customer = make_customer(customer="_Test Common Party USD") + cust_doc = frappe.get_doc("Customer", customer) + cust_doc.default_currency = "USD" + test_account_details = { + "company": "_Test Company", + "account": debtors, + } + cust_doc.append("accounts", test_account_details) + cust_doc.save() + + # create a supplier + supplier = create_supplier(supplier_name="_Test Common Party INR").name + supp_doc = frappe.get_doc("Supplier", supplier) + supp_doc.default_currency = "INR" + test_account_details = { + "company": "_Test Company", + "account": creditors, + } + supp_doc.append("accounts", test_account_details) + supp_doc.save() + + # create a party link between customer & supplier + create_party_link("Supplier", supplier, customer) + + # create a sales invoice + si = create_sales_invoice( + customer=customer, + currency="USD", + conversion_rate=get_exchange_rate("USD", "INR"), + debit_to=debtors, + do_not_save=1, + ) + si.party_account_currency = "USD" + si.save() + si.submit() + + # check outstanding of sales invoice + si.reload() + self.assertEqual(si.status, "Paid") + self.assertEqual(flt(si.outstanding_amount), 0.0) + + # check creation of journal entry + jv = frappe.get_all( + "Journal Entry Account", + { + "account": si.debit_to, + "party_type": "Customer", + "party": si.customer, + "reference_type": si.doctype, + "reference_name": si.name, + }, + pluck="credit_in_account_currency", + ) + self.assertTrue(jv) + self.assertEqual(jv[0], si.grand_total) + + def test_invoice_remarks(self): + si = frappe.copy_doc(self.globalTestRecords["Sales Invoice"][0]) + si.po_no = "Test PO" + si.po_date = nowdate() + si.save() + si.submit() + self.assertEqual(si.remarks, f"Against Customer Order Test PO dated {format_date(nowdate())}") +>>>>>>> e371f68d66 (fix: handle multi currency in common party journal entry) def check_gl_entries(doc, voucher_no, expected_gle, posting_date): gl_entries = frappe.db.sql( diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index e7d8bc9ddd4..6a5914ddbe0 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -2313,6 +2313,12 @@ class AccountsController(TransactionBase): secondary_account = get_party_account(secondary_party_type, secondary_party, self.company) primary_account_currency = get_account_currency(primary_account) secondary_account_currency = get_account_currency(secondary_account) + default_currency = erpnext.get_company_currency(self.company) + + # Determine if multi-currency journal entry is needed + multi_currency = ( + primary_account_currency != default_currency or secondary_account_currency != default_currency + ) jv = frappe.new_doc("Journal Entry") jv.voucher_type = "Journal Entry" @@ -2337,7 +2343,7 @@ class AccountsController(TransactionBase): advance_entry.cost_center = self.cost_center or erpnext.get_default_cost_center(self.company) advance_entry.is_advance = "Yes" - # update dimesions + # Update dimensions dimensions_dict = frappe._dict() active_dimensions = get_dimensions()[0] for dim in active_dimensions: @@ -2346,17 +2352,58 @@ class AccountsController(TransactionBase): reconcilation_entry.update(dimensions_dict) advance_entry.update(dimensions_dict) - if self.doctype == "Sales Invoice": - reconcilation_entry.credit_in_account_currency = self.outstanding_amount - advance_entry.debit_in_account_currency = self.outstanding_amount + # Calculate exchange rates if necessary + if multi_currency: + # Exchange rates for primary and secondary accounts + exc_rate_primary_to_default = ( + 1 + if primary_account_currency == default_currency + else get_exchange_rate(primary_account_currency, default_currency, self.posting_date) + ) + exc_rate_secondary_to_default = ( + 1 + if secondary_account_currency == default_currency + else get_exchange_rate(secondary_account_currency, default_currency, self.posting_date) + ) + exc_rate_secondary_to_primary = ( + 1 + if secondary_account_currency == primary_account_currency + else get_exchange_rate( + secondary_account_currency, primary_account_currency, self.posting_date + ) + ) + + # Convert outstanding amount from secondary to primary account currency, if needed + + os_in_default_currency = self.outstanding_amount * exc_rate_secondary_to_default + os_in_primary_currency = self.outstanding_amount * exc_rate_secondary_to_primary + + if self.doctype == "Sales Invoice": + # Calculate credit and debit values for reconciliation and advance entries + reconcilation_entry.credit_in_account_currency = self.outstanding_amount + reconcilation_entry.credit = os_in_default_currency + + advance_entry.debit_in_account_currency = os_in_primary_currency + advance_entry.debit = os_in_default_currency + else: + advance_entry.credit_in_account_currency = os_in_primary_currency + advance_entry.credit = os_in_default_currency + + reconcilation_entry.debit_in_account_currency = self.outstanding_amount + reconcilation_entry.debit = os_in_default_currency + + # Set exchange rates for entries + reconcilation_entry.exchange_rate = exc_rate_secondary_to_default + advance_entry.exchange_rate = exc_rate_primary_to_default else: - advance_entry.credit_in_account_currency = self.outstanding_amount - reconcilation_entry.debit_in_account_currency = self.outstanding_amount - - default_currency = erpnext.get_company_currency(self.company) - if primary_account_currency != default_currency or secondary_account_currency != default_currency: - jv.multi_currency = 1 + if self.doctype == "Sales Invoice": + reconcilation_entry.credit_in_account_currency = self.outstanding_amount + advance_entry.debit_in_account_currency = self.outstanding_amount + else: + advance_entry.credit_in_account_currency = self.outstanding_amount + reconcilation_entry.debit_in_account_currency = self.outstanding_amount + jv.multi_currency = multi_currency jv.append("accounts", reconcilation_entry) jv.append("accounts", advance_entry) From 1988c2353992aaf76225414d5f3b00ababecc899 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 2 Dec 2024 13:42:27 +0530 Subject: [PATCH 19/26] chore: resolve conflict --- .../doctype/sales_invoice/test_sales_invoice.py | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index eb383beee78..f4b9349c577 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -3760,9 +3760,7 @@ class TestSalesInvoice(FrappeTestCase): self.assertTrue(jv) self.assertEqual(jv[0], si.grand_total) -<<<<<<< HEAD -======= - @IntegrationTestCase.change_settings("Accounts Settings", {"enable_common_party_accounting": True}) + @change_settings("Accounts Settings", {"enable_common_party_accounting": True}) def test_common_party_with_different_currency_in_debtor_and_creditor(self): from erpnext.accounts.doctype.account.test_account import create_account from erpnext.accounts.doctype.opening_invoice_creation_tool.test_opening_invoice_creation_tool import ( @@ -3844,14 +3842,6 @@ class TestSalesInvoice(FrappeTestCase): self.assertTrue(jv) self.assertEqual(jv[0], si.grand_total) - def test_invoice_remarks(self): - si = frappe.copy_doc(self.globalTestRecords["Sales Invoice"][0]) - si.po_no = "Test PO" - si.po_date = nowdate() - si.save() - si.submit() - self.assertEqual(si.remarks, f"Against Customer Order Test PO dated {format_date(nowdate())}") ->>>>>>> e371f68d66 (fix: handle multi currency in common party journal entry) def check_gl_entries(doc, voucher_no, expected_gle, posting_date): gl_entries = frappe.db.sql( From 5533592b2b85afdcf66b4b3289248f7580c3672a Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Mon, 2 Dec 2024 14:22:41 +0530 Subject: [PATCH 20/26] fix: incorrect Gross Margin on project (#44461) (cherry picked from commit 7de9c14a2ce329ea6134fddf5c13424bc5191657) # Conflicts: # erpnext/accounts/doctype/sales_invoice/sales_invoice.py # erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py --- .../doctype/sales_invoice/sales_invoice.py | 9 ++++ .../sales_invoice/test_sales_invoice.py | 45 +++++++++++++++++++ 2 files changed, 54 insertions(+) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index ff81a114d54..f9197851fad 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -1556,8 +1556,17 @@ class SalesInvoice(SellingController): ) def update_project(self): +<<<<<<< HEAD if self.project: project = frappe.get_doc("Project", self.project) +======= + unique_projects = list(set([d.project for d in self.get("items") if d.project])) + if self.project and self.project not in unique_projects: + unique_projects.append(self.project) + + for p in unique_projects: + project = frappe.get_doc("Project", p) +>>>>>>> 7de9c14a2c (fix: incorrect Gross Margin on project (#44461)) project.update_billed_amount() project.db_update() diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index f4b9349c577..134298dc7f9 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -3843,6 +3843,7 @@ class TestSalesInvoice(FrappeTestCase): self.assertEqual(jv[0], si.grand_total) +<<<<<<< HEAD def check_gl_entries(doc, voucher_no, expected_gle, posting_date): gl_entries = frappe.db.sql( """select account, debit, credit, posting_date @@ -3852,6 +3853,50 @@ def check_gl_entries(doc, voucher_no, expected_gle, posting_date): order by posting_date asc, account asc""", (voucher_no, posting_date), as_dict=1, +======= + def test_gl_voucher_subtype(self): + si = create_sales_invoice() + gl_entries = frappe.get_all( + "GL Entry", + filters={"voucher_type": "Sales Invoice", "voucher_no": si.name}, + pluck="voucher_subtype", + ) + + self.assertTrue(all([x == "Sales Invoice" for x in gl_entries])) + + si = create_sales_invoice(is_return=1, qty=-1) + gl_entries = frappe.get_all( + "GL Entry", + filters={"voucher_type": "Sales Invoice", "voucher_no": si.name}, + pluck="voucher_subtype", + ) + + self.assertTrue(all([x == "Credit Note" for x in gl_entries])) + + def test_total_billed_amount(self): + si = create_sales_invoice(do_not_submit=True) + + project = frappe.new_doc("Project") + project.project_name = "Test Total Billed Amount" + project.save() + + si.project = project.name + si.save() + si.submit() + + doc = frappe.get_doc("Project", project.name) + self.assertEqual(doc.total_billed_amount, si.grand_total) + + +def set_advance_flag(company, flag, default_account): + frappe.db.set_value( + "Company", + company, + { + "book_advance_payments_in_separate_party_account": flag, + "default_advance_received_account": default_account, + }, +>>>>>>> 7de9c14a2c (fix: incorrect Gross Margin on project (#44461)) ) for i, gle in enumerate(gl_entries): From d7583e39933756103d7717bb39a12a4e0d5970d3 Mon Sep 17 00:00:00 2001 From: ljain112 Date: Thu, 28 Nov 2024 20:45:17 +0530 Subject: [PATCH 21/26] fix: adjusted incoming rate for zero rated item in purchase receipt (cherry picked from commit 3182c6981c7bb2107b8c2d59b7f0e474f94c0cea) --- .../purchase_invoice/test_purchase_invoice.py | 24 +++++++++++++++++++ .../purchase_receipt/purchase_receipt.py | 2 +- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index 950a7a3eb29..2855d999233 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -1643,6 +1643,30 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin): frappe.db.set_single_value("Buying Settings", "set_landed_cost_based_on_purchase_invoice_rate", 1) + # Cost of Item is zero in Purchase Receipt + pr = make_purchase_receipt(qty=1, rate=0) + + stock_value_difference = frappe.db.get_value( + "Stock Ledger Entry", + {"voucher_type": "Purchase Receipt", "voucher_no": pr.name}, + "stock_value_difference", + ) + self.assertEqual(stock_value_difference, 0) + + pi = create_purchase_invoice_from_receipt(pr.name) + for row in pi.items: + row.rate = 150 + + pi.save() + pi.submit() + + stock_value_difference = frappe.db.get_value( + "Stock Ledger Entry", + {"voucher_type": "Purchase Receipt", "voucher_no": pr.name}, + "stock_value_difference", + ) + self.assertEqual(stock_value_difference, 150) + # Increase the cost of the item pr = make_purchase_receipt(qty=1, rate=100) diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index faf305cfe9c..b293a45580a 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -907,7 +907,7 @@ def update_billing_percentage(pr_doc, update_modified=True, adjust_incoming_rate if adjust_incoming_rate: adjusted_amt = 0.0 - if item.billed_amt and item.amount: + if item.billed_amt is not None and item.amount is not None: adjusted_amt = flt(item.billed_amt) - flt(item.amount) item.db_set("rate_difference_with_purchase_invoice", adjusted_amt, update_modified=False) From 86ff3e0c106fe00c2ae37877a88bfd0261323e37 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Tue, 3 Dec 2024 15:06:16 +0530 Subject: [PATCH 22/26] chore: fix conflicts --- erpnext/accounts/doctype/sales_invoice/sales_invoice.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index f9197851fad..b3ef8e23985 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -1556,17 +1556,12 @@ class SalesInvoice(SellingController): ) def update_project(self): -<<<<<<< HEAD - if self.project: - project = frappe.get_doc("Project", self.project) -======= unique_projects = list(set([d.project for d in self.get("items") if d.project])) if self.project and self.project not in unique_projects: unique_projects.append(self.project) for p in unique_projects: project = frappe.get_doc("Project", p) ->>>>>>> 7de9c14a2c (fix: incorrect Gross Margin on project (#44461)) project.update_billed_amount() project.db_update() From ccd7992e9949d821df605581e1b2b1e1e3089313 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Tue, 3 Dec 2024 15:08:38 +0530 Subject: [PATCH 23/26] chore: fix conflicts --- .../sales_invoice/test_sales_invoice.py | 49 ++++--------------- 1 file changed, 9 insertions(+), 40 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 134298dc7f9..6e4c6002016 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -3842,37 +3842,6 @@ class TestSalesInvoice(FrappeTestCase): self.assertTrue(jv) self.assertEqual(jv[0], si.grand_total) - -<<<<<<< HEAD -def check_gl_entries(doc, voucher_no, expected_gle, posting_date): - gl_entries = frappe.db.sql( - """select account, debit, credit, posting_date - from `tabGL Entry` - where voucher_type='Sales Invoice' and voucher_no=%s and posting_date > %s - and is_cancelled = 0 - order by posting_date asc, account asc""", - (voucher_no, posting_date), - as_dict=1, -======= - def test_gl_voucher_subtype(self): - si = create_sales_invoice() - gl_entries = frappe.get_all( - "GL Entry", - filters={"voucher_type": "Sales Invoice", "voucher_no": si.name}, - pluck="voucher_subtype", - ) - - self.assertTrue(all([x == "Sales Invoice" for x in gl_entries])) - - si = create_sales_invoice(is_return=1, qty=-1) - gl_entries = frappe.get_all( - "GL Entry", - filters={"voucher_type": "Sales Invoice", "voucher_no": si.name}, - pluck="voucher_subtype", - ) - - self.assertTrue(all([x == "Credit Note" for x in gl_entries])) - def test_total_billed_amount(self): si = create_sales_invoice(do_not_submit=True) @@ -3888,15 +3857,15 @@ def check_gl_entries(doc, voucher_no, expected_gle, posting_date): self.assertEqual(doc.total_billed_amount, si.grand_total) -def set_advance_flag(company, flag, default_account): - frappe.db.set_value( - "Company", - company, - { - "book_advance_payments_in_separate_party_account": flag, - "default_advance_received_account": default_account, - }, ->>>>>>> 7de9c14a2c (fix: incorrect Gross Margin on project (#44461)) +def check_gl_entries(doc, voucher_no, expected_gle, posting_date): + gl_entries = frappe.db.sql( + """select account, debit, credit, posting_date + from `tabGL Entry` + where voucher_type='Sales Invoice' and voucher_no=%s and posting_date > %s + and is_cancelled = 0 + order by posting_date asc, account asc""", + (voucher_no, posting_date), + as_dict=1, ) for i, gle in enumerate(gl_entries): From 7d724d7647f13022f9cbc30f792518a3997525eb Mon Sep 17 00:00:00 2001 From: ljain112 Date: Mon, 2 Dec 2024 14:08:21 +0530 Subject: [PATCH 24/26] fix: remove queries (cherry picked from commit a86b223aed6e978154dbe6feb3581bcdacff2d60) --- .../report/gross_profit/gross_profit.py | 29 +++++++------------ 1 file changed, 10 insertions(+), 19 deletions(-) diff --git a/erpnext/accounts/report/gross_profit/gross_profit.py b/erpnext/accounts/report/gross_profit/gross_profit.py index edc0ecbd581..c50804cb3e7 100644 --- a/erpnext/accounts/report/gross_profit/gross_profit.py +++ b/erpnext/accounts/report/gross_profit/gross_profit.py @@ -402,10 +402,10 @@ class GrossProfitGenerator: self.load_invoice_items() self.get_delivery_notes() + self.load_product_bundle() if filters.group_by == "Invoice": self.group_items_by_invoice() - self.load_product_bundle() self.load_non_stock_items() self.get_returned_invoice_items() self.process() @@ -797,6 +797,7 @@ class GrossProfitGenerator: `tabSales Invoice`.project, `tabSales Invoice`.update_stock, `tabSales Invoice`.customer, `tabSales Invoice`.customer_group, `tabSales Invoice`.territory, `tabSales Invoice Item`.item_code, + `tabSales Invoice`.base_net_total as "invoice_base_net_total", `tabSales Invoice Item`.item_name, `tabSales Invoice Item`.description, `tabSales Invoice Item`.warehouse, `tabSales Invoice Item`.item_group, `tabSales Invoice Item`.brand, `tabSales Invoice Item`.so_detail, @@ -857,6 +858,7 @@ class GrossProfitGenerator: """ grouped = OrderedDict() + product_bundels = self.product_bundles.get("Sales Invoice", {}) for row in self.si_list: # initialize list with a header row for each new parent @@ -867,8 +869,7 @@ class GrossProfitGenerator: ) # if item is a bundle, add it's components as seperate rows - if frappe.db.exists("Product Bundle", row.item_code): - bundled_items = self.get_bundle_items(row) + if bundled_items := product_bundels.get(row.parent, {}).get(row.item_code): for x in bundled_items: bundle_item = self.get_bundle_item_row(row, x) grouped.get(row.parent).append(bundle_item) @@ -904,18 +905,11 @@ class GrossProfitGenerator: "item_row": None, "is_return": row.is_return, "cost_center": row.cost_center, - "base_net_amount": frappe.db.get_value("Sales Invoice", row.parent, "base_net_total"), + "base_net_amount": row.invoice_base_net_total, } ) - def get_bundle_items(self, product_bundle): - return frappe.get_all( - "Product Bundle Item", filters={"parent": product_bundle.item_code}, fields=["item_code", "qty"] - ) - def get_bundle_item_row(self, product_bundle, item): - item_name, description, item_group, brand = self.get_bundle_item_details(item.item_code) - return frappe._dict( { "parent_invoice": product_bundle.item_code, @@ -928,23 +922,20 @@ class GrossProfitGenerator: "customer": product_bundle.customer, "customer_group": product_bundle.customer_group, "item_code": item.item_code, - "item_name": item_name, - "description": description, + "item_name": item.item_name, + "description": item.description, "warehouse": product_bundle.warehouse, - "item_group": item_group, - "brand": brand, + "item_group": "", + "brand": "", "dn_detail": product_bundle.dn_detail, "delivery_note": product_bundle.delivery_note, - "qty": (flt(product_bundle.qty) * flt(item.qty)), + "qty": item.total_qty * -1, "item_row": None, "is_return": product_bundle.is_return, "cost_center": product_bundle.cost_center, } ) - def get_bundle_item_details(self, item_code): - return frappe.db.get_value("Item", item_code, ["item_name", "description", "item_group", "brand"]) - def get_stock_ledger_entries(self, item_code, warehouse): if item_code and warehouse: if (item_code, warehouse) not in self.sle: From 91f6393104ace04afae656b54c347b0b205c10da Mon Sep 17 00:00:00 2001 From: ljain112 Date: Mon, 2 Dec 2024 15:20:20 +0530 Subject: [PATCH 25/26] fix: correct buying amount for product bundel (cherry picked from commit 4e6a5893e7a26da863920c782f2719861b5aaecd) --- .../report/gross_profit/gross_profit.py | 36 +++++++++++-------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/erpnext/accounts/report/gross_profit/gross_profit.py b/erpnext/accounts/report/gross_profit/gross_profit.py index c50804cb3e7..8e9f136e602 100644 --- a/erpnext/accounts/report/gross_profit/gross_profit.py +++ b/erpnext/accounts/report/gross_profit/gross_profit.py @@ -617,6 +617,7 @@ class GrossProfitGenerator: if packed_item.get("parent_detail_docname") == row.item_row: packed_item_row = row.copy() packed_item_row.warehouse = packed_item.warehouse + packed_item_row.qty = packed_item.total_qty * -1 buying_amount += self.get_buying_amount(packed_item_row, packed_item.item_code) return flt(buying_amount, self.currency_precision) @@ -649,7 +650,9 @@ class GrossProfitGenerator: else: my_sle = self.get_stock_ledger_entries(item_code, row.warehouse) if (row.update_stock or row.dn_detail) and my_sle: - parenttype, parent = row.parenttype, row.parent + parenttype = row.parenttype + parent = row.invoice or row.parent + if row.dn_detail: parenttype, parent = "Delivery Note", row.delivery_note @@ -909,30 +912,33 @@ class GrossProfitGenerator: } ) - def get_bundle_item_row(self, product_bundle, item): + def get_bundle_item_row(self, row, item): return frappe._dict( { - "parent_invoice": product_bundle.item_code, - "indent": product_bundle.indent + 1, + "parent_invoice": row.item_code, + "parenttype": row.parenttype, + "indent": row.indent + 1, "parent": None, "invoice_or_item": item.item_code, - "posting_date": product_bundle.posting_date, - "posting_time": product_bundle.posting_time, - "project": product_bundle.project, - "customer": product_bundle.customer, - "customer_group": product_bundle.customer_group, + "posting_date": row.posting_date, + "posting_time": row.posting_time, + "project": row.project, + "customer": row.customer, + "customer_group": row.customer_group, "item_code": item.item_code, "item_name": item.item_name, "description": item.description, - "warehouse": product_bundle.warehouse, + "warehouse": item.warehouse or row.warehouse, + "update_stock": row.update_stock, "item_group": "", "brand": "", - "dn_detail": product_bundle.dn_detail, - "delivery_note": product_bundle.delivery_note, + "dn_detail": row.dn_detail, + "delivery_note": row.delivery_note, "qty": item.total_qty * -1, - "item_row": None, - "is_return": product_bundle.is_return, - "cost_center": product_bundle.cost_center, + "item_row": row.item_row, + "is_return": row.is_return, + "cost_center": row.cost_center, + "invoice": row.parent, } ) From a94e3cd433f8dec8aa04672ac525643b110efd01 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 3 Dec 2024 15:39:17 +0530 Subject: [PATCH 26/26] chore: fix typo (cherry picked from commit fc0122ce760f8c2f14de931bf8f082de8cefa02d) --- erpnext/accounts/report/gross_profit/gross_profit.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/report/gross_profit/gross_profit.py b/erpnext/accounts/report/gross_profit/gross_profit.py index 8e9f136e602..833bb78a329 100644 --- a/erpnext/accounts/report/gross_profit/gross_profit.py +++ b/erpnext/accounts/report/gross_profit/gross_profit.py @@ -861,7 +861,7 @@ class GrossProfitGenerator: """ grouped = OrderedDict() - product_bundels = self.product_bundles.get("Sales Invoice", {}) + product_bundles = self.product_bundles.get("Sales Invoice", {}) for row in self.si_list: # initialize list with a header row for each new parent @@ -872,7 +872,7 @@ class GrossProfitGenerator: ) # if item is a bundle, add it's components as seperate rows - if bundled_items := product_bundels.get(row.parent, {}).get(row.item_code): + if bundled_items := product_bundles.get(row.parent, {}).get(row.item_code): for x in bundled_items: bundle_item = self.get_bundle_item_row(row, x) grouped.get(row.parent).append(bundle_item)