From 4556c36736cf79e9861c2feb5e022961e738bbf5 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Wed, 3 May 2023 20:45:12 +0530 Subject: [PATCH 01/25] refactor: limit output to 50 in reconciliation tool (cherry picked from commit 7a381affce02e5aea2c5a9a38ebf2638e8128f9a) # Conflicts: # erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.json --- .../payment_reconciliation.json | 29 +++++++++++++++++++ .../payment_reconciliation.py | 2 ++ erpnext/accounts/utils.py | 23 +++++++++++++-- 3 files changed, 52 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.json b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.json index 18d34850850..c988b36cb01 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.json +++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.json @@ -26,8 +26,10 @@ "bank_cash_account", "cost_center", "sec_break1", + "invoice_name", "invoices", "column_break_15", + "payment_name", "payments", "sec_break2", "allocation" @@ -136,6 +138,7 @@ "label": "Minimum Invoice Amount" }, { + "default": "50", "description": "System will fetch all the entries if limit value is zero.", "fieldname": "invoice_limit", "fieldtype": "Int", @@ -166,6 +169,7 @@ "label": "Maximum Payment Amount" }, { + "default": "50", "description": "System will fetch all the entries if limit value is zero.", "fieldname": "payment_limit", "fieldtype": "Int", @@ -185,13 +189,38 @@ "fieldtype": "Link", "label": "Cost Center", "options": "Cost Center" +<<<<<<< HEAD +======= + }, + { + "depends_on": "eval:doc.party", + "fieldname": "default_advance_account", + "fieldtype": "Link", + "label": "Default Advance Account", + "mandatory_depends_on": "doc.party_type", + "options": "Account" + }, + { + "fieldname": "invoice_name", + "fieldtype": "Data", + "label": "Filter on Invoice" + }, + { + "fieldname": "payment_name", + "fieldtype": "Data", + "label": "Filter on Payment" +>>>>>>> 7a381affce (refactor: limit output to 50 in reconciliation tool) } ], "hide_toolbar": 1, "icon": "icon-resize-horizontal", "issingle": 1, "links": [], +<<<<<<< HEAD "modified": "2022-04-29 15:37:10.246831", +======= + "modified": "2023-08-15 05:35:50.109290", +>>>>>>> 7a381affce (refactor: limit output to 50 in reconciliation tool) "modified_by": "Administrator", "module": "Accounts", "name": "Payment Reconciliation", diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py index cc3ec26066f..6db39cc9991 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py +++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py @@ -210,6 +210,8 @@ class PaymentReconciliation(Document): min_outstanding=self.minimum_invoice_amount if self.minimum_invoice_amount else None, max_outstanding=self.maximum_invoice_amount if self.maximum_invoice_amount else None, accounting_dimensions=self.accounting_dimension_filter_conditions, + limit=self.invoice_limit, + voucher_no=self.invoice_name, ) cr_dr_notes = ( diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index 2df3387b83e..a089a856c04 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -884,7 +884,9 @@ def get_outstanding_invoices( min_outstanding=None, max_outstanding=None, accounting_dimensions=None, - vouchers=None, + vouchers=None, # list of dicts [{'voucher_type': '', 'voucher_no': ''}] for filtering + limit=None, # passed by reconciliation tool + voucher_no=None, # filter passed by reconciliation tool ): ple = qb.DocType("Payment Ledger Entry") @@ -917,6 +919,8 @@ def get_outstanding_invoices( max_outstanding=max_outstanding, get_invoices=True, accounting_dimensions=accounting_dimensions or [], + limit=limit, + voucher_no=voucher_no, ) for d in invoice_list: @@ -1648,12 +1652,13 @@ class QueryPaymentLedger(object): self.voucher_posting_date = [] self.min_outstanding = None self.max_outstanding = None + self.limit = self.voucher_no = None def reset(self): # clear filters self.vouchers.clear() self.common_filter.clear() - self.min_outstanding = self.max_outstanding = None + self.min_outstanding = self.max_outstanding = self.limit = None # clear result self.voucher_outstandings.clear() @@ -1667,6 +1672,7 @@ class QueryPaymentLedger(object): filter_on_voucher_no = [] filter_on_against_voucher_no = [] + if self.vouchers: voucher_types = set([x.voucher_type for x in self.vouchers]) voucher_nos = set([x.voucher_no for x in self.vouchers]) @@ -1677,6 +1683,10 @@ class QueryPaymentLedger(object): filter_on_against_voucher_no.append(ple.against_voucher_type.isin(voucher_types)) filter_on_against_voucher_no.append(ple.against_voucher_no.isin(voucher_nos)) + if self.voucher_no: + filter_on_voucher_no.append(ple.voucher_no.like(f"%{self.voucher_no}%")) + filter_on_against_voucher_no.append(ple.against_voucher_no.like(f"%{self.voucher_no}%")) + # build outstanding amount filter filter_on_outstanding_amount = [] if self.min_outstanding: @@ -1792,6 +1802,11 @@ class QueryPaymentLedger(object): ) ) + if self.limit: + self.cte_query_voucher_amount_and_outstanding = ( + self.cte_query_voucher_amount_and_outstanding.limit(self.limit) + ) + # execute SQL self.voucher_outstandings = self.cte_query_voucher_amount_and_outstanding.run(as_dict=True) @@ -1805,6 +1820,8 @@ class QueryPaymentLedger(object): get_payments=False, get_invoices=False, accounting_dimensions=None, + limit=None, + voucher_no=None, ): """ Fetch voucher amount and outstanding amount from Payment Ledger using Database CTE @@ -1826,6 +1843,8 @@ class QueryPaymentLedger(object): self.max_outstanding = max_outstanding self.get_payments = get_payments self.get_invoices = get_invoices + self.limit = limit + self.voucher_no = voucher_no self.query_for_outstanding() return self.voucher_outstandings From d727a13562d3585e0522071b9e25dfd2e8ca7294 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 4 Jul 2023 08:40:47 +0530 Subject: [PATCH 02/25] refactor: trigger on value change (cherry picked from commit e48f8139ebdd5aa64e3b9139157948a7a17afb61) --- .../payment_reconciliation/payment_reconciliation.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js index 07f35c9fe11..6f1f34bc9f3 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js +++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js @@ -151,6 +151,15 @@ erpnext.accounts.PaymentReconciliationController = class PaymentReconciliationCo this.frm.refresh(); } + invoice_name() { + this.frm.trigger("get_unreconciled_entries"); + } + + payment_name() { + this.frm.trigger("get_unreconciled_entries"); + } + + clear_child_tables() { this.frm.clear_table("invoices"); this.frm.clear_table("payments"); From a8c53eeb931c2fe15bdfe3072c88539be7aeed4b Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 4 Jul 2023 08:49:30 +0530 Subject: [PATCH 03/25] refactor: filter on cr/dr notes (cherry picked from commit 52f609e67a30988e498cf03bc0da7fa946ab3f5d) --- .../payment_reconciliation.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py index 6db39cc9991..258587b9999 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py +++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py @@ -5,6 +5,7 @@ import frappe from frappe import _, msgprint, qb from frappe.model.document import Document +from frappe.query_builder import Criterion from frappe.query_builder.custom import ConstantColumn from frappe.utils import flt, fmt_money, get_link_to_form, getdate, nowdate, today @@ -130,6 +131,15 @@ class PaymentReconciliation(Document): def get_return_invoices(self): voucher_type = "Sales Invoice" if self.party_type == "Customer" else "Purchase Invoice" doc = qb.DocType(voucher_type) + + conditions = [] + conditions.append(doc.docstatus == 1) + conditions.append(doc[frappe.scrub(self.party_type)] == self.party) + conditions.append(doc.is_return == 1) + + if self.payment_name: + conditions.append(doc.name.like(f"%{self.payment_name}%")) + self.return_invoices = ( qb.from_(doc) .select( @@ -137,11 +147,7 @@ class PaymentReconciliation(Document): doc.name.as_("voucher_no"), doc.return_against, ) - .where( - (doc.docstatus == 1) - & (doc[frappe.scrub(self.party_type)] == self.party) - & (doc.is_return == 1) - ) + .where(Criterion.all(conditions)) .run(as_dict=True) ) From c5080abd4648bd03c062bf62ccaf95a6a69d35ce Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 4 Jul 2023 09:02:05 +0530 Subject: [PATCH 04/25] refactor: filter on advance payments (cherry picked from commit 86bac2cf52e6094755e8f21df871a7f56a4d53a5) # Conflicts: # erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py # erpnext/controllers/accounts_controller.py --- .../payment_reconciliation.py | 18 +++++++++ erpnext/controllers/accounts_controller.py | 38 +++++++++++++++++++ 2 files changed, 56 insertions(+) diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py index 258587b9999..051f769fb24 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py +++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py @@ -58,7 +58,25 @@ class PaymentReconciliation(Document): def get_payment_entries(self): order_doctype = "Sales Order" if self.party_type == "Customer" else "Purchase Order" +<<<<<<< HEAD condition = self.get_conditions(get_payments=True) +======= + condition = frappe._dict( + { + "company": self.get("company"), + "get_payments": True, + "cost_center": self.get("cost_center"), + "from_payment_date": self.get("from_payment_date"), + "to_payment_date": self.get("to_payment_date"), + "maximum_payment_amount": self.get("maximum_payment_amount"), + "minimum_payment_amount": self.get("minimum_payment_amount"), + } + ) + + if self.payment_name: + condition.update({"name": self.payment_name}) + +>>>>>>> 86bac2cf52 (refactor: filter on advance payments) payment_entries = get_advance_payment_entries( self.party_type, self.party, diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 76fe6a91182..8b88599c4d1 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -2320,10 +2320,48 @@ def get_advance_payment_entries( payment_entries_against_order, unallocated_payment_entries = [], [] limit_cond = "limit %s" % limit if limit else "" +<<<<<<< HEAD if order_list or against_all_orders: if order_list: reference_condition = " and t2.reference_name in ({0})".format( ", ".join(["%s"] * len(order_list)) +======= + if payment_type == "Receive": + q = q.select((payment_entry.source_exchange_rate).as_("exchange_rate")) + else: + q = q.select((payment_entry.target_exchange_rate).as_("exchange_rate")) + + if condition: + if condition.get("name", None): + q = q.where(payment_entry.name.like(f"%{condition.get('name')}%")) + + q = q.where(payment_entry.company == condition["company"]) + q = ( + q.where(payment_entry.posting_date >= condition["from_payment_date"]) + if condition.get("from_payment_date") + else q + ) + q = ( + q.where(payment_entry.posting_date <= condition["to_payment_date"]) + if condition.get("to_payment_date") + else q + ) + if condition.get("get_payments") == True: + q = ( + q.where(payment_entry.cost_center == condition["cost_center"]) + if condition.get("cost_center") + else q + ) + q = ( + q.where(payment_entry.unallocated_amount >= condition["minimum_payment_amount"]) + if condition.get("minimum_payment_amount") + else q + ) + q = ( + q.where(payment_entry.unallocated_amount <= condition["maximum_payment_amount"]) + if condition.get("maximum_payment_amount") + else q +>>>>>>> 86bac2cf52 (refactor: filter on advance payments) ) else: reference_condition = "" From ab9da5281e338611f6bb3dcceb19eb9949e21501 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 4 Jul 2023 09:06:08 +0530 Subject: [PATCH 05/25] refactor: filter for journal entries (cherry picked from commit d01f0f2e965d776b9b142e33559efe012b4734e5) --- .../doctype/payment_reconciliation/payment_reconciliation.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py index 051f769fb24..27b5af600b6 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py +++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py @@ -92,6 +92,9 @@ class PaymentReconciliation(Document): def get_jv_entries(self): condition = self.get_conditions() + if self.payment_name: + condition += f" and t1.name like '%%{self.payment_name}%%'" + if self.get("cost_center"): condition += f" and t2.cost_center = '{self.cost_center}' " From 4a4cba071542ef14786abf220bbde5477d036399 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Wed, 23 Aug 2023 12:57:00 +0530 Subject: [PATCH 06/25] chore: resolve conflict --- .../payment_reconciliation.json | 17 +-------- .../payment_reconciliation.py | 17 +-------- erpnext/controllers/accounts_controller.py | 38 ------------------- 3 files changed, 2 insertions(+), 70 deletions(-) diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.json b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.json index c988b36cb01..0dc9c135b8c 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.json +++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.json @@ -189,16 +189,6 @@ "fieldtype": "Link", "label": "Cost Center", "options": "Cost Center" -<<<<<<< HEAD -======= - }, - { - "depends_on": "eval:doc.party", - "fieldname": "default_advance_account", - "fieldtype": "Link", - "label": "Default Advance Account", - "mandatory_depends_on": "doc.party_type", - "options": "Account" }, { "fieldname": "invoice_name", @@ -209,18 +199,13 @@ "fieldname": "payment_name", "fieldtype": "Data", "label": "Filter on Payment" ->>>>>>> 7a381affce (refactor: limit output to 50 in reconciliation tool) } ], "hide_toolbar": 1, "icon": "icon-resize-horizontal", "issingle": 1, "links": [], -<<<<<<< HEAD - "modified": "2022-04-29 15:37:10.246831", -======= "modified": "2023-08-15 05:35:50.109290", ->>>>>>> 7a381affce (refactor: limit output to 50 in reconciliation tool) "modified_by": "Administrator", "module": "Accounts", "name": "Payment Reconciliation", @@ -247,4 +232,4 @@ "sort_order": "DESC", "states": [], "track_changes": 1 -} \ No newline at end of file +} diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py index 27b5af600b6..abffd262961 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py +++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py @@ -58,25 +58,10 @@ class PaymentReconciliation(Document): def get_payment_entries(self): order_doctype = "Sales Order" if self.party_type == "Customer" else "Purchase Order" -<<<<<<< HEAD condition = self.get_conditions(get_payments=True) -======= - condition = frappe._dict( - { - "company": self.get("company"), - "get_payments": True, - "cost_center": self.get("cost_center"), - "from_payment_date": self.get("from_payment_date"), - "to_payment_date": self.get("to_payment_date"), - "maximum_payment_amount": self.get("maximum_payment_amount"), - "minimum_payment_amount": self.get("minimum_payment_amount"), - } - ) - if self.payment_name: - condition.update({"name": self.payment_name}) + condition += "name like '%%{0}%%'".format(self.payment_name) ->>>>>>> 86bac2cf52 (refactor: filter on advance payments) payment_entries = get_advance_payment_entries( self.party_type, self.party, diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 8b88599c4d1..76fe6a91182 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -2320,48 +2320,10 @@ def get_advance_payment_entries( payment_entries_against_order, unallocated_payment_entries = [], [] limit_cond = "limit %s" % limit if limit else "" -<<<<<<< HEAD if order_list or against_all_orders: if order_list: reference_condition = " and t2.reference_name in ({0})".format( ", ".join(["%s"] * len(order_list)) -======= - if payment_type == "Receive": - q = q.select((payment_entry.source_exchange_rate).as_("exchange_rate")) - else: - q = q.select((payment_entry.target_exchange_rate).as_("exchange_rate")) - - if condition: - if condition.get("name", None): - q = q.where(payment_entry.name.like(f"%{condition.get('name')}%")) - - q = q.where(payment_entry.company == condition["company"]) - q = ( - q.where(payment_entry.posting_date >= condition["from_payment_date"]) - if condition.get("from_payment_date") - else q - ) - q = ( - q.where(payment_entry.posting_date <= condition["to_payment_date"]) - if condition.get("to_payment_date") - else q - ) - if condition.get("get_payments") == True: - q = ( - q.where(payment_entry.cost_center == condition["cost_center"]) - if condition.get("cost_center") - else q - ) - q = ( - q.where(payment_entry.unallocated_amount >= condition["minimum_payment_amount"]) - if condition.get("minimum_payment_amount") - else q - ) - q = ( - q.where(payment_entry.unallocated_amount <= condition["maximum_payment_amount"]) - if condition.get("maximum_payment_amount") - else q ->>>>>>> 86bac2cf52 (refactor: filter on advance payments) ) else: reference_condition = "" From 20c45c79757a6c3d6ece0815973426de58a8a587 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Wed, 23 Aug 2023 14:15:35 +0530 Subject: [PATCH 07/25] chore: linter fix --- erpnext/accounts/utils.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index a089a856c04..0c01ff78c8c 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -884,9 +884,9 @@ def get_outstanding_invoices( min_outstanding=None, max_outstanding=None, accounting_dimensions=None, - vouchers=None, # list of dicts [{'voucher_type': '', 'voucher_no': ''}] for filtering - limit=None, # passed by reconciliation tool - voucher_no=None, # filter passed by reconciliation tool + vouchers=None, # list of dicts [{'voucher_type': '', 'voucher_no': ''}] for filtering + limit=None, # passed by reconciliation tool + voucher_no=None, # filter passed by reconciliation tool ): ple = qb.DocType("Payment Ledger Entry") From 4fa07777e98ab9a4848f235d7a5e80b88a538b80 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 24 Aug 2023 10:15:05 +0530 Subject: [PATCH 08/25] feat(MR): Project and Cost Center in Connections (backport #36794) (#36795) feat(MR): Project and Cost Center in Connections (#36794) (cherry picked from commit 54ffe41b54d1a2246d338668300a35dff6254665) Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com> --- .../doctype/material_request/material_request_dashboard.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/erpnext/stock/doctype/material_request/material_request_dashboard.py b/erpnext/stock/doctype/material_request/material_request_dashboard.py index 2bba52a4e25..f91ea6a0bba 100644 --- a/erpnext/stock/doctype/material_request/material_request_dashboard.py +++ b/erpnext/stock/doctype/material_request/material_request_dashboard.py @@ -6,6 +6,8 @@ def get_data(): "fieldname": "material_request", "internal_links": { "Sales Order": ["items", "sales_order"], + "Project": ["items", "project"], + "Cost Center": ["items", "cost_center"], }, "transactions": [ { @@ -15,5 +17,6 @@ def get_data(): {"label": _("Stock"), "items": ["Stock Entry", "Purchase Receipt", "Pick List"]}, {"label": _("Manufacturing"), "items": ["Work Order"]}, {"label": _("Internal Transfer"), "items": ["Sales Order"]}, + {"label": _("Accounting Dimensions"), "items": ["Project", "Cost Center"]}, ], } From 6edfcf4de82156be6e2ef2204e264e59a2d3b6eb Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 24 Aug 2023 10:42:59 +0530 Subject: [PATCH 09/25] fix: SCR return status (backport #36793) (#36796) fix: SCR return status (#36793) (cherry picked from commit 723563c16797e77b0722048db18dfcf0c32535a6) Co-authored-by: s-aga-r --- .../subcontracting_receipt.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py index ecec73e265c..cf457dfe82d 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py @@ -268,17 +268,24 @@ class SubcontractingReceipt(SubcontractingController): status = "Draft" elif self.docstatus == 1: status = "Completed" + if self.is_return: status = "Return" - return_against = frappe.get_doc("Subcontracting Receipt", self.return_against) - return_against.run_method("update_status") elif self.per_returned == 100: status = "Return Issued" + elif self.docstatus == 2: status = "Cancelled" + if self.is_return: + frappe.get_doc("Subcontracting Receipt", self.return_against).update_status( + update_modified=update_modified + ) + if status: - frappe.db.set_value("Subcontracting Receipt", self.name, "status", status, update_modified) + frappe.db.set_value( + "Subcontracting Receipt", self.name, "status", status, update_modified=update_modified + ) def get_gl_entries(self, warehouse_account=None): from erpnext.accounts.general_ledger import process_gl_map From e8dc63c89c01f6c8617a911ac87f56fc5768da12 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Thu, 24 Aug 2023 11:49:03 +0530 Subject: [PATCH 10/25] fix: Tax withholding reversal on Debit Notes (cherry picked from commit 6d9cebfee94a3485b207bbd5d298a680c26addef) --- .../tax_withholding_category/tax_withholding_category.py | 4 ++-- erpnext/controllers/sales_and_purchase_return.py | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py index d17ca08c408..943c0057f99 100644 --- a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py +++ b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py @@ -268,9 +268,9 @@ def get_tax_amount(party_type, parties, inv, tax_details, posting_date, pan_no=N net_total, limit_consumed, ldc.certificate_limit, ldc.rate, tax_details ) else: - tax_amount = net_total * tax_details.rate / 100 if net_total > 0 else 0 + tax_amount = net_total * tax_details.rate / 100 else: - tax_amount = net_total * tax_details.rate / 100 if net_total > 0 else 0 + tax_amount = net_total * tax_details.rate / 100 # once tds is deducted, not need to add vouchers in the invoice voucher_wise_amount = {} diff --git a/erpnext/controllers/sales_and_purchase_return.py b/erpnext/controllers/sales_and_purchase_return.py index b5d8246c116..a69b21c7c1d 100644 --- a/erpnext/controllers/sales_and_purchase_return.py +++ b/erpnext/controllers/sales_and_purchase_return.py @@ -345,6 +345,8 @@ def make_return_doc(doctype: str, source_name: str, target_doc=None): elif doctype == "Purchase Invoice": # look for Print Heading "Debit Note" doc.select_print_heading = frappe.get_cached_value("Print Heading", _("Debit Note")) + if source.tax_withholding_category: + doc.set_onload("supplier_tds", source.tax_withholding_category) for tax in doc.get("taxes") or []: if tax.charge_type == "Actual": From fd4159423d7e127499bdde04184f9e11f5fa9e78 Mon Sep 17 00:00:00 2001 From: ViralKansodiya <141210323+viralkansodiya@users.noreply.github.com> Date: Fri, 25 Aug 2023 13:43:53 +0530 Subject: [PATCH 11/25] fix: error listindexoutofrange when save a production plan (#36807) fix: error listindexoutof range when save a production plan --- .../manufacturing/doctype/production_plan/production_plan.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py index db4003bc585..a494550423f 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -53,7 +53,7 @@ class ProductionPlan(Document): data = sales_order_query(filters={"company": self.company, "sales_orders": sales_orders}) title = _("Production Plan Already Submitted") - if not data: + if not data and sales_orders: msg = _("No items are available in the sales order {0} for production").format(sales_orders[0]) if len(sales_orders) > 1: sales_orders = ", ".join(sales_orders) From bc6bd81f87921c61cc855379eb2a915dadb2ff2b Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Fri, 25 Aug 2023 15:05:26 +0530 Subject: [PATCH 12/25] fix: fetch JVs in tax withholding report without party values --- .../tds_payable_monthly.py | 54 ++++++++----------- 1 file changed, 23 insertions(+), 31 deletions(-) diff --git a/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.py b/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.py index 7d166614722..7191720c57e 100644 --- a/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.py +++ b/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.py @@ -257,7 +257,7 @@ def get_tds_docs(filters): } party = frappe.get_all(filters.get("party_type"), pluck="name") - query_filters.update({"against": ("in", party)}) + or_filters.update({"against": ("in", party), "voucher_type": "Journal Entry"}) if filters.get("party"): del query_filters["account"] @@ -294,7 +294,7 @@ def get_tds_docs(filters): if journal_entries: journal_entry_party_map = get_journal_entry_party_map(journal_entries) - get_doc_info(journal_entries, "Journal Entry", tax_category_map) + get_doc_info(journal_entries, "Journal Entry", tax_category_map, net_total_map) return ( tds_documents, @@ -309,7 +309,11 @@ def get_journal_entry_party_map(journal_entries): journal_entry_party_map = {} for d in frappe.db.get_all( "Journal Entry Account", - {"parent": ("in", journal_entries), "party_type": "Supplier", "party": ("is", "set")}, + { + "parent": ("in", journal_entries), + "party_type": ("in", ("Supplier", "Customer")), + "party": ("is", "set"), + }, ["parent", "party"], ): if d.parent not in journal_entry_party_map: @@ -320,41 +324,29 @@ def get_journal_entry_party_map(journal_entries): def get_doc_info(vouchers, doctype, tax_category_map, net_total_map=None): - if doctype == "Purchase Invoice": - fields = [ - "name", - "tax_withholding_category", - "base_tax_withholding_net_total", - "grand_total", - "base_total", - ] - elif doctype == "Sales Invoice": - fields = ["name", "base_net_total", "grand_total", "base_total"] - elif doctype == "Payment Entry": - fields = [ - "name", - "tax_withholding_category", - "paid_amount", - "paid_amount_after_tax", - "base_paid_amount", - ] - else: - fields = ["name", "tax_withholding_category"] + common_fields = ["name", "tax_withholding_category"] + fields_dict = { + "Purchase Invoice": ["base_tax_withholding_net_total", "grand_total", "base_total"], + "Sales Invoice": ["base_net_total", "grand_total", "base_total"], + "Payment Entry": ["paid_amount", "paid_amount_after_tax", "base_paid_amount"], + "Journal Entry": ["total_amount"], + } - entries = frappe.get_all(doctype, filters={"name": ("in", vouchers)}, fields=fields) + entries = frappe.get_all( + doctype, filters={"name": ("in", vouchers)}, fields=common_fields + fields_dict[doctype] + ) for entry in entries: tax_category_map.update({entry.name: entry.tax_withholding_category}) if doctype == "Purchase Invoice": - net_total_map.update( - {entry.name: [entry.base_tax_withholding_net_total, entry.grand_total, entry.base_total]} - ) + value = [entry.base_tax_withholding_net_total, entry.grand_total, entry.base_total] elif doctype == "Sales Invoice": - net_total_map.update({entry.name: [entry.base_net_total, entry.grand_total, entry.base_total]}) + value = [entry.base_net_total, entry.grand_total, entry.base_total] elif doctype == "Payment Entry": - net_total_map.update( - {entry.name: [entry.paid_amount, entry.paid_amount_after_tax, entry.base_paid_amount]} - ) + value = [entry.paid_amount, entry.paid_amount_after_tax, entry.base_paid_amount] + else: + value = [entry.total_amount] * 3 + net_total_map.update({entry.name: value}) def get_tax_rate_map(filters): From c4d338a59b932390e6b48a31a18e07037b7419b2 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 24 Aug 2023 20:46:33 +0530 Subject: [PATCH 13/25] refactor(test): make use of mixin in ar/ap report tests (cherry picked from commit bb7bed4c1a10aa49bbb1d14654454866918cf35c) --- .../test_accounts_receivable.py | 211 ++++++++---------- erpnext/accounts/test/accounts_mixin.py | 25 ++- 2 files changed, 121 insertions(+), 115 deletions(-) diff --git a/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py index 6f1889b34e1..0099e79e5dc 100644 --- a/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py @@ -8,20 +8,17 @@ from erpnext import get_default_cost_center from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice from erpnext.accounts.report.accounts_receivable.accounts_receivable import execute +from erpnext.accounts.test.accounts_mixin import AccountsTestMixin from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order -class TestAccountsReceivable(FrappeTestCase): +class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase): def setUp(self): - frappe.db.sql("delete from `tabSales Invoice` where company='_Test Company 2'") - frappe.db.sql("delete from `tabSales Order` where company='_Test Company 2'") - frappe.db.sql("delete from `tabPayment Entry` where company='_Test Company 2'") - frappe.db.sql("delete from `tabGL Entry` where company='_Test Company 2'") - frappe.db.sql("delete from `tabPayment Ledger Entry` where company='_Test Company 2'") - frappe.db.sql("delete from `tabJournal Entry` where company='_Test Company 2'") - frappe.db.sql("delete from `tabExchange Rate Revaluation` where company='_Test Company 2'") - - self.create_usd_account() + self.create_company() + self.create_customer() + self.create_item() + self.create_usd_receivable_account() + self.clear_old_entries() def tearDown(self): frappe.db.rollback() @@ -49,9 +46,61 @@ class TestAccountsReceivable(FrappeTestCase): debtors_usd.account_type = debtors.account_type self.debtors_usd = debtors_usd.save().name + def create_sales_invoice(self, no_payment_schedule=False, do_not_submit=False): + frappe.set_user("Administrator") + si = create_sales_invoice( + item=self.item, + company=self.company, + customer=self.customer, + debit_to=self.debit_to, + posting_date=today(), + parent_cost_center=self.cost_center, + cost_center=self.cost_center, + rate=100, + price_list_rate=100, + do_not_save=1, + ) + if not no_payment_schedule: + si.append( + "payment_schedule", + dict(due_date=getdate(add_days(today(), 30)), invoice_portion=30.00, payment_amount=30), + ) + si.append( + "payment_schedule", + dict(due_date=getdate(add_days(today(), 60)), invoice_portion=50.00, payment_amount=50), + ) + si.append( + "payment_schedule", + dict(due_date=getdate(add_days(today(), 90)), invoice_portion=20.00, payment_amount=20), + ) + si = si.save() + if not do_not_submit: + si = si.submit() + return si + + def create_payment_entry(self, docname): + pe = get_payment_entry("Sales Invoice", docname, bank_account=self.cash, party_amount=40) + pe.paid_from = self.debit_to + pe.insert() + pe.submit() + + def create_credit_note(self, docname): + credit_note = create_sales_invoice( + company=self.company, + customer=self.customer, + item=self.item, + qty=-1, + debit_to=self.debit_to, + cost_center=self.cost_center, + is_return=1, + return_against=docname, + ) + + return credit_note + def test_accounts_receivable(self): filters = { - "company": "_Test Company 2", + "company": self.company, "based_on_payment_terms": 1, "report_date": today(), "range1": 30, @@ -61,7 +110,9 @@ class TestAccountsReceivable(FrappeTestCase): } # check invoice grand total and invoiced column's value for 3 payment terms - name = make_sales_invoice().name + si = self.create_sales_invoice() + name = si.name + report = execute(filters) expected_data = [[100, 30], [100, 50], [100, 20]] @@ -71,7 +122,7 @@ class TestAccountsReceivable(FrappeTestCase): self.assertEqual(expected_data[i - 1], [row.invoice_grand_total, row.invoiced]) # check invoice grand total, invoiced, paid and outstanding column's value after payment - make_payment(name) + self.create_payment_entry(si.name) report = execute(filters) expected_data_after_payment = [[100, 50, 10, 40], [100, 20, 0, 20]] @@ -84,10 +135,10 @@ class TestAccountsReceivable(FrappeTestCase): ) # check invoice grand total, invoiced, paid and outstanding column's value after credit note - make_credit_note(name) + self.create_credit_note(si.name) report = execute(filters) - expected_data_after_credit_note = [100, 0, 0, 40, -40, "Debtors - _TC2"] + expected_data_after_credit_note = [100, 0, 0, 40, -40, self.debit_to] row = report[1][0] self.assertEqual( @@ -108,21 +159,20 @@ class TestAccountsReceivable(FrappeTestCase): """ so = make_sales_order( - company="_Test Company 2", - customer="_Test Customer 2", - warehouse="Finished Goods - _TC2", - currency="EUR", - debit_to="Debtors - _TC2", - income_account="Sales - _TC2", - expense_account="Cost of Goods Sold - _TC2", - cost_center="Main - _TC2", + company=self.company, + customer=self.customer, + warehouse=self.warehouse, + debit_to=self.debit_to, + income_account=self.income_account, + expense_account=self.expense_account, + cost_center=self.cost_center, ) pe = get_payment_entry(so.doctype, so.name) pe = pe.save().submit() filters = { - "company": "_Test Company 2", + "company": self.company, "based_on_payment_terms": 0, "report_date": today(), "range1": 30, @@ -147,34 +197,32 @@ class TestAccountsReceivable(FrappeTestCase): ) @change_settings( - "Accounts Settings", {"allow_multi_currency_invoices_against_single_party_account": 1} + "Accounts Settings", + {"allow_multi_currency_invoices_against_single_party_account": 1, "allow_stale": 0}, ) def test_exchange_revaluation_for_party(self): """ - Exchange Revaluation for party on Receivable/Payable shoule be included + Exchange Revaluation for party on Receivable/Payable should be included """ - company = "_Test Company 2" - customer = "_Test Customer 2" - # Using Exchange Gain/Loss account for unrealized as well. - company_doc = frappe.get_doc("Company", company) + company_doc = frappe.get_doc("Company", self.company) company_doc.unrealized_exchange_gain_loss_account = company_doc.exchange_gain_loss_account company_doc.save() - si = make_sales_invoice(no_payment_schedule=True, do_not_submit=True) + si = self.create_sales_invoice(no_payment_schedule=True, do_not_submit=True) si.currency = "USD" - si.conversion_rate = 0.90 + si.conversion_rate = 80 si.debit_to = self.debtors_usd si = si.save().submit() # Exchange Revaluation err = frappe.new_doc("Exchange Rate Revaluation") - err.company = company + err.company = self.company err.posting_date = today() accounts = err.get_accounts_data() err.extend("accounts", accounts) - err.accounts[0].new_exchange_rate = 0.95 + err.accounts[0].new_exchange_rate = 85 row = err.accounts[0] row.new_balance_in_base_currency = flt( row.new_exchange_rate * flt(row.balance_in_account_currency) @@ -189,7 +237,7 @@ class TestAccountsReceivable(FrappeTestCase): je = je.submit() filters = { - "company": company, + "company": self.company, "report_date": today(), "range1": 30, "range2": 60, @@ -198,7 +246,7 @@ class TestAccountsReceivable(FrappeTestCase): } report = execute(filters) - expected_data_for_err = [0, -5, 0, 5] + expected_data_for_err = [0, -500, 0, 500] row = [x for x in report[1] if x.voucher_type == je.doctype and x.voucher_no == je.name][0] self.assertEqual( expected_data_for_err, @@ -214,46 +262,43 @@ class TestAccountsReceivable(FrappeTestCase): """ Payment against credit/debit note should be considered against the parent invoice """ - company = "_Test Company 2" - customer = "_Test Customer 2" - si1 = make_sales_invoice() + si1 = self.create_sales_invoice() - pe = get_payment_entry("Sales Invoice", si1.name, bank_account="Cash - _TC2") - pe.paid_from = "Debtors - _TC2" + pe = get_payment_entry(si1.doctype, si1.name, bank_account=self.cash) + pe.paid_from = self.debit_to pe.insert() pe.submit() - cr_note = make_credit_note(si1.name) + cr_note = self.create_credit_note(si1.name) - si2 = make_sales_invoice() + si2 = self.create_sales_invoice() # manually link cr_note with si2 using journal entry je = frappe.new_doc("Journal Entry") - je.company = company + je.company = self.company je.voucher_type = "Credit Note" je.posting_date = today() - debit_account = "Debtors - _TC2" debit_entry = { - "account": debit_account, + "account": self.debit_to, "party_type": "Customer", - "party": customer, + "party": self.customer, "debit": 100, "debit_in_account_currency": 100, "reference_type": cr_note.doctype, "reference_name": cr_note.name, - "cost_center": "Main - _TC2", + "cost_center": self.cost_center, } credit_entry = { - "account": debit_account, + "account": self.debit_to, "party_type": "Customer", - "party": customer, + "party": self.customer, "credit": 100, "credit_in_account_currency": 100, "reference_type": si2.doctype, "reference_name": si2.name, - "cost_center": "Main - _TC2", + "cost_center": self.cost_center, } je.append("accounts", debit_entry) @@ -261,7 +306,7 @@ class TestAccountsReceivable(FrappeTestCase): je = je.save().submit() filters = { - "company": company, + "company": self.company, "report_date": today(), "range1": 30, "range2": 60, @@ -270,65 +315,3 @@ class TestAccountsReceivable(FrappeTestCase): } report = execute(filters) self.assertEqual(report[1], []) - - -def make_sales_invoice(no_payment_schedule=False, do_not_submit=False): - frappe.set_user("Administrator") - - si = create_sales_invoice( - company="_Test Company 2", - customer="_Test Customer 2", - currency="EUR", - warehouse="Finished Goods - _TC2", - debit_to="Debtors - _TC2", - income_account="Sales - _TC2", - expense_account="Cost of Goods Sold - _TC2", - cost_center="Main - _TC2", - do_not_save=1, - ) - - if not no_payment_schedule: - si.append( - "payment_schedule", - dict(due_date=getdate(add_days(today(), 30)), invoice_portion=30.00, payment_amount=30), - ) - si.append( - "payment_schedule", - dict(due_date=getdate(add_days(today(), 60)), invoice_portion=50.00, payment_amount=50), - ) - si.append( - "payment_schedule", - dict(due_date=getdate(add_days(today(), 90)), invoice_portion=20.00, payment_amount=20), - ) - - si = si.save() - - if not do_not_submit: - si = si.submit() - - return si - - -def make_payment(docname): - pe = get_payment_entry("Sales Invoice", docname, bank_account="Cash - _TC2", party_amount=40) - pe.paid_from = "Debtors - _TC2" - pe.insert() - pe.submit() - - -def make_credit_note(docname): - credit_note = create_sales_invoice( - company="_Test Company 2", - customer="_Test Customer 2", - currency="EUR", - qty=-1, - warehouse="Finished Goods - _TC2", - debit_to="Debtors - _TC2", - income_account="Sales - _TC2", - expense_account="Cost of Goods Sold - _TC2", - cost_center="Main - _TC2", - is_return=1, - return_against=docname, - ) - - return credit_note diff --git a/erpnext/accounts/test/accounts_mixin.py b/erpnext/accounts/test/accounts_mixin.py index debfffdcbb3..bf01362c97f 100644 --- a/erpnext/accounts/test/accounts_mixin.py +++ b/erpnext/accounts/test/accounts_mixin.py @@ -60,7 +60,6 @@ class AccountsTestMixin: self.income_account = "Sales - " + abbr self.expense_account = "Cost of Goods Sold - " + abbr self.debit_to = "Debtors - " + abbr - self.debit_usd = "Debtors USD - " + abbr self.cash = "Cash - " + abbr self.creditors = "Creditors - " + abbr self.retained_earnings = "Retained Earnings - " + abbr @@ -105,6 +104,28 @@ class AccountsTestMixin: new_acc.save() setattr(self, acc.attribute_name, new_acc.name) + def create_usd_receivable_account(self): + account_name = "Debtors USD" + if not frappe.db.get_value( + "Account", filters={"account_name": account_name, "company": self.company} + ): + acc = frappe.new_doc("Account") + acc.account_name = account_name + acc.parent_account = "Accounts Receivable - " + self.company_abbr + acc.company = self.company + acc.account_currency = "USD" + acc.account_type = "Receivable" + acc.insert() + else: + name = frappe.db.get_value( + "Account", + filters={"account_name": account_name, "company": self.company}, + fieldname="name", + pluck=True, + ) + acc = frappe.get_doc("Account", name) + self.debtors_usd = acc.name + def clear_old_entries(self): doctype_list = [ "GL Entry", @@ -113,6 +134,8 @@ class AccountsTestMixin: "Purchase Invoice", "Payment Entry", "Journal Entry", + "Sales Order", + "Exchange Rate Revaluation", ] for doctype in doctype_list: qb.from_(qb.DocType(doctype)).delete().where(qb.DocType(doctype).company == self.company).run() From 78b0a52d41f32a9fa0557d835a8e1265eeb4ef36 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 25 Aug 2023 15:41:18 +0530 Subject: [PATCH 14/25] test: increase coverage in ar/ap report (cherry picked from commit ce81ffd84438a9fe9df251507adea50e1f55f089) --- .../test_accounts_receivable.py | 257 +++++++++++++++++- 1 file changed, 255 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py index 0099e79e5dc..0c7d931d2d5 100644 --- a/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py @@ -107,6 +107,7 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase): "range2": 60, "range3": 90, "range4": 120, + "show_remarks": True, } # check invoice grand total and invoiced column's value for 3 payment terms @@ -115,11 +116,11 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase): report = execute(filters) - expected_data = [[100, 30], [100, 50], [100, 20]] + expected_data = [[100, 30, "No Remarks"], [100, 50, "No Remarks"], [100, 20, "No Remarks"]] for i in range(3): row = report[1][i - 1] - self.assertEqual(expected_data[i - 1], [row.invoice_grand_total, row.invoiced]) + self.assertEqual(expected_data[i - 1], [row.invoice_grand_total, row.invoiced, row.remarks]) # check invoice grand total, invoiced, paid and outstanding column's value after payment self.create_payment_entry(si.name) @@ -315,3 +316,255 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase): } report = execute(filters) self.assertEqual(report[1], []) + + def test_group_by_party(self): + si1 = self.create_sales_invoice(do_not_submit=True) + si1.posting_date = add_days(today(), -1) + si1.save().submit() + si2 = self.create_sales_invoice(do_not_submit=True) + si2.items[0].rate = 85 + si2.save().submit() + + filters = { + "company": self.company, + "report_date": today(), + "range1": 30, + "range2": 60, + "range3": 90, + "range4": 120, + "group_by_party": True, + } + report = execute(filters)[1] + self.assertEqual(len(report), 5) + + # assert voucher rows + expected_voucher_rows = [ + [100.0, 100.0, 100.0, 100.0], + [85.0, 85.0, 85.0, 85.0], + ] + voucher_rows = [] + for x in report[0:2]: + voucher_rows.append( + [x.invoiced, x.outstanding, x.invoiced_in_account_currency, x.outstanding_in_account_currency] + ) + self.assertEqual(expected_voucher_rows, voucher_rows) + + # assert total rows + expected_total_rows = [ + [self.customer, 185.0, 185.0], # party total + {}, # empty row for padding + ["Total", 185.0, 185.0], # grand total + ] + party_total_row = report[2] + self.assertEqual( + expected_total_rows[0], + [ + party_total_row.get("party"), + party_total_row.get("invoiced"), + party_total_row.get("outstanding"), + ], + ) + empty_row = report[3] + self.assertEqual(expected_total_rows[1], empty_row) + grand_total_row = report[4] + self.assertEqual( + expected_total_rows[2], + [ + grand_total_row.get("party"), + grand_total_row.get("invoiced"), + grand_total_row.get("outstanding"), + ], + ) + + def test_future_payments(self): + si = self.create_sales_invoice() + pe = get_payment_entry(si.doctype, si.name) + pe.posting_date = add_days(today(), 1) + pe.paid_amount = 90.0 + pe.references[0].allocated_amount = 90.0 + pe.save().submit() + filters = { + "company": self.company, + "report_date": today(), + "range1": 30, + "range2": 60, + "range3": 90, + "range4": 120, + "show_future_payments": True, + } + report = execute(filters)[1] + self.assertEqual(len(report), 1) + + expected_data = [100.0, 100.0, 10.0, 90.0] + + row = report[0] + self.assertEqual( + expected_data, [row.invoiced, row.outstanding, row.remaining_balance, row.future_amount] + ) + + pe.cancel() + # full payment in future date + pe = get_payment_entry(si.doctype, si.name) + pe.posting_date = add_days(today(), 1) + pe.save().submit() + report = execute(filters)[1] + self.assertEqual(len(report), 1) + expected_data = [100.0, 100.0, 0.0, 100.0] + row = report[0] + self.assertEqual( + expected_data, [row.invoiced, row.outstanding, row.remaining_balance, row.future_amount] + ) + + pe.cancel() + # over payment in future date + pe = get_payment_entry(si.doctype, si.name) + pe.posting_date = add_days(today(), 1) + pe.paid_amount = 110 + pe.save().submit() + report = execute(filters)[1] + self.assertEqual(len(report), 2) + expected_data = [[100.0, 0.0, 100.0, 0.0, 100.0], [0.0, 10.0, -10.0, -10.0, 0.0]] + for idx, row in enumerate(report): + self.assertEqual( + expected_data[idx], + [row.invoiced, row.paid, row.outstanding, row.remaining_balance, row.future_amount], + ) + + def test_sales_person(self): + sales_person = ( + frappe.get_doc({"doctype": "Sales Person", "sales_person_name": "John Clark", "enabled": True}) + .insert() + .submit() + ) + si = self.create_sales_invoice(do_not_submit=True) + si.append("sales_team", {"sales_person": sales_person.name, "allocated_percentage": 100}) + si.save().submit() + + filters = { + "company": self.company, + "report_date": today(), + "range1": 30, + "range2": 60, + "range3": 90, + "range4": 120, + "sales_person": sales_person.name, + "show_sales_person": True, + } + report = execute(filters)[1] + self.assertEqual(len(report), 1) + + expected_data = [100.0, 100.0, sales_person.name] + + row = report[0] + self.assertEqual(expected_data, [row.invoiced, row.outstanding, row.sales_person]) + + def test_cost_center_filter(self): + si = self.create_sales_invoice() + filters = { + "company": self.company, + "report_date": today(), + "range1": 30, + "range2": 60, + "range3": 90, + "range4": 120, + "cost_center": self.cost_center, + } + report = execute(filters)[1] + self.assertEqual(len(report), 1) + expected_data = [100.0, 100.0, self.cost_center] + row = report[0] + self.assertEqual(expected_data, [row.invoiced, row.outstanding, row.cost_center]) + + def test_customer_group_filter(self): + si = self.create_sales_invoice() + cus_group = frappe.db.get_value("Customer", self.customer, "customer_group") + filters = { + "company": self.company, + "report_date": today(), + "range1": 30, + "range2": 60, + "range3": 90, + "range4": 120, + "customer_group": cus_group, + } + report = execute(filters)[1] + self.assertEqual(len(report), 1) + expected_data = [100.0, 100.0, cus_group] + row = report[0] + self.assertEqual(expected_data, [row.invoiced, row.outstanding, row.customer_group]) + + filters.update({"customer_group": "Individual"}) + report = execute(filters)[1] + self.assertEqual(len(report), 0) + + def test_party_account_filter(self): + si1 = self.create_sales_invoice() + self.customer2 = ( + frappe.get_doc( + { + "doctype": "Customer", + "customer_name": "Jane Doe", + "type": "Individual", + "default_currency": "USD", + } + ) + .insert() + .submit() + ) + + si2 = self.create_sales_invoice(do_not_submit=True) + si2.posting_date = add_days(today(), -1) + si2.customer = self.customer2 + si2.currency = "USD" + si2.conversion_rate = 80 + si2.debit_to = self.debtors_usd + si2.save().submit() + + # Filter on company currency receivable account + filters = { + "company": self.company, + "report_date": today(), + "range1": 30, + "range2": 60, + "range3": 90, + "range4": 120, + "party_account": self.debit_to, + } + report = execute(filters)[1] + self.assertEqual(len(report), 1) + expected_data = [100.0, 100.0, self.debit_to, si1.currency] + row = report[0] + self.assertEqual( + expected_data, [row.invoiced, row.outstanding, row.party_account, row.account_currency] + ) + + # Filter on USD receivable account + filters.update({"party_account": self.debtors_usd}) + report = execute(filters)[1] + self.assertEqual(len(report), 1) + expected_data = [8000.0, 8000.0, self.debtors_usd, si2.currency] + row = report[0] + self.assertEqual( + expected_data, [row.invoiced, row.outstanding, row.party_account, row.account_currency] + ) + + # without filter on party account + filters.pop("party_account") + report = execute(filters)[1] + self.assertEqual(len(report), 2) + expected_data = [ + [8000.0, 8000.0, 100.0, 100.0, self.debtors_usd, si2.currency], + [100.0, 100.0, 100.0, 100.0, self.debit_to, si1.currency], + ] + for idx, row in enumerate(report): + self.assertEqual( + expected_data[idx], + [ + row.invoiced, + row.outstanding, + row.invoiced_in_account_currency, + row.outstanding_in_account_currency, + row.party_account, + row.account_currency, + ], + ) From 0f98cc85e9dce00fa333474cfb438f243503df81 Mon Sep 17 00:00:00 2001 From: Gourav Saini <63018500+gouravsaini021@users.noreply.github.com> Date: Sat, 26 Aug 2023 18:04:01 +0530 Subject: [PATCH 15/25] fix: Allow to make return against sales invoice which has closed sales order fix: Allow to make return against sales invoice which has closed sales order --- erpnext/controllers/selling_controller.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py index ef289ff6a67..8948b7e3f7c 100644 --- a/erpnext/controllers/selling_controller.py +++ b/erpnext/controllers/selling_controller.py @@ -388,7 +388,7 @@ class SellingController(StockController): for d in self.get("items"): if d.get(ref_fieldname): status = frappe.db.get_value("Sales Order", d.get(ref_fieldname), "status") - if status in ("Closed", "On Hold"): + if status in ("Closed", "On Hold") and not self.is_return: frappe.throw(_("Sales Order {0} is {1}").format(d.get(ref_fieldname), status)) def update_reserved_qty(self): @@ -404,7 +404,9 @@ class SellingController(StockController): if so and so_item_rows: sales_order = frappe.get_doc("Sales Order", so) - if sales_order.status in ["Closed", "Cancelled"]: + if (sales_order.status == "Closed" and not self.is_return) or sales_order.status in [ + "Cancelled" + ]: frappe.throw( _("{0} {1} is cancelled or closed").format(_("Sales Order"), so), frappe.InvalidStatusError ) From c07548a61235f371b93a7afde9ab7e25c6dc03d0 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Sat, 26 Aug 2023 19:29:15 +0530 Subject: [PATCH 16/25] fix: missing company flag for regional fn (#36791) fix: missing company flag for regional fn (#36791) * fix: missing company flag for regional fn (cherry picked from commit 9bc5952dd55fd1dc8d2c8061e325c202793f81d3) Co-authored-by: Dany Robert --- erpnext/controllers/accounts_controller.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 76fe6a91182..53039cfd8b5 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -200,9 +200,9 @@ class AccountsController(TransactionBase): # apply tax withholding only if checked and applicable self.set_tax_withholding() - validate_regional(self) - - validate_einvoice_fields(self) + with temporary_flag("company", self.company): + validate_regional(self) + validate_einvoice_fields(self) if self.doctype != "Material Request" and not self.ignore_pricing_rule: apply_pricing_rule_on_transaction(self) From 9b2a84f2597644d3b8dfb1758516fd2ae344d1a9 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Sat, 26 Aug 2023 19:29:57 +0530 Subject: [PATCH 17/25] chore: update fr translation for Naming Series (#36785) * chore: update fr translation for Naming Series (#36785) * chore: update fr translation for Naming Series * chore: update fr translation * chore: update fr translation * chore: update fr translation (cherry picked from commit e462edc6282f6e95b948f4f2583dabd96a92ca62) # Conflicts: # erpnext/translations/fr.csv * chore: resolve conflicts --------- Co-authored-by: HENRY Florian Co-authored-by: Deepesh Garg --- erpnext/translations/fr.csv | 114 ++++++++++++++++++++---------------- 1 file changed, 63 insertions(+), 51 deletions(-) diff --git a/erpnext/translations/fr.csv b/erpnext/translations/fr.csv index fde3f57211e..eb9076b4062 100644 --- a/erpnext/translations/fr.csv +++ b/erpnext/translations/fr.csv @@ -3536,7 +3536,7 @@ Quality Feedback Template,Modèle de commentaires sur la qualité, Rules for applying different promotional schemes.,Règles d'application de différents programmes promotionnels., Shift,Décalage, Show {0},Montrer {0}, -"Special Characters except '-', '#', '.', '/', '{{' and '}}' not allowed in naming series {0}","Caractères spéciaux sauf "-", "#", ".", "/", "{{" Et "}}" non autorisés dans les séries de nommage {0}", +"Special Characters except '-', '#', '.', '/', '{{' and '}}' not allowed in naming series {0}","Caractères spéciaux sauf "-", "#", ".", "/", "{{" Et "}}" non autorisés dans les masques de numérotation {0}", Target Details,Détails de la cible, {0} already has a Parent Procedure {1}.,{0} a déjà une procédure parent {1}., API,API, @@ -3551,7 +3551,7 @@ Importing {0} of {1},Importer {0} de {1}, Invalid URL,URL invalide, Landscape,Paysage, Last Sync On,Dernière synchronisation le, -Naming Series,Nom de série, +Naming Series,Masque de numérotation, No data to export,Aucune donnée à exporter, Portrait,Portrait, Print Heading,Imprimer Titre, @@ -4282,7 +4282,7 @@ Please set {0},Veuillez définir {0},supplier Draft,Brouillon,"docstatus,=,0" Cancelled,Annulé,"docstatus,=,2" Please setup Instructor Naming System in Education > Education Settings,Veuillez configurer le système de dénomination de l'instructeur dans Éducation> Paramètres de l'éducation, -Please set Naming Series for {0} via Setup > Settings > Naming Series,Veuillez définir la série de noms pour {0} via Configuration> Paramètres> Série de noms, +Please set Naming Series for {0} via Setup > Settings > Naming Series,Veuillez définir le masque de numérotation pour {0} via Configuration> Paramètres> Série de noms, UOM Conversion factor ({0} -> {1}) not found for item: {2},Facteur de conversion UdM ({0} -> {1}) introuvable pour l'article: {2}, Item Code > Item Group > Brand,Code article> Groupe d'articles> Marque, Customer > Customer Group > Territory,Client> Groupe de clients> Territoire, @@ -4297,7 +4297,7 @@ Fetch Serial Numbers based on FIFO,Récupérer les numéros de série basés sur Current Odometer Value should be greater than Last Odometer Value {0},La valeur actuelle de l'odomètre doit être supérieure à la dernière valeur de l'odomètre {0}, No additional expenses has been added,Aucune dépense supplémentaire n'a été ajoutée, Asset{} {assets_link} created for {},Élément {} {assets_link} créé pour {}, -Row {}: Asset Naming Series is mandatory for the auto creation for item {},Ligne {}: la série de noms d'éléments est obligatoire pour la création automatique de l'élément {}, +Row {}: Asset Naming Series is mandatory for the auto creation for item {},Ligne {}: Le masque de numérotation d'éléments est obligatoire pour la création automatique de l'élément {}, Assets not created for {0}. You will have to create asset manually.,Éléments non créés pour {0}. Vous devrez créer un actif manuellement., {0} {1} has accounting entries in currency {2} for company {3}. Please select a receivable or payable account with currency {2}.,{0} {1} a des écritures comptables dans la devise {2} pour l'entreprise {3}. Veuillez sélectionner un compte à recevoir ou à payer avec la devise {2}., Invalid Account,Compte invalide, @@ -4321,7 +4321,7 @@ Advanced Settings,Réglages avancés, Path,Chemin, Components,Composants, Verified By,Vérifié Par, -Invalid naming series (. missing) for {0},Série de noms non valide (. Manquante) pour {0}, +Invalid naming series (. missing) for {0},Masque de numérotation non valide (. Manquante) pour {0}, Filter Based On,Filtre basé sur, Reqd by date,Reqd par date, Manufacturer Part Number {0} is invalid,Le numéro de pièce du fabricant {0} n'est pas valide, @@ -5933,7 +5933,7 @@ Student Admission Program,Programme d'admission des étudiants, Minimum Age,Âge Minimum, Maximum Age,Âge Maximum, Application Fee,Frais de Dossier, -Naming Series (for Student Applicant),Nom de série (pour un candidat étudiant), +Naming Series (for Student Applicant),Masque de numérotation (pour un candidat étudiant), LMS Only,LMS seulement, EDU-APP-.YYYY.-,EDU-APP-YYYY.-, Application Status,État de la Demande, @@ -6424,7 +6424,7 @@ Hotel Reservation User,Utilisateur chargé des réservations d'hôtel, Hotel Room Reservation Item,Article de réservation de la chambre d'hôtel, Hotel Settings,Paramètres d'Hotel, Default Taxes and Charges,Taxes et frais par défaut, -Default Invoice Naming Series,Numéro de série par défaut pour les factures, +Default Invoice Naming Series,Masque de numérotation par défaut pour les factures, Additional Salary,Salaire supplémentaire, HR,RH, HR-ADS-.YY.-.MM.-,HR-ADS-.YY .-. MM.-, @@ -8034,7 +8034,7 @@ Default Unit of Measure,Unité de Mesure par Défaut, Maintain Stock,Maintenir Stock, Standard Selling Rate,Prix de Vente Standard, Auto Create Assets on Purchase,Création automatique d'actifs à l'achat, -Asset Naming Series,Nom de série de l'actif, +Asset Naming Series,Masque de numérotation de l'actif, Over Delivery/Receipt Allowance (%),Surlivrance / indemnité de réception (%), Barcodes,Codes-barres, Shelf Life In Days,Durée de conservation en jours, @@ -8053,7 +8053,7 @@ Serial Nos and Batches,N° de Série et Lots, Has Batch No,A un Numéro de Lot, Automatically Create New Batch,Créer un Nouveau Lot Automatiquement, Batch Number Series,Série de numéros de lots, -"Example: ABCD.#####. If series is set and Batch No is not mentioned in transactions, then automatic batch number will be created based on this series. If you always want to explicitly mention Batch No for this item, leave this blank. Note: this setting will take priority over the Naming Series Prefix in Stock Settings.","Exemple: ABCD. #####. Si la série est définie et que le numéro de lot n'est pas mentionné dans les transactions, un numéro de lot sera automatiquement créé en avec cette série. Si vous préferez mentionner explicitement et systématiquement le numéro de lot pour cet article, laissez ce champ vide. Remarque: ce paramètre aura la priorité sur le préfixe de la série dans les paramètres de stock.", +"Example: ABCD.#####. If series is set and Batch No is not mentioned in transactions, then automatic batch number will be created based on this series. If you always want to explicitly mention Batch No for this item, leave this blank. Note: this setting will take priority over the Naming Series Prefix in Stock Settings.","Exemple: ABCD. #####. Si le masque est définie et que le numéro de lot n'est pas mentionné dans les transactions, un numéro de lot sera automatiquement créé en avec ce masque. Si vous préferez mentionner explicitement et systématiquement le numéro de lot pour cet article, laissez ce champ vide. Remarque: ce paramètre aura la priorité sur le préfixe du masque dans les paramètres de stock.", Has Expiry Date,A une date d'expiration, Retain Sample,Conserver l'échantillon, Max Sample Quantity,Quantité maximum d'échantillon, @@ -8353,8 +8353,8 @@ Inter Warehouse Transfer Settings,Paramètres de transfert entre entrepôts, Freeze Stock Entries,Geler les Entrées de Stocks, Stock Frozen Upto,Stock Gelé Jusqu'au, Batch Identification,Identification par lots, -Use Naming Series,Utiliser la série de noms, -Naming Series Prefix,Préfix du nom de série, +Use Naming Series,Utiliser le masque de numérotation, +Naming Series Prefix,Préfix du masque de numérotation, UOM Category,Catégorie d'unité de mesure (UdM), UOM Conversion Detail,Détails de Conversion de l'UdM, Variant Field,Champ de Variante, @@ -8824,7 +8824,7 @@ Is Inter State,Est Inter State, Purchase Details,Détails d'achat, Depreciation Posting Date,Date comptable de l'amortissement, "By default, the Supplier Name is set as per the Supplier Name entered. If you want Suppliers to be named by a ","Par défaut, le nom du fournisseur est défini selon le nom du fournisseur saisi. Si vous souhaitez que les fournisseurs soient nommés par un", - choose the 'Naming Series' option.,choisissez l'option 'Naming Series'., + choose the 'Naming Series' option.,choisissez l'option 'Masque de numérotation'., Configure the default Price List when creating a new Purchase transaction. Item prices will be fetched from this Price List.,Configurez la liste de prix par défaut lors de la création d'une nouvelle transaction d'achat. Les prix des articles seront extraits de cette liste de prix., "If this option is configured 'Yes', ERPNext will prevent you from creating a Purchase Invoice or Receipt without creating a Purchase Order first. This configuration can be overridden for a particular supplier by enabling the 'Allow Purchase Invoice Creation Without Purchase Order' checkbox in the Supplier master.","Si cette option est configurée «Oui», ERPNext vous empêchera de créer une facture d'achat ou un reçu sans créer d'abord une Commande d'Achat. Cette configuration peut être remplacée pour un fournisseur particulier en cochant la case «Autoriser la création de facture d'achat sans commmande d'achat» dans la fiche fournisseur.", "If this option is configured 'Yes', ERPNext will prevent you from creating a Purchase Invoice without creating a Purchase Receipt first. This configuration can be overridden for a particular supplier by enabling the 'Allow Purchase Invoice Creation Without Purchase Receipt' checkbox in the Supplier master.","Si cette option est configurée «Oui», ERPNext vous empêchera de créer une facture d'achat sans créer d'abord un reçu d'achat. Cette configuration peut être remplacée pour un fournisseur particulier en cochant la case "Autoriser la création de facture d'achat sans reçu d'achat" dans la fiche fournisseur.", @@ -9858,14 +9858,14 @@ Is Rate Adjustment Entry (Debit Note),Est un justement du prix de la note de dé Issue a debit note with 0 qty against an existing Sales Invoice,Creer une note de débit avec une quatité à O pour la facture Control Historical Stock Transactions,Controle de l'historique des stransaction de stock No stock transactions can be created or modified before this date.,Aucune transaction ne peux être créée ou modifié avant cette date. -Stock transactions that are older than the mentioned days cannot be modified.,Les transactions de stock plus ancienne que le nombre de jours ci-dessus ne peuvent être modifiées -Role Allowed to Create/Edit Back-dated Transactions,Rôle autorisé à créer et modifier des transactions anti-datée -"If mentioned, the system will allow only the users with this Role to create or modify any stock transaction earlier than the latest stock transaction for a specific item and warehouse. If set as blank, it allows all users to create/edit back-dated transactions.","Les utilisateur de ce role pourront creer et modifier des transactions dans le passé. Si vide tout les utilisateurs pourrons le faire" -Auto Insert Item Price If Missing,Création du prix de l'article dans les listes de prix si abscent -Update Existing Price List Rate,Mise a jour automatique du prix dans les listes de prix -Show Barcode Field in Stock Transactions,Afficher le champ Code Barre dans les transactions de stock -Convert Item Description to Clean HTML in Transactions,Convertir les descriptions d'articles en HTML valide lors des transactions -Have Default Naming Series for Batch ID?,Nom de série par défaut pour les Lots ou Séries +Stock transactions that are older than the mentioned days cannot be modified.,Les transactions de stock plus ancienne que le nombre de jours ci-dessus ne peuvent être modifiées, +Role Allowed to Create/Edit Back-dated Transactions,Rôle autorisé à créer et modifier des transactions anti-datée, +"If mentioned, the system will allow only the users with this Role to create or modify any stock transaction earlier than the latest stock transaction for a specific item and warehouse. If set as blank, it allows all users to create/edit back-dated transactions.",Les utilisateur de ce role pourront creer et modifier des transactions dans le passé. Si vide tout les utilisateurs pourrons le faire +Auto Insert Item Price If Missing,Création du prix de l'article dans les listes de prix si abscent, +Update Existing Price List Rate,Mise a jour automatique du prix dans les listes de prix, +Show Barcode Field in Stock Transactions,Afficher le champ Code Barre dans les transactions de stock, +Convert Item Description to Clean HTML in Transactions,Convertir les descriptions d'articles en HTML valide lors des transactions, +Have Default Naming Series for Batch ID?,Masque de numérotation par défaut pour les Lots ou Séries, "The percentage you are allowed to transfer more against the quantity ordered. For example, if you have ordered 100 units, and your Allowance is 10%, then you are allowed transfer 110 units","Le pourcentage de quantité que vous pourrez réceptionner en plus de la quantité commandée. Par exemple, vous avez commandé 100 unités, votre pourcentage de dépassement est de 10%, vous pourrez réceptionner 110 unités" Allowed Items,Articles autorisés Party Specific Item,Restriction d'article disponible @@ -9892,34 +9892,46 @@ Interview Feedback,Retour d'entretien Journal Energy Point,Historique des points d'énergies Billing Address Details,Adresse de facturation (détails) Supplier Address Details,Adresse Fournisseur (détails) -Retail,Commerce -Users,Utilisateurs -Permission Manager,Gestion des permissions -Fetch Timesheet,Récuprer les temps saisis -Get Supplier Group Details,Appliquer les informations depuis le Groupe de fournisseur -Quality Inspection(s),Inspection(s) Qualité -Set Advances and Allocate (FIFO),Affecter les encours au réglement -Apply Putaway Rule,Appliquer la régle de routage d'entrepot -Delete Transactions,Supprimer les transactions -Default Payment Discount Account,Compte par défaut des paiements de remise -Unrealized Profit / Loss Account,Compte de perte -Enable Provisional Accounting For Non Stock Items,Activer la provision pour les articles non stockés -Publish in Website,Publier sur le Site Web -List View,Vue en liste -Allow Excess Material Transfer,Autoriser les transfert de stock supérieurs à l'attendue -Allow transferring raw materials even after the Required Quantity is fulfilled,Autoriser les transfert de matiéres premiére mais si la quantité requise est atteinte -Add Corrective Operation Cost in Finished Good Valuation,Ajouter des opérations de correction de coût pour la valorisation des produits finis -Make Serial No / Batch from Work Order,Générer des numéros de séries / lots depuis les Ordres de Fabrications -System will automatically create the serial numbers / batch for the Finished Good on submission of work order,le systéme va créer des numéros de séries / lots à la validation des produit finis depuis les Ordres de Fabrications -Allow material consumptions without immediately manufacturing finished goods against a Work Order,Autoriser la consommation sans immédiatement fabriqué les produit fini dans les ordres de fabrication -Quality Inspection Parameter,Paramétre des Inspection Qualité -Parameter Group,Groupe de paramétre -E Commerce Settings,Paramétrage E-Commerce -Follow these steps to create a landing page for your store:,Suivez les intructions suivantes pour créer votre page d'accueil de boutique en ligne -Show Price in Quotation,Afficher les prix sur les devis -Add-ons,Extensions -Enable Wishlist,Activer la liste de souhaits -Enable Reviews and Ratings,Activer les avis et notes -Enable Recommendations,Activer les recommendations -Item Search Settings,Paramétrage de la recherche d'article -Purchase demande,Demande de materiel +Retail,Commerce, +Users,Utilisateurs, +Permission Manager,Gestion des permissions, +Fetch Timesheet,Récuprer les temps saisis, +Get Supplier Group Details,Appliquer les informations depuis le Groupe de fournisseur, +Quality Inspection(s),Inspection(s) Qualite, +Set Advances and Allocate (FIFO),Affecter les encours au réglement, +Apply Putaway Rule,Appliquer la régle de routage d'entrepot, +Delete Transactions,Supprimer les transactions, +Default Payment Discount Account,Compte par défaut des paiements de remise, +Unrealized Profit / Loss Account,Compte de perte, +Enable Provisional Accounting For Non Stock Items,Activer la provision pour les articles non stockés, +Publish in Website,Publier sur le Site Web, +List View,Vue en liste, +Allow Excess Material Transfer,Autoriser les transfert de stock supérieurs à l'attendue, +Allow transferring raw materials even after the Required Quantity is fulfilled,Autoriser les transfert de matiéres premiére mais si la quantité requise est atteinte, +Add Corrective Operation Cost in Finished Good Valuation,Ajouter des opérations de correction de coût pour la valorisation des produits finis, +Make Serial No / Batch from Work Order,Générer des numéros de séries / lots depuis les Ordres de Fabrications, +System will automatically create the serial numbers / batch for the Finished Good on submission of work order,le systéme va créer des numéros de séries / lots à la validation des produit finis depuis les Ordres de Fabrications, +Allow material consumptions without immediately manufacturing finished goods against a Work Order,Autoriser la consommation sans immédiatement fabriqué les produit fini dans les ordres de fabrication, +Quality Inspection Parameter,Paramétre des Inspection Qualite, +Parameter Group,Groupe de paramétre, +E Commerce Settings,Paramétrage E-Commerce, +Follow these steps to create a landing page for your store:,Suivez les intructions suivantes pour créer votre page d'accueil de boutique en ligne, +Show Price in Quotation,Afficher les prix sur les devis, +Add-ons,Extensions, +Enable Wishlist,Activer la liste de souhaits, +Enable Reviews and Ratings,Activer les avis et notes, +Enable Recommendations,Activer les recommendations, +Item Search Settings,Paramétrage de la recherche d'article, +Purchase demande,Demande de materiel, +Internal Customer,Client interne +Internal Supplier,Fournisseur interne +Contact & Address,Contact et Adresse +Primary Address and Contact,Adresse et contact principal +Supplier Primary Contact,Contact fournisseur principal +Supplier Primary Address,Adresse fournisseur principal +From Opportunity,Depuis l'opportunité +Default Receivable Accounts,Compte de débit par défaut +Receivable Accounts,Compte de débit +Mention if a non-standard receivable account,Veuillez mentionner s'il s'agit d'un compte débiteur non standard +Allow Purchase,Autoriser à l'achat +Inventory Settings,Paramétrage de l'inventaire From 256c3c81a473b21e6fd44c75fe61e7896e119876 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Sun, 27 Aug 2023 08:29:53 +0530 Subject: [PATCH 18/25] test: Exchange Rate Revaluation functions and its impact on ledger (cherry picked from commit d40504b9731bbeaf153e87b0d8d765a17fa3c43e) --- .../test_exchange_rate_revaluation.py | 294 +++++++++++++++++- 1 file changed, 292 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/exchange_rate_revaluation/test_exchange_rate_revaluation.py b/erpnext/accounts/doctype/exchange_rate_revaluation/test_exchange_rate_revaluation.py index ec55e60fd1f..ced04ced3fd 100644 --- a/erpnext/accounts/doctype/exchange_rate_revaluation/test_exchange_rate_revaluation.py +++ b/erpnext/accounts/doctype/exchange_rate_revaluation/test_exchange_rate_revaluation.py @@ -3,6 +3,296 @@ import unittest +import frappe +from frappe import qb +from frappe.tests.utils import FrappeTestCase, change_settings +from frappe.utils import add_days, flt, today -class TestExchangeRateRevaluation(unittest.TestCase): - pass +from erpnext import get_default_cost_center +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 +from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice +from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice +from erpnext.accounts.party import get_party_account +from erpnext.accounts.test.accounts_mixin import AccountsTestMixin +from erpnext.stock.doctype.item.test_item import create_item + + +class TestExchangeRateRevaluation(AccountsTestMixin, FrappeTestCase): + def setUp(self): + self.create_company() + self.create_usd_receivable_account() + self.create_item() + self.create_customer() + self.clear_old_entries() + self.set_system_and_company_settings() + + def tearDown(self): + frappe.db.rollback() + + def set_system_and_company_settings(self): + # set number and currency precision + system_settings = frappe.get_doc("System Settings") + system_settings.float_precision = 2 + system_settings.currency_precision = 2 + system_settings.save() + + # Using Exchange Gain/Loss account for unrealized as well. + company_doc = frappe.get_doc("Company", self.company) + company_doc.unrealized_exchange_gain_loss_account = company_doc.exchange_gain_loss_account + company_doc.save() + + @change_settings( + "Accounts Settings", + {"allow_multi_currency_invoices_against_single_party_account": 1, "allow_stale": 0}, + ) + def test_01_revaluation_of_forex_balance(self): + """ + Test Forex account balance and Journal creation post Revaluation + """ + si = create_sales_invoice( + item=self.item, + company=self.company, + customer=self.customer, + debit_to=self.debtors_usd, + posting_date=today(), + parent_cost_center=self.cost_center, + cost_center=self.cost_center, + rate=100, + price_list_rate=100, + do_not_submit=1, + ) + si.currency = "USD" + si.conversion_rate = 80 + si.save().submit() + + err = frappe.new_doc("Exchange Rate Revaluation") + err.company = (self.company,) + err.posting_date = today() + accounts = err.get_accounts_data() + err.extend("accounts", accounts) + row = err.accounts[0] + row.new_exchange_rate = 85 + row.new_balance_in_base_currency = flt( + row.new_exchange_rate * flt(row.balance_in_account_currency) + ) + row.gain_loss = row.new_balance_in_base_currency - flt(row.balance_in_base_currency) + err.set_total_gain_loss() + err = err.save().submit() + + # Create JV for ERR + err_journals = err.make_jv_entries() + je = frappe.get_doc("Journal Entry", err_journals.get("revaluation_jv")) + je = je.submit() + + je.reload() + self.assertEqual(je.voucher_type, "Exchange Rate Revaluation") + self.assertEqual(je.total_debit, 8500.0) + self.assertEqual(je.total_credit, 8500.0) + + acc_balance = frappe.db.get_all( + "GL Entry", + filters={"account": self.debtors_usd, "is_cancelled": 0}, + fields=["sum(debit)-sum(credit) as balance"], + )[0] + self.assertEqual(acc_balance.balance, 8500.0) + + @change_settings( + "Accounts Settings", + {"allow_multi_currency_invoices_against_single_party_account": 1, "allow_stale": 0}, + ) + def test_02_accounts_only_with_base_currency_balance(self): + """ + Test Revaluation on Forex account with balance only in base currency + """ + si = create_sales_invoice( + item=self.item, + company=self.company, + customer=self.customer, + debit_to=self.debtors_usd, + posting_date=today(), + parent_cost_center=self.cost_center, + cost_center=self.cost_center, + rate=100, + price_list_rate=100, + do_not_submit=1, + ) + si.currency = "USD" + si.conversion_rate = 80 + si.save().submit() + + pe = get_payment_entry(si.doctype, si.name) + pe.source_exchange_rate = 85 + pe.received_amount = 8500 + pe.save().submit() + + # Cancel the auto created gain/loss JE to simulate balance only in base currency + je = frappe.db.get_all( + "Journal Entry Account", filters={"reference_name": si.name}, pluck="parent" + )[0] + frappe.get_doc("Journal Entry", je).cancel() + + err = frappe.new_doc("Exchange Rate Revaluation") + err.company = (self.company,) + err.posting_date = today() + err.fetch_and_calculate_accounts_data() + err = err.save().submit() + + # Create JV for ERR + self.assertTrue(err.check_journal_entry_condition()) + err_journals = err.make_jv_entries() + je = frappe.get_doc("Journal Entry", err_journals.get("zero_balance_jv")) + je = je.submit() + + je.reload() + self.assertEqual(je.voucher_type, "Exchange Gain Or Loss") + self.assertEqual(len(je.accounts), 2) + # Only base currency fields will be posted to + for acc in je.accounts: + self.assertEqual(acc.debit_in_account_currency, 0) + self.assertEqual(acc.credit_in_account_currency, 0) + + self.assertEqual(je.total_debit, 500.0) + self.assertEqual(je.total_credit, 500.0) + + acc_balance = frappe.db.get_all( + "GL Entry", + filters={"account": self.debtors_usd, "is_cancelled": 0}, + fields=[ + "sum(debit)-sum(credit) as balance", + "sum(debit_in_account_currency)-sum(credit_in_account_currency) as balance_in_account_currency", + ], + )[0] + # account shouldn't have balance in base and account currency + self.assertEqual(acc_balance.balance, 0.0) + self.assertEqual(acc_balance.balance_in_account_currency, 0.0) + + @change_settings( + "Accounts Settings", + {"allow_multi_currency_invoices_against_single_party_account": 1, "allow_stale": 0}, + ) + def test_03_accounts_only_with_account_currency_balance(self): + """ + Test Revaluation on Forex account with balance only in account currency + """ + precision = frappe.db.get_single_value("System Settings", "currency_precision") + + # posting on previous date to make sure that ERR picks up the Payment entry's exchange + # rate while calculating gain/loss for account currency balance + si = create_sales_invoice( + item=self.item, + company=self.company, + customer=self.customer, + debit_to=self.debtors_usd, + posting_date=add_days(today(), -1), + parent_cost_center=self.cost_center, + cost_center=self.cost_center, + rate=100, + price_list_rate=100, + do_not_submit=1, + ) + si.currency = "USD" + si.conversion_rate = 80 + si.save().submit() + + pe = get_payment_entry(si.doctype, si.name) + pe.paid_amount = 95 + pe.source_exchange_rate = 84.211 + pe.received_amount = 8000 + pe.references = [] + pe.save().submit() + + acc_balance = frappe.db.get_all( + "GL Entry", + filters={"account": self.debtors_usd, "is_cancelled": 0}, + fields=[ + "sum(debit)-sum(credit) as balance", + "sum(debit_in_account_currency)-sum(credit_in_account_currency) as balance_in_account_currency", + ], + )[0] + # account should have balance only in account currency + self.assertEqual(flt(acc_balance.balance, precision), 0.0) + self.assertEqual(flt(acc_balance.balance_in_account_currency, precision), 5.0) # in USD + + err = frappe.new_doc("Exchange Rate Revaluation") + err.company = (self.company,) + err.posting_date = today() + err.fetch_and_calculate_accounts_data() + err.set_total_gain_loss() + err = err.save().submit() + + # Create JV for ERR + self.assertTrue(err.check_journal_entry_condition()) + err_journals = err.make_jv_entries() + je = frappe.get_doc("Journal Entry", err_journals.get("zero_balance_jv")) + je = je.submit() + + je.reload() + self.assertEqual(je.voucher_type, "Exchange Gain Or Loss") + self.assertEqual(len(je.accounts), 2) + # Only account currency fields will be posted to + for acc in je.accounts: + self.assertEqual(flt(acc.debit, precision), 0.0) + self.assertEqual(flt(acc.credit, precision), 0.0) + + row = [x for x in je.accounts if x.account == self.debtors_usd][0] + self.assertEqual(flt(row.credit_in_account_currency, precision), 5.0) # in USD + row = [x for x in je.accounts if x.account != self.debtors_usd][0] + self.assertEqual(flt(row.debit_in_account_currency, precision), 421.06) # in INR + + # total_debit and total_credit will be 0.0, as JV is posting only to account currency fields + self.assertEqual(flt(je.total_debit, precision), 0.0) + self.assertEqual(flt(je.total_credit, precision), 0.0) + + acc_balance = frappe.db.get_all( + "GL Entry", + filters={"account": self.debtors_usd, "is_cancelled": 0}, + fields=[ + "sum(debit)-sum(credit) as balance", + "sum(debit_in_account_currency)-sum(credit_in_account_currency) as balance_in_account_currency", + ], + )[0] + # account shouldn't have balance in base and account currency post revaluation + self.assertEqual(flt(acc_balance.balance, precision), 0.0) + self.assertEqual(flt(acc_balance.balance_in_account_currency, precision), 0.0) + + @change_settings( + "Accounts Settings", + {"allow_multi_currency_invoices_against_single_party_account": 1, "allow_stale": 0}, + ) + def test_04_get_account_details_function(self): + si = create_sales_invoice( + item=self.item, + company=self.company, + customer=self.customer, + debit_to=self.debtors_usd, + posting_date=today(), + parent_cost_center=self.cost_center, + cost_center=self.cost_center, + rate=100, + price_list_rate=100, + do_not_submit=1, + ) + si.currency = "USD" + si.conversion_rate = 80 + si.save().submit() + + from erpnext.accounts.doctype.exchange_rate_revaluation.exchange_rate_revaluation import ( + get_account_details, + ) + + account_details = get_account_details( + self.company, si.posting_date, self.debtors_usd, "Customer", self.customer, 0.05 + ) + # not checking for new exchange rate and balances as it is dependent on live exchange rates + expected_data = { + "account_currency": "USD", + "balance_in_base_currency": 8000.0, + "balance_in_account_currency": 100.0, + "current_exchange_rate": 80.0, + "zero_balance": False, + "new_balance_in_account_currency": 100.0, + } + + for key, val in expected_data.items(): + self.assertEqual(expected_data.get(key), account_details.get(key)) From bd41cb221b9cfb231271f07f0977cb09771c5454 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 28 Aug 2023 11:53:11 +0530 Subject: [PATCH 19/25] fix: Asset Category filter is not working in asset depreciation(#36806) fix: Asset Category filter is not working in asset depreciation fix: Asset Category filter is not working in asset depreciation and balances Co-authored-by: ubuntu (cherry picked from commit 388a42ec7ee0e6a2a36b81c6f3c84b07deb8b12c) Co-authored-by: ViralKansodiya <141210323+viralkansodiya@users.noreply.github.com> --- .../asset_depreciations_and_balances.py | 27 ++++++++++++++----- 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/erpnext/accounts/report/asset_depreciations_and_balances/asset_depreciations_and_balances.py b/erpnext/accounts/report/asset_depreciations_and_balances/asset_depreciations_and_balances.py index d67eee3552d..bdc8d8504f8 100644 --- a/erpnext/accounts/report/asset_depreciations_and_balances/asset_depreciations_and_balances.py +++ b/erpnext/accounts/report/asset_depreciations_and_balances/asset_depreciations_and_balances.py @@ -58,6 +58,9 @@ def get_data(filters): def get_asset_categories(filters): + condition = "" + if filters.get("asset_category"): + condition += " and asset_category = %(asset_category)s" return frappe.db.sql( """ SELECT asset_category, @@ -98,15 +101,25 @@ def get_asset_categories(filters): 0 end), 0) as cost_of_scrapped_asset from `tabAsset` - where docstatus=1 and company=%(company)s and purchase_date <= %(to_date)s + where docstatus=1 and company=%(company)s and purchase_date <= %(to_date)s {} group by asset_category - """, - {"to_date": filters.to_date, "from_date": filters.from_date, "company": filters.company}, + """.format( + condition + ), + { + "to_date": filters.to_date, + "from_date": filters.from_date, + "company": filters.company, + "asset_category": filters.get("asset_category"), + }, as_dict=1, ) def get_assets(filters): + condition = "" + if filters.get("asset_category"): + condition = " and a.asset_category = '{}'".format(filters.get("asset_category")) return frappe.db.sql( """ SELECT results.asset_category, @@ -138,7 +151,7 @@ def get_assets(filters): aca.parent = a.asset_category and aca.company_name = %(company)s join `tabCompany` company on company.name = %(company)s - where a.docstatus=1 and a.company=%(company)s and a.purchase_date <= %(to_date)s and gle.debit != 0 and gle.is_cancelled = 0 and gle.account = ifnull(aca.depreciation_expense_account, company.depreciation_expense_account) + where a.docstatus=1 and a.company=%(company)s and a.purchase_date <= %(to_date)s and gle.debit != 0 and gle.is_cancelled = 0 and gle.account = ifnull(aca.depreciation_expense_account, company.depreciation_expense_account) {0} group by a.asset_category union SELECT a.asset_category, @@ -154,10 +167,12 @@ def get_assets(filters): end), 0) as depreciation_eliminated_during_the_period, 0 as depreciation_amount_during_the_period from `tabAsset` a - where a.docstatus=1 and a.company=%(company)s and a.purchase_date <= %(to_date)s + where a.docstatus=1 and a.company=%(company)s and a.purchase_date <= %(to_date)s {0} group by a.asset_category) as results group by results.asset_category - """, + """.format( + condition + ), {"to_date": filters.to_date, "from_date": filters.from_date, "company": filters.company}, as_dict=1, ) From adc87f16a31d9d35505168e97cc0967f89267d77 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 28 Aug 2023 13:59:00 +0530 Subject: [PATCH 20/25] fix: fetch rounded total while pulling reference details on SO (cherry picked from commit 714b8289c1cf23d9a0039e94f305d926a82a28a0) --- erpnext/accounts/doctype/payment_entry/payment_entry.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 127768071a0..765e69b75bd 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -1867,10 +1867,15 @@ def get_reference_details(reference_doctype, reference_name, party_account_curre if not total_amount: if party_account_currency == company_currency: # for handling cases that don't have multi-currency (base field) - total_amount = ref_doc.get("base_grand_total") or ref_doc.get("grand_total") + total_amount = ( + ref_doc.get("base_rounded_total") + or ref_doc.get("rounded_total") + or ref_doc.get("base_grand_total") + or ref_doc.get("grand_total") + ) exchange_rate = 1 else: - total_amount = ref_doc.get("grand_total") + total_amount = ref_doc.get("rounded_total") or ref_doc.get("grand_total") if not exchange_rate: # Get the exchange rate from the original ref doc # or get it based on the posting date of the ref doc. From 0350c6985608cff38a6a7e7cb0b5329f6601fc6d Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 28 Aug 2023 14:21:21 +0530 Subject: [PATCH 21/25] test: allocation err misfire on Sales Order (cherry picked from commit 67a0969b782deb1847ea75e2f565df824d6df36e) --- .../doctype/payment_entry/test_payment_entry.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py index 21379458874..6b897acc27d 100644 --- a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py @@ -1201,6 +1201,22 @@ class TestPaymentEntry(FrappeTestCase): template.allocate_payment_based_on_payment_terms = 1 template.save() + def test_allocation_validation_for_sales_order(self): + so = make_sales_order(do_not_save=True) + so.items[0].rate = 99.55 + so.save().submit() + pe = get_payment_entry("Sales Order", so.name, bank_account="_Test Cash - _TC") + pe.paid_from = "Debtors - _TC" + pe.paid_amount = 45.55 + pe.references[0].allocated_amount = 45.55 + pe.save().submit() + pe = get_payment_entry("Sales Order", so.name, bank_account="_Test Cash - _TC") + pe.paid_from = "Debtors - _TC" + pe.save().submit() + + so.reload() + self.assertEqual(so.advance_paid, so.rounded_total) + def create_payment_entry(**args): payment_entry = frappe.new_doc("Payment Entry") From 05f657e690bc94856a6c22bd5ac35262c3cabf00 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 28 Aug 2023 14:30:37 +0530 Subject: [PATCH 22/25] test: assert rounded amount is calculated (cherry picked from commit 2fdbe82835625b578b052ac09a5a4f531e2beab5) --- erpnext/accounts/doctype/payment_entry/test_payment_entry.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py index 6b897acc27d..2de009f8c43 100644 --- a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py @@ -1205,6 +1205,7 @@ class TestPaymentEntry(FrappeTestCase): so = make_sales_order(do_not_save=True) so.items[0].rate = 99.55 so.save().submit() + self.assertGreater(so.rounded_total, 0.0) pe = get_payment_entry("Sales Order", so.name, bank_account="_Test Cash - _TC") pe.paid_from = "Debtors - _TC" pe.paid_amount = 45.55 @@ -1212,6 +1213,7 @@ class TestPaymentEntry(FrappeTestCase): pe.save().submit() pe = get_payment_entry("Sales Order", so.name, bank_account="_Test Cash - _TC") pe.paid_from = "Debtors - _TC" + # No validation error should be thrown here. pe.save().submit() so.reload() From 9789b7bdefd453ac647f0cb760b681c2f6d5a76e Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 28 Aug 2023 18:17:50 +0530 Subject: [PATCH 23/25] fix: error in report when data is not available to load chart in report (backport #36842) (#36853) fix: error in report when data is not available to load chart in report (#36842) (cherry picked from commit 3a2933db4d12766d3183893ba2edc9cb5faca527) Co-authored-by: ViralKansodiya <141210323+viralkansodiya@users.noreply.github.com> --- .../assets/report/fixed_asset_register/fixed_asset_register.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py b/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py index bf62a8fb39c..383be973477 100644 --- a/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py +++ b/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py @@ -156,6 +156,8 @@ def get_data(filters): def prepare_chart_data(data, filters): + if not data: + return labels_values_map = {} if filters.filter_based_on not in ("Date Range", "Fiscal Year"): filters_filter_based_on = "Date Range" From d2091cc22c6bdc1b83b67d82c0b305f1654ae6d6 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 29 Aug 2023 11:01:40 +0530 Subject: [PATCH 24/25] fix: create entries for only PR items present in LCV (#36852) fix: create entries for only PR items present in LCV (#36852) * fix: check if item code exists in lcv before creating gle * refactor: use qb to fetch lcv items (cherry picked from commit 26e8b8f95920cdc5b8d541a394a3389c0480a8fa) Co-authored-by: Gursheen Kaur Anand <40693548+GursheenK@users.noreply.github.com> --- .../purchase_invoice/purchase_invoice.py | 29 ++++++------- .../landed_cost_voucher.py | 42 +++++++++++++------ .../purchase_receipt/purchase_receipt.py | 41 +++++++++--------- 3 files changed, 65 insertions(+), 47 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 6161e5b36ee..2340f486f14 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -768,21 +768,22 @@ class PurchaseInvoice(BuyingController): # Amount added through landed-cost-voucher if landed_cost_entries: - for account, amount in landed_cost_entries[(item.item_code, item.name)].items(): - gl_entries.append( - self.get_gl_dict( - { - "account": account, - "against": item.expense_account, - "cost_center": item.cost_center, - "remarks": self.get("remarks") or _("Accounting Entry for Stock"), - "credit": flt(amount["base_amount"]), - "credit_in_account_currency": flt(amount["amount"]), - "project": item.project or self.project, - }, - item=item, + if (item.item_code, item.name) in landed_cost_entries: + for account, amount in landed_cost_entries[(item.item_code, item.name)].items(): + gl_entries.append( + self.get_gl_dict( + { + "account": account, + "against": item.expense_account, + "cost_center": item.cost_center, + "remarks": self.get("remarks") or _("Accounting Entry for Stock"), + "credit": flt(amount["base_amount"]), + "credit_in_account_currency": flt(amount["amount"]), + "project": item.project or self.project, + }, + item=item, + ) ) - ) # sub-contracting warehouse if flt(item.rm_supp_cost): diff --git a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py index 111a0861b71..7f0dc2df9f3 100644 --- a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py +++ b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py @@ -6,6 +6,7 @@ import frappe from frappe import _ from frappe.model.document import Document from frappe.model.meta import get_field_precision +from frappe.query_builder.custom import ConstantColumn from frappe.utils import flt import erpnext @@ -19,19 +20,7 @@ class LandedCostVoucher(Document): self.set("items", []) for pr in self.get("purchase_receipts"): if pr.receipt_document_type and pr.receipt_document: - pr_items = frappe.db.sql( - """select pr_item.item_code, pr_item.description, - pr_item.qty, pr_item.base_rate, pr_item.base_amount, pr_item.name, - pr_item.cost_center, pr_item.is_fixed_asset - from `tab{doctype} Item` pr_item where parent = %s - and exists(select name from tabItem - where name = pr_item.item_code and (is_stock_item = 1 or is_fixed_asset=1)) - """.format( - doctype=pr.receipt_document_type - ), - pr.receipt_document, - as_dict=True, - ) + pr_items = get_pr_items(pr) for d in pr_items: item = self.append("items") @@ -247,3 +236,30 @@ class LandedCostVoucher(Document): ), tuple([item.valuation_rate] + serial_nos), ) + + +def get_pr_items(purchase_receipt): + item = frappe.qb.DocType("Item") + pr_item = frappe.qb.DocType(purchase_receipt.receipt_document_type + " Item") + return ( + frappe.qb.from_(pr_item) + .inner_join(item) + .on(item.name == pr_item.item_code) + .select( + pr_item.item_code, + pr_item.description, + pr_item.qty, + pr_item.base_rate, + pr_item.base_amount, + pr_item.name, + pr_item.cost_center, + pr_item.is_fixed_asset, + ConstantColumn(purchase_receipt.receipt_document_type).as_("receipt_document_type"), + ConstantColumn(purchase_receipt.receipt_document).as_("receipt_document"), + ) + .where( + (pr_item.parent == purchase_receipt.receipt_document) + & ((item.is_stock_item == 1) | (item.is_fixed_asset == 1)) + ) + .run(as_dict=True) + ) diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index c793529e843..4f6c6364a47 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -472,27 +472,28 @@ class PurchaseReceipt(BuyingController): # Amount added through landed-cos-voucher if d.landed_cost_voucher_amount and landed_cost_entries: - for account, amount in landed_cost_entries[(d.item_code, d.name)].items(): - account_currency = get_account_currency(account) - credit_amount = ( - flt(amount["base_amount"]) - if (amount["base_amount"] or account_currency != self.company_currency) - else flt(amount["amount"]) - ) + if (d.item_code, d.name) in landed_cost_entries: + for account, amount in landed_cost_entries[(d.item_code, d.name)].items(): + account_currency = get_account_currency(account) + credit_amount = ( + flt(amount["base_amount"]) + if (amount["base_amount"] or account_currency != self.company_currency) + else flt(amount["amount"]) + ) - self.add_gl_entry( - gl_entries=gl_entries, - account=account, - cost_center=d.cost_center, - debit=0.0, - credit=credit_amount, - remarks=remarks, - against_account=warehouse_account_name, - credit_in_account_currency=flt(amount["amount"]), - account_currency=account_currency, - project=d.project, - item=d, - ) + self.add_gl_entry( + gl_entries=gl_entries, + account=account, + cost_center=d.cost_center, + debit=0.0, + credit=credit_amount, + remarks=remarks, + against_account=warehouse_account_name, + credit_in_account_currency=flt(amount["amount"]), + account_currency=account_currency, + project=d.project, + item=d, + ) if d.rate_difference_with_purchase_invoice and stock_rbnb: account_currency = get_account_currency(stock_rbnb) From 22247cfa179c178ba8d6bbd76dd566f6440ee5fa Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 29 Aug 2023 16:57:59 +0530 Subject: [PATCH 25/25] fix: added valuation field type (Float/Currency) in the filter (backport #36866) (#36868) fix: added valuation field type (Float/Currency) in the filter (#36866) (cherry picked from commit dea802dc4134f7ed1f85ff337ddcb2d521087644) Co-authored-by: rohitwaghchaure --- erpnext/stock/report/stock_balance/stock_balance.js | 8 ++++++++ erpnext/stock/report/stock_balance/stock_balance.py | 5 ++++- erpnext/stock/report/stock_ledger/stock_ledger.js | 10 +++++++++- erpnext/stock/report/stock_ledger/stock_ledger.py | 12 ++++++++---- 4 files changed, 29 insertions(+), 6 deletions(-) diff --git a/erpnext/stock/report/stock_balance/stock_balance.js b/erpnext/stock/report/stock_balance/stock_balance.js index 33ed955a5c4..6de5f00ece8 100644 --- a/erpnext/stock/report/stock_balance/stock_balance.js +++ b/erpnext/stock/report/stock_balance/stock_balance.js @@ -71,6 +71,14 @@ frappe.query_reports["Stock Balance"] = { "width": "80", "options": "Warehouse Type" }, + { + "fieldname": "valuation_field_type", + "label": __("Valuation Field Type"), + "fieldtype": "Select", + "width": "80", + "options": "Currency\nFloat", + "default": "Currency" + }, { "fieldname":"include_uom", "label": __("Include UOM"), diff --git a/erpnext/stock/report/stock_balance/stock_balance.py b/erpnext/stock/report/stock_balance/stock_balance.py index d362d818ebf..7e81a72028f 100644 --- a/erpnext/stock/report/stock_balance/stock_balance.py +++ b/erpnext/stock/report/stock_balance/stock_balance.py @@ -430,9 +430,12 @@ class StockBalanceReport(object): { "label": _("Valuation Rate"), "fieldname": "val_rate", - "fieldtype": "Float", + "fieldtype": self.filters.valuation_field_type or "Currency", "width": 90, "convertible": "rate", + "options": "Company:company:default_currency" + if self.filters.valuation_field_type == "Currency" + else None, }, { "label": _("Company"), diff --git a/erpnext/stock/report/stock_ledger/stock_ledger.js b/erpnext/stock/report/stock_ledger/stock_ledger.js index 0def161d283..b00b422a67a 100644 --- a/erpnext/stock/report/stock_ledger/stock_ledger.js +++ b/erpnext/stock/report/stock_ledger/stock_ledger.js @@ -82,7 +82,15 @@ frappe.query_reports["Stock Ledger"] = { "label": __("Include UOM"), "fieldtype": "Link", "options": "UOM" - } + }, + { + "fieldname": "valuation_field_type", + "label": __("Valuation Field Type"), + "fieldtype": "Select", + "width": "80", + "options": "Currency\nFloat", + "default": "Currency" + }, ], "formatter": function (value, row, column, data, default_formatter) { value = default_formatter(value, row, column, data); diff --git a/erpnext/stock/report/stock_ledger/stock_ledger.py b/erpnext/stock/report/stock_ledger/stock_ledger.py index ed28ed3ee46..eeef39641b0 100644 --- a/erpnext/stock/report/stock_ledger/stock_ledger.py +++ b/erpnext/stock/report/stock_ledger/stock_ledger.py @@ -196,17 +196,21 @@ def get_columns(filters): { "label": _("Avg Rate (Balance Stock)"), "fieldname": "valuation_rate", - "fieldtype": "Float", + "fieldtype": filters.valuation_field_type, "width": 180, - "options": "Company:company:default_currency", + "options": "Company:company:default_currency" + if filters.valuation_field_type == "Currency" + else None, "convertible": "rate", }, { "label": _("Valuation Rate"), "fieldname": "in_out_rate", - "fieldtype": "Float", + "fieldtype": filters.valuation_field_type, "width": 140, - "options": "Company:company:default_currency", + "options": "Company:company:default_currency" + if filters.valuation_field_type == "Currency" + else None, "convertible": "rate", }, {