From 2e52a63b0df0f20ff774131623580377ce57d451 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sun, 4 Jun 2023 19:20:28 +0530 Subject: [PATCH 01/63] feat: Accounting Ledger Preview --- .../doctype/sales_invoice/sales_invoice.js | 1 + erpnext/controllers/stock_controller.py | 13 +++++++ .../public/js/controllers/stock_controller.js | 36 ++++++++++++++++++- 3 files changed, 49 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js index 8cb29505eb2..1ef0c51cbac 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js @@ -88,6 +88,7 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends e } this.show_general_ledger(); + this.show_ledger_preview(); if(doc.update_stock) this.show_stock_ledger(); diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index befde71775a..30dcc7ffa7f 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -15,6 +15,7 @@ from erpnext.accounts.general_ledger import ( make_reverse_gl_entries, process_gl_map, ) +from erpnext.accounts.report.general_ledger.general_ledger import get_columns from erpnext.accounts.utils import get_fiscal_year from erpnext.controllers.accounts_controller import AccountsController from erpnext.stock import get_warehouse_account_map @@ -824,6 +825,18 @@ class StockController(AccountsController): gl_entries.append(self.get_gl_dict(gl_entry, item=item)) +@frappe.whitelist() +def show_ledger_preview(company, doctype, docname): + filters = {"company": company} + doc = frappe.get_doc(doctype, docname) + columns = get_columns(filters) + data = doc.get_gl_entries() + return { + "columns": columns, + "data": data, + } + + def repost_required_for_queue(doc: StockController) -> bool: """check if stock document contains repeated item-warehouse with queue based valuation. diff --git a/erpnext/public/js/controllers/stock_controller.js b/erpnext/public/js/controllers/stock_controller.js index d346357a8f8..919ffda52fc 100644 --- a/erpnext/public/js/controllers/stock_controller.js +++ b/erpnext/public/js/controllers/stock_controller.js @@ -66,7 +66,7 @@ erpnext.stock.StockController = class StockController extends frappe.ui.form.Con } show_general_ledger() { - var me = this; + let me = this; if(this.frm.doc.docstatus > 0) { cur_frm.add_custom_button(__('Accounting Ledger'), function() { frappe.route_options = { @@ -81,4 +81,38 @@ erpnext.stock.StockController = class StockController extends frappe.ui.form.Con }, __("View")); } } + + show_ledger_preview() { + let me = this + if(this.frm.doc.docstatus == 0) { + cur_frm.add_custom_button(__('Accounting Ledger Preview'), function() { + frappe.call({ + "method": "erpnext.controllers.stock_controller.show_ledger_preview", + "args": { + "company": me.frm.doc.company, + "doctype": me.frm.doc.doctype, + "docname": me.frm.doc.name + }, + "callback": function(response) { + me.get_datatable(response); + } + }) + }, __("View")); + } + } + + get_datatable(response) { + const datatable_options = { + columns: response.columns, + data: response.data, + dynamicRowHeight: true, + checkboxColumn: false, + inlineFilters: true, + }; + + this.datatable = new frappe.DataTable( + this.frm.page.main.parent, + datatable_options + ); + } }; From 5155d5bfb2d8909d76f6515fdae1cb0230d27a67 Mon Sep 17 00:00:00 2001 From: DaizyModi Date: Wed, 7 Jun 2023 12:05:17 +0530 Subject: [PATCH 02/63] chore: remove whitelisting for methods not accessed from UI --- erpnext/accounts/doctype/bank_account/bank_account.py | 1 - erpnext/assets/doctype/asset_maintenance/asset_maintenance.py | 1 - .../supplier_scorecard_period/supplier_scorecard_period.py | 1 - .../doctype/plaid_settings/plaid_settings.py | 1 - erpnext/stock/get_item_details.py | 1 - 5 files changed, 5 deletions(-) diff --git a/erpnext/accounts/doctype/bank_account/bank_account.py b/erpnext/accounts/doctype/bank_account/bank_account.py index b91f0f91371..363a2776aa7 100644 --- a/erpnext/accounts/doctype/bank_account/bank_account.py +++ b/erpnext/accounts/doctype/bank_account/bank_account.py @@ -70,7 +70,6 @@ def make_bank_account(doctype, docname): return doc -@frappe.whitelist() def get_party_bank_account(party_type, party): return frappe.db.get_value(party_type, party, "default_bank_account") diff --git a/erpnext/assets/doctype/asset_maintenance/asset_maintenance.py b/erpnext/assets/doctype/asset_maintenance/asset_maintenance.py index 83031415ec3..641d35fa04c 100644 --- a/erpnext/assets/doctype/asset_maintenance/asset_maintenance.py +++ b/erpnext/assets/doctype/asset_maintenance/asset_maintenance.py @@ -42,7 +42,6 @@ class AssetMaintenance(Document): maintenance_log.db_set("maintenance_status", "Cancelled") -@frappe.whitelist() def assign_tasks(asset_maintenance_name, assign_to_member, maintenance_task, next_due_date): team_member = frappe.db.get_value("User", assign_to_member, "email") args = { diff --git a/erpnext/buying/doctype/supplier_scorecard_period/supplier_scorecard_period.py b/erpnext/buying/doctype/supplier_scorecard_period/supplier_scorecard_period.py index a8b76db0931..1967df2a26b 100644 --- a/erpnext/buying/doctype/supplier_scorecard_period/supplier_scorecard_period.py +++ b/erpnext/buying/doctype/supplier_scorecard_period/supplier_scorecard_period.py @@ -99,7 +99,6 @@ def import_string_path(path): return mod -@frappe.whitelist() def make_supplier_scorecard(source_name, target_doc=None): def update_criteria_fields(obj, target, source_parent): target.max_score, target.formula = frappe.db.get_value( diff --git a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py index e57a30a88e1..61d2acefae4 100644 --- a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py +++ b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py @@ -161,7 +161,6 @@ def add_account_subtype(account_subtype): frappe.throw(frappe.get_traceback()) -@frappe.whitelist() def sync_transactions(bank, bank_account): """Sync transactions based on the last integration date as the start date, after sync is completed add the transaction date of the oldest transaction as the last integration date.""" diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index 64650bc2018..4f85ac054d0 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -191,7 +191,6 @@ def process_string_args(args): return args -@frappe.whitelist() def get_item_code(barcode=None, serial_no=None): if barcode: item_code = frappe.db.get_value("Item Barcode", {"barcode": barcode}, fieldname=["parent"]) From 83c46085fbb56e1cfb2981a7d57b8ed313b4979a Mon Sep 17 00:00:00 2001 From: Nikhil Kothari Date: Wed, 7 Jun 2023 14:14:57 +0530 Subject: [PATCH 03/63] feat: added support for mandatory dimensions per account --- .../accounting_dimension_filter.js | 10 ++++ .../accounting_dimension_filter.json | 60 ++++++++----------- .../accounting_dimension_filter.py | 7 ++- 3 files changed, 38 insertions(+), 39 deletions(-) diff --git a/erpnext/accounts/doctype/accounting_dimension_filter/accounting_dimension_filter.js b/erpnext/accounts/doctype/accounting_dimension_filter/accounting_dimension_filter.js index 8a6b021b8ad..6f0b6fcd912 100644 --- a/erpnext/accounts/doctype/accounting_dimension_filter/accounting_dimension_filter.js +++ b/erpnext/accounts/doctype/accounting_dimension_filter/accounting_dimension_filter.js @@ -68,6 +68,16 @@ frappe.ui.form.on('Accounting Dimension Filter', { frm.refresh_field("dimensions"); frm.trigger('setup_filters'); }, + apply_restriction_on_values: function(frm) { + /** If restriction on values is not applied, we should set "allow_or_restrict" to "Restrict" with an empty allowed dimension table. + * Hence it's not "restricted" on any value. + */ + if (!frm.doc.apply_restriction_on_values) { + frm.set_value("allow_or_restrict", "Restrict"); + frm.clear_table("dimensions"); + frm.refresh_field("dimensions"); + } + } }); frappe.ui.form.on('Allowed Dimension', { diff --git a/erpnext/accounts/doctype/accounting_dimension_filter/accounting_dimension_filter.json b/erpnext/accounts/doctype/accounting_dimension_filter/accounting_dimension_filter.json index 0f3fbc0b8d3..1f320b0a1ae 100644 --- a/erpnext/accounts/doctype/accounting_dimension_filter/accounting_dimension_filter.json +++ b/erpnext/accounts/doctype/accounting_dimension_filter/accounting_dimension_filter.json @@ -10,6 +10,7 @@ "disabled", "column_break_2", "company", + "apply_restriction_on_values", "allow_or_restrict", "section_break_4", "accounts", @@ -24,94 +25,80 @@ "fieldtype": "Select", "in_list_view": 1, "label": "Accounting Dimension", - "reqd": 1, - "show_days": 1, - "show_seconds": 1 + "reqd": 1 }, { "fieldname": "column_break_2", - "fieldtype": "Column Break", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "Column Break" }, { "fieldname": "section_break_4", "fieldtype": "Section Break", - "hide_border": 1, - "show_days": 1, - "show_seconds": 1 + "hide_border": 1 }, { "fieldname": "column_break_6", - "fieldtype": "Column Break", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "Column Break" }, { + "depends_on": "eval:doc.apply_restriction_on_values == 1;", "fieldname": "allow_or_restrict", "fieldtype": "Select", "label": "Allow Or Restrict Dimension", "options": "Allow\nRestrict", - "reqd": 1, - "show_days": 1, - "show_seconds": 1 + "reqd": 1 }, { "fieldname": "accounts", "fieldtype": "Table", "label": "Applicable On Account", "options": "Applicable On Account", - "reqd": 1, - "show_days": 1, - "show_seconds": 1 + "reqd": 1 }, { - "depends_on": "eval:doc.accounting_dimension", + "depends_on": "eval:doc.accounting_dimension && doc.apply_restriction_on_values", "fieldname": "dimensions", "fieldtype": "Table", "label": "Applicable Dimension", - "options": "Allowed Dimension", - "reqd": 1, - "show_days": 1, - "show_seconds": 1 + "mandatory_depends_on": "eval:doc.apply_restriction_on_values == 1;", + "options": "Allowed Dimension" }, { "default": "0", "fieldname": "disabled", "fieldtype": "Check", - "label": "Disabled", - "show_days": 1, - "show_seconds": 1 + "label": "Disabled" }, { "fieldname": "company", "fieldtype": "Link", "label": "Company", "options": "Company", - "reqd": 1, - "show_days": 1, - "show_seconds": 1 + "reqd": 1 }, { "fieldname": "dimension_filter_help", "fieldtype": "HTML", - "label": "Dimension Filter Help", - "show_days": 1, - "show_seconds": 1 + "label": "Dimension Filter Help" }, { "fieldname": "section_break_10", - "fieldtype": "Section Break", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "Section Break" + }, + { + "default": "0", + "fieldname": "apply_restriction_on_values", + "fieldtype": "Check", + "label": "Apply restriction on dimension values" } ], "index_web_pages_for_search": 1, "links": [], - "modified": "2021-02-03 12:04:58.678402", + "modified": "2023-06-07 13:41:36.736175", "modified_by": "Administrator", "module": "Accounts", "name": "Accounting Dimension Filter", + "naming_rule": "Expression", "owner": "Administrator", "permissions": [ { @@ -154,5 +141,6 @@ "quick_entry": 1, "sort_field": "modified", "sort_order": "DESC", + "states": [], "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/accounts/doctype/accounting_dimension_filter/accounting_dimension_filter.py b/erpnext/accounts/doctype/accounting_dimension_filter/accounting_dimension_filter.py index 80f736fa5bb..c0573a703cc 100644 --- a/erpnext/accounts/doctype/accounting_dimension_filter/accounting_dimension_filter.py +++ b/erpnext/accounts/doctype/accounting_dimension_filter/accounting_dimension_filter.py @@ -44,12 +44,12 @@ def get_dimension_filter_map(): a.applicable_on_account, d.dimension_value, p.accounting_dimension, p.allow_or_restrict, a.is_mandatory FROM - `tabApplicable On Account` a, `tabAllowed Dimension` d, + `tabApplicable On Account` a, `tabAccounting Dimension Filter` p + LEFT JOIN `tabAllowed Dimension` d ON d.parent = p.name WHERE p.name = a.parent AND p.disabled = 0 - AND p.name = d.parent """, as_dict=1, ) @@ -76,4 +76,5 @@ def build_map(map_object, dimension, account, filter_value, allow_or_restrict, i (dimension, account), {"allowed_dimensions": [], "is_mandatory": is_mandatory, "allow_or_restrict": allow_or_restrict}, ) - map_object[(dimension, account)]["allowed_dimensions"].append(filter_value) + if filter_value: + map_object[(dimension, account)]["allowed_dimensions"].append(filter_value) From e1116bbbbb6987e37d940f1f00e3e334ed0101d0 Mon Sep 17 00:00:00 2001 From: Nikhil Kothari Date: Wed, 7 Jun 2023 14:20:42 +0530 Subject: [PATCH 04/63] fix: added server side check for allow/restrict --- .../accounting_dimension_filter.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/erpnext/accounts/doctype/accounting_dimension_filter/accounting_dimension_filter.py b/erpnext/accounts/doctype/accounting_dimension_filter/accounting_dimension_filter.py index c0573a703cc..2a6c76dd4ae 100644 --- a/erpnext/accounts/doctype/accounting_dimension_filter/accounting_dimension_filter.py +++ b/erpnext/accounts/doctype/accounting_dimension_filter/accounting_dimension_filter.py @@ -8,6 +8,13 @@ from frappe.model.document import Document class AccountingDimensionFilter(Document): + + def before_save(self): + # If restriction is not applied on values, then remove all the dimensions and set allow_or_restrict to Restrict + if not self.apply_restriction_on_values: + self.allow_or_restrict = "Restrict" + self.set('dimensions', []) + def validate(self): self.validate_applicable_accounts() From 83a75844755c141fca8e3885c904581d07196057 Mon Sep 17 00:00:00 2001 From: Nikhil Kothari Date: Wed, 7 Jun 2023 15:00:05 +0530 Subject: [PATCH 05/63] fix: tests now create filters with checkbox enabled --- .../accounting_dimension_filter.json | 4 ++-- .../test_accounting_dimension_filter.py | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/accounting_dimension_filter/accounting_dimension_filter.json b/erpnext/accounts/doctype/accounting_dimension_filter/accounting_dimension_filter.json index 1f320b0a1ae..2bd6c12a0a3 100644 --- a/erpnext/accounts/doctype/accounting_dimension_filter/accounting_dimension_filter.json +++ b/erpnext/accounts/doctype/accounting_dimension_filter/accounting_dimension_filter.json @@ -86,7 +86,7 @@ "fieldtype": "Section Break" }, { - "default": "0", + "default": "1", "fieldname": "apply_restriction_on_values", "fieldtype": "Check", "label": "Apply restriction on dimension values" @@ -94,7 +94,7 @@ ], "index_web_pages_for_search": 1, "links": [], - "modified": "2023-06-07 13:41:36.736175", + "modified": "2023-06-07 14:59:41.869117", "modified_by": "Administrator", "module": "Accounts", "name": "Accounting Dimension Filter", diff --git a/erpnext/accounts/doctype/accounting_dimension_filter/test_accounting_dimension_filter.py b/erpnext/accounts/doctype/accounting_dimension_filter/test_accounting_dimension_filter.py index f13f2f9f279..6aba2ab253f 100644 --- a/erpnext/accounts/doctype/accounting_dimension_filter/test_accounting_dimension_filter.py +++ b/erpnext/accounts/doctype/accounting_dimension_filter/test_accounting_dimension_filter.py @@ -64,6 +64,7 @@ def create_accounting_dimension_filter(): "accounting_dimension": "Cost Center", "allow_or_restrict": "Allow", "company": "_Test Company", + "apply_restriction_on_values": 1, "accounts": [ { "applicable_on_account": "Sales - _TC", @@ -85,6 +86,7 @@ def create_accounting_dimension_filter(): "doctype": "Accounting Dimension Filter", "accounting_dimension": "Department", "allow_or_restrict": "Allow", + "apply_restriction_on_values": 1, "company": "_Test Company", "accounts": [{"applicable_on_account": "Sales - _TC", "is_mandatory": 1}], "dimensions": [{"accounting_dimension": "Department", "dimension_value": "Accounts - _TC"}], From 0bd4de450431b2992b08389570a1526f187f0672 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 7 Jun 2023 22:33:35 +0530 Subject: [PATCH 06/63] fix: Remove special treatment for P&L Accounts --- erpnext/accounts/utils.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index 0ee06e8239c..a5cb3247627 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -237,11 +237,6 @@ def get_balance_on( if not (frappe.flags.ignore_account_permission or ignore_account_permission): acc.check_permission("read") - if report_type == "Profit and Loss": - # for pl accounts, get balance within a fiscal year - cond.append( - "posting_date >= '%s' and voucher_type != 'Period Closing Voucher'" % year_start_date - ) # different filter for group and ledger - improved performance if acc.is_group: cond.append( From f68ab3dfff50a2161858f072b33f75c306a731f4 Mon Sep 17 00:00:00 2001 From: Devin Slauenwhite Date: Wed, 7 Jun 2023 15:14:24 -0400 Subject: [PATCH 07/63] test: reconcile credit against invoice --- .../test_payment_reconciliation.py | 41 ++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py index 3be11ae31a7..e4efc6894fd 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py +++ b/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py @@ -11,10 +11,13 @@ from frappe.utils import add_days, flt, nowdate 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.stock.doctype.item.test_item import create_item +test_dependencies = ["Item"] + class TestPaymentReconciliation(FrappeTestCase): def setUp(self): @@ -163,7 +166,7 @@ class TestPaymentReconciliation(FrappeTestCase): def create_payment_reconciliation(self): pr = frappe.new_doc("Payment Reconciliation") pr.company = self.company - pr.party_type = "Customer" + pr.party_type = self.party_type or "Customer" pr.party = self.customer pr.receivable_payable_account = get_party_account(pr.party_type, pr.party, pr.company) pr.from_invoice_date = pr.to_invoice_date = pr.from_payment_date = pr.to_payment_date = nowdate() @@ -890,6 +893,42 @@ class TestPaymentReconciliation(FrappeTestCase): self.assertEqual(pr.allocation[0].allocated_amount, 85) self.assertEqual(pr.allocation[0].difference_amount, 0) + def test_reconciliation_purchase_invoice_against_return(self): + pi = make_purchase_invoice( + supplier="_Test Supplier USD", currency="USD", conversion_rate=50 + ).submit() + + pi_return = frappe.get_doc(pi.as_dict()) + pi_return.name = None + pi_return.docstatus = 0 + pi_return.is_return = 1 + pi_return.conversion_rate = 80 + pi_return.items[0].qty = -pi_return.items[0].qty + pi_return.submit() + + self.company = "_Test Company" + self.party_type = "Supplier" + self.customer = "_Test Supplier USD" + + pr = self.create_payment_reconciliation() + pr.get_unreconciled_entries() + + invoices = [] + payments = [] + for invoice in pr.invoices: + if invoice.invoice_number == pi.name: + invoices.append(invoice.as_dict()) + break + for payment in pr.payments: + if payment.reference_name == pi_return.name: + payments.append(payment.as_dict()) + break + + pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments})) + + # Should not raise frappe.exceptions.ValidationError: Total Debit must be equal to Total Credit. + pr.reconcile() + def make_customer(customer_name, currency=None): if not frappe.db.exists("Customer", customer_name): From 7973951c370de0bc95c82ed132b109cdade9f2b9 Mon Sep 17 00:00:00 2001 From: Devin Slauenwhite Date: Wed, 7 Jun 2023 15:55:16 -0400 Subject: [PATCH 08/63] fix: missing attribute error --- .../payment_reconciliation/test_payment_reconciliation.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py index e4efc6894fd..2ac7df0e39b 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py +++ b/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py @@ -166,7 +166,9 @@ class TestPaymentReconciliation(FrappeTestCase): def create_payment_reconciliation(self): pr = frappe.new_doc("Payment Reconciliation") pr.company = self.company - pr.party_type = self.party_type or "Customer" + pr.party_type = ( + self.party_type if hasattr(self, "party_type") and self.party_type else "Customer" + ) pr.party = self.customer pr.receivable_payable_account = get_party_account(pr.party_type, pr.party, pr.company) pr.from_invoice_date = pr.to_invoice_date = pr.from_payment_date = pr.to_payment_date = nowdate() From 54935438e127d984ca28ddac4cda84e090e7f72a Mon Sep 17 00:00:00 2001 From: Devin Slauenwhite Date: Wed, 7 Jun 2023 15:55:37 -0400 Subject: [PATCH 09/63] fix: reconcile invoice against credit note --- .../payment_reconciliation.py | 29 +++++++++++++++---- 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py index cc2b9420cc2..77adf459118 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py +++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py @@ -316,6 +316,7 @@ class PaymentReconciliation(Document): entry_list = [] dr_or_cr_notes = [] + difference_entries = [] for row in self.get("allocation"): reconciled_entry = [] if row.invoice_number and row.allocated_amount: @@ -328,13 +329,15 @@ class PaymentReconciliation(Document): reconciled_entry.append(payment_details) if payment_details.difference_amount: - self.make_difference_entry(payment_details) + difference_entries.append( + self.make_difference_entry(payment_details, do_not_save_and_submit=bool(dr_or_cr_notes)) + ) if entry_list: reconcile_against_document(entry_list, skip_ref_details_update_for_pe) if dr_or_cr_notes: - reconcile_dr_cr_note(dr_or_cr_notes, self.company) + reconcile_dr_cr_note(dr_or_cr_notes, difference_entries, self.company) @frappe.whitelist() def reconcile(self): @@ -362,7 +365,7 @@ class PaymentReconciliation(Document): self.get_unreconciled_entries() - def make_difference_entry(self, row): + def make_difference_entry(self, row, do_not_save_and_submit=False): journal_entry = frappe.new_doc("Journal Entry") journal_entry.voucher_type = "Exchange Gain Or Loss" journal_entry.company = self.company @@ -410,8 +413,11 @@ class PaymentReconciliation(Document): journal_entry.append("accounts", journal_account) - journal_entry.save() - journal_entry.submit() + if not do_not_save_and_submit: + journal_entry.save() + journal_entry.submit() + + return journal_entry def get_payment_details(self, row, dr_or_cr): return frappe._dict( @@ -577,7 +583,14 @@ class PaymentReconciliation(Document): return condition -def reconcile_dr_cr_note(dr_cr_notes, company): +def reconcile_dr_cr_note(dr_cr_notes, difference_entries, company): + def find_difference_entry(voucher_type, voucher_no): + for jv in difference_entries: + accounts = iter(jv.accounts) + for account in accounts: + if account.reference_type == voucher_type and account.reference_name == voucher_no: + return next(accounts) + for inv in dr_cr_notes: voucher_type = "Credit Note" if inv.voucher_type == "Sales Invoice" else "Debit Note" @@ -622,5 +635,9 @@ def reconcile_dr_cr_note(dr_cr_notes, company): ], } ) + + if difference_entry := find_difference_entry(inv.against_voucher_type, inv.against_voucher): + jv.append("accounts", difference_entry) + jv.flags.ignore_mandatory = True jv.submit() From e30c3eafefca33afd91e2e473750a1582a3b88ad Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 12 Jun 2023 11:46:51 +0530 Subject: [PATCH 10/63] fix: Stock ledger preview --- .../doctype/sales_invoice/sales_invoice.json | 32 ++++++++- erpnext/controllers/buying_controller.py | 1 + erpnext/controllers/stock_controller.py | 69 +++++++++++++++++-- .../public/js/controllers/stock_controller.js | 15 ++-- 4 files changed, 104 insertions(+), 13 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json index 6a65b30ceb0..15be2e71baa 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json @@ -211,7 +211,12 @@ "is_discounted", "remarks", "repost_required", - "connections_tab" + "connections_tab", + "ledger_preview", + "accounting_ledger_section", + "accounting_ledger_preview_html", + "stock_ledger_section", + "stock_ledger_preview_html" ], "fields": [ { @@ -2142,6 +2147,29 @@ "fieldname": "use_company_roundoff_cost_center", "fieldtype": "Check", "label": "Use Company default Cost Center for Round off" + }, + { + "fieldname": "ledger_preview", + "fieldtype": "Tab Break", + "label": "Ledger Preview" + }, + { + "fieldname": "accounting_ledger_section", + "fieldtype": "Section Break", + "label": "Accounting Ledger" + }, + { + "fieldname": "accounting_ledger_preview_html", + "fieldtype": "HTML" + }, + { + "fieldname": "stock_ledger_section", + "fieldtype": "Section Break", + "label": "Stock Ledger" + }, + { + "fieldname": "stock_ledger_preview_html", + "fieldtype": "HTML" } ], "icon": "fa fa-file-text", @@ -2154,7 +2182,7 @@ "link_fieldname": "consolidated_invoice" } ], - "modified": "2023-04-28 14:15:59.901154", + "modified": "2023-06-11 11:18:14.024258", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice", diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py index e15b61287eb..efddae7e70b 100644 --- a/erpnext/controllers/buying_controller.py +++ b/erpnext/controllers/buying_controller.py @@ -511,6 +511,7 @@ class BuyingController(SubcontractingController): if self.get("is_old_subcontracting_flow"): self.make_sl_entries_for_supplier_warehouse(sl_entries) + self.make_sl_entries( sl_entries, allow_negative_stock=allow_negative_stock, diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index 30dcc7ffa7f..a81c03615ee 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -15,7 +15,6 @@ from erpnext.accounts.general_ledger import ( make_reverse_gl_entries, process_gl_map, ) -from erpnext.accounts.report.general_ledger.general_ledger import get_columns from erpnext.accounts.utils import get_fiscal_year from erpnext.controllers.accounts_controller import AccountsController from erpnext.stock import get_warehouse_account_map @@ -827,16 +826,76 @@ class StockController(AccountsController): @frappe.whitelist() def show_ledger_preview(company, doctype, docname): + from erpnext.accounts.report.general_ledger.general_ledger import get_columns as get_gl_columns + from erpnext.stock.report.stock_ledger.stock_ledger import get_columns as get_sl_columns + + frappe.db.savepoint("show_ledger_preview") + filters = {"company": company} doc = frappe.get_doc(doctype, docname) - columns = get_columns(filters) - data = doc.get_gl_entries() + + datatable_sl_columns = [] + datatable_sl_data = [] + + if doc.update_stock or doc.doctype in ("Purchase Receipt", "Delivery Note"): + sl_columns = get_sl_columns(filters) + doc.docstatus = 1 + doc.update_stock_ledger() + sl_entries = get_sl_entries_for_preview(doc.doctype, doc.name) + datatable_sl_columns = get_columns(sl_columns) + datatable_sl_data = get_data(sl_columns, sl_entries) + + doc.docstatus = 1 + gl_columns = get_gl_columns(filters) + doc.make_gl_entries() + gl_data = get_gl_entries_for_preview(doc.doctype, doc.name) + + datatable_gl_columns = get_columns(gl_columns) + datatable_gl_data = get_data(gl_columns, gl_data) + + frappe.db.rollback(save_point="show_ledger_preview") + return { - "columns": columns, - "data": data, + "gl_columns": datatable_gl_columns, + "gl_data": datatable_gl_data, + "sl_columns": datatable_sl_columns, + "sl_data": datatable_sl_data, } +def get_sl_entries_for_preview(doctype, docname): + return frappe.get_all( + "Stock Ledger Entry", filters={"voucher_type": doctype, "voucher_no": docname}, fields=["*"] + ) + + +def get_gl_entries_for_preview(doctype, docname): + return frappe.get_all( + "GL Entry", filters={"voucher_type": doctype, "voucher_no": docname}, fields=["*"] + ) + + +def get_columns(raw_columns): + return [ + {"name": d.get("label"), "editable": False, "width": 100} + for d in raw_columns + if not d.get("hidden") + ] + + +def get_data(raw_columns, raw_data): + datatable_data = [] + for row in raw_data: + data_row = [] + for column in raw_columns: + if not column.get("hidden"): + data_row.append(row.get(column.get("fieldname"))) + + datatable_data.append(data_row) + + return datatable_data + + def repost_required_for_queue(doc: StockController) -> bool: """check if stock document contains repeated item-warehouse with queue based valuation. diff --git a/erpnext/public/js/controllers/stock_controller.js b/erpnext/public/js/controllers/stock_controller.js index 919ffda52fc..0ef2e6eb697 100644 --- a/erpnext/public/js/controllers/stock_controller.js +++ b/erpnext/public/js/controllers/stock_controller.js @@ -94,24 +94,27 @@ erpnext.stock.StockController = class StockController extends frappe.ui.form.Con "docname": me.frm.doc.name }, "callback": function(response) { - me.get_datatable(response); + console.log(response.message); + me.get_datatable(response.message.gl_columns, response.message.gl_data, me.frm.get_field("accounting_ledger_preview_html").wrapper); + me.get_datatable(response.message.sl_columns, response.message.sl_data, me.frm.get_field("stock_ledger_preview_html").wrapper); + me.frm.scroll_to_field("accounting_ledger_preview_html"); } }) }, __("View")); } } - get_datatable(response) { + get_datatable(columns, data, wrapper) { const datatable_options = { - columns: response.columns, - data: response.data, + columns: columns, + data: data, dynamicRowHeight: true, checkboxColumn: false, inlineFilters: true, }; - this.datatable = new frappe.DataTable( - this.frm.page.main.parent, + new frappe.DataTable( + wrapper, datatable_options ); } From 011ac131cfdbb7ae75dcebc92412c43a3de8cd92 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 12 Jun 2023 18:42:49 +0530 Subject: [PATCH 11/63] fix: Add column values --- erpnext/controllers/stock_controller.py | 133 +++++++++++++----- .../public/js/controllers/stock_controller.js | 1 - 2 files changed, 95 insertions(+), 39 deletions(-) diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index a81c03615ee..3277721126d 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -826,60 +826,118 @@ class StockController(AccountsController): @frappe.whitelist() def show_ledger_preview(company, doctype, docname): - from erpnext.accounts.report.general_ledger.general_ledger import get_columns as get_gl_columns - from erpnext.stock.report.stock_ledger.stock_ledger import get_columns as get_sl_columns - frappe.db.savepoint("show_ledger_preview") filters = {"company": company} doc = frappe.get_doc(doctype, docname) - datatable_sl_columns = [] - datatable_sl_data = [] - - if doc.update_stock or doc.doctype in ("Purchase Receipt", "Delivery Note"): - sl_columns = get_sl_columns(filters) - doc.docstatus = 1 - doc.update_stock_ledger() - sl_entries = get_sl_entries_for_preview(doc.doctype, doc.name) - datatable_sl_columns = get_columns(sl_columns) - datatable_sl_data = get_data(sl_columns, sl_entries) - - doc.docstatus = 1 - gl_columns = get_gl_columns(filters) - doc.make_gl_entries() - gl_data = get_gl_entries_for_preview(doc.doctype, doc.name) - - datatable_gl_columns = get_columns(gl_columns) - datatable_gl_data = get_data(gl_columns, gl_data) + sl_columns, sl_data = get_stock_ledger_preview(doc, filters) + gl_columns, gl_data = get_accounting_ledger_preview(doc, filters) frappe.db.rollback(save_point="show_ledger_preview") return { - "gl_columns": datatable_gl_columns, - "gl_data": datatable_gl_data, - "sl_columns": datatable_sl_columns, - "sl_data": datatable_sl_data, + "gl_columns": gl_columns, + "gl_data": gl_data, + "sl_columns": sl_columns, + "sl_data": sl_data, } -def get_sl_entries_for_preview(doctype, docname): +def get_accounting_ledger_preview(doc, filters): + from erpnext.accounts.report.general_ledger.general_ledger import get_columns as get_gl_columns + + gl_columns, gl_data = [], [] + fields = [ + "posting_date", + "account", + "debit", + "credit", + "against", + "party", + "party_type", + "against_voucher_type", + "against_voucher", + ] + + doc.docstatus = 1 + doc.make_gl_entries() + columns = get_gl_columns(filters) + gl_entries = get_gl_entries_for_preview(doc.doctype, doc.name, fields) + + gl_columns = get_columns(columns, fields) + gl_data = get_data(fields, gl_entries) + + return gl_columns, gl_data + + +def get_stock_ledger_preview(doc, filters): + from erpnext.stock.report.stock_ledger.stock_ledger import get_columns as get_sl_columns + + sl_columns, sl_data = [], [] + fields = [ + "item_code", + "stock_uom", + "actual_qty", + "qty_after_transaction", + "warehouse", + "incoming_rate", + "valuation_rate", + "stock_value", + "stock_value_difference", + ] + columns_fields = [ + "item_code", + "stock_uom", + "in_qty", + "out_qty", + "qty_after_transaction", + "warehouse", + "incoming_rate", + "valuation_rate", + "stock_value", + "stock_value_difference", + ] + + if doc.update_stock or doc.doctype in ("Purchase Receipt", "Delivery Note"): + doc.docstatus = 1 + doc.update_stock_ledger() + columns = get_sl_columns(filters) + sl_entries = get_sl_entries_for_preview(doc.doctype, doc.name, fields) + + sl_columns = get_columns(columns, columns_fields) + sl_data = get_data(columns_fields, sl_entries) + + return sl_columns, sl_data + + +def get_sl_entries_for_preview(doctype, docname, fields): + sl_entries = frappe.get_all( + "Stock Ledger Entry", filters={"voucher_type": doctype, "voucher_no": docname}, fields=fields + ) + + for entry in sl_entries: + if entry.actual_qty > 0: + entry["in_qty"] = entry.actual_qty + entry["out_qty"] = 0 + else: + entry["out_qty"] = abs(entry.actual_qty) + entry["in_qty"] = 0 + + return sl_entries + + +def get_gl_entries_for_preview(doctype, docname, fields): return frappe.get_all( - "Stock Ledger Entry", filters={"voucher_type": doctype, "voucher_no": docname}, fields=["*"] + "GL Entry", filters={"voucher_type": doctype, "voucher_no": docname}, fields=fields ) -def get_gl_entries_for_preview(doctype, docname): - return frappe.get_all( - "GL Entry", filters={"voucher_type": doctype, "voucher_no": docname}, fields=["*"] - ) - - -def get_columns(raw_columns): +def get_columns(raw_columns, fields): return [ - {"name": d.get("label"), "editable": False, "width": 100} + {"name": d.get("label"), "editable": False, "width": 110} for d in raw_columns - if not d.get("hidden") + if not d.get("hidden") and d.get("fieldname") in fields ] @@ -888,8 +946,7 @@ def get_data(raw_columns, raw_data): for row in raw_data: data_row = [] for column in raw_columns: - if not column.get("hidden"): - data_row.append(row.get(column.get("fieldname"))) + data_row.append(row.get(column) or "") datatable_data.append(data_row) diff --git a/erpnext/public/js/controllers/stock_controller.js b/erpnext/public/js/controllers/stock_controller.js index 0ef2e6eb697..0a14ed79359 100644 --- a/erpnext/public/js/controllers/stock_controller.js +++ b/erpnext/public/js/controllers/stock_controller.js @@ -94,7 +94,6 @@ erpnext.stock.StockController = class StockController extends frappe.ui.form.Con "docname": me.frm.doc.name }, "callback": function(response) { - console.log(response.message); me.get_datatable(response.message.gl_columns, response.message.gl_data, me.frm.get_field("accounting_ledger_preview_html").wrapper); me.get_datatable(response.message.sl_columns, response.message.sl_data, me.frm.get_field("stock_ledger_preview_html").wrapper); me.frm.scroll_to_field("accounting_ledger_preview_html"); From b3d565c91faca2b06d6814a6ea69e0191b267b7e Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Mon, 19 Jun 2023 19:54:03 +0530 Subject: [PATCH 12/63] feat: Provision to send Accounts Receivable Reports using Process Statement of Accounts Issue #35707 --- .../process_statement_of_accounts.html | 2 +- .../process_statement_of_accounts.js | 14 + .../process_statement_of_accounts.json | 67 ++- .../process_statement_of_accounts.py | 191 ++++++--- .../accounts_receivable.html | 399 ++++++++++-------- 5 files changed, 429 insertions(+), 244 deletions(-) diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html index 03abc93e0b8..5307ccb1931 100644 --- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html +++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html @@ -1,6 +1,6 @@
- {% if letter_head %} + {% if letter_head.content %}
{{ letter_head.content }}

{% endif %} diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.js b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.js index 7dd5ef36f29..cec48c1d56d 100644 --- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.js +++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.js @@ -65,6 +65,20 @@ frappe.ui.form.on('Process Statement Of Accounts', { frm.set_value('to_date', frappe.datetime.get_today()); } }, + report: function(frm){ + let filters = { + 'company': frm.doc.company, + } + if(frm.doc.report == 'Accounts Receivable'){ + filters['account_type'] = 'Receivable'; + } + frm.set_query("account", function() { + return { + filters: filters + }; + }); + + }, customer_collection: function(frm){ frm.set_value('collection_name', ''); if(frm.doc.customer_collection){ diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.json b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.json index e23620fd4e5..70e810439c2 100644 --- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.json +++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.json @@ -6,17 +6,24 @@ "editable_grid": 1, "engine": "InnoDB", "field_order": [ + "report", "section_break_11", "from_date", + "posting_date", "company", "account", "group_by", "cost_center", + "territory", "column_break_14", "to_date", "finance_book", "currency", "project", + "payment_terms_template", + "sales_partner", + "sales_person", + "based_on_payment_terms", "section_break_3", "customer_collection", "collection_name", @@ -67,14 +74,14 @@ "reqd": 1 }, { - "depends_on": "eval:doc.enable_auto_email == 0;", + "depends_on": "eval:(doc.enable_auto_email == 0 && doc.report == 'General Ledger');", "fieldname": "from_date", "fieldtype": "Date", "label": "From Date", "mandatory_depends_on": "eval:doc.frequency == '';" }, { - "depends_on": "eval:doc.enable_auto_email == 0;", + "depends_on": "eval:(doc.enable_auto_email == 0 && doc.report == 'General Ledger');", "fieldname": "to_date", "fieldtype": "Date", "label": "To Date", @@ -87,6 +94,7 @@ "options": "PSOA Cost Center" }, { + "depends_on": "eval: (doc.report == 'General Ledger');", "fieldname": "project", "fieldtype": "Table MultiSelect", "label": "Project", @@ -104,7 +112,7 @@ { "fieldname": "section_break_11", "fieldtype": "Section Break", - "label": "General Ledger Filters" + "label": "Report Filters" }, { "fieldname": "column_break_14", @@ -164,12 +172,14 @@ }, { "default": "Group by Voucher (Consolidated)", + "depends_on": "eval:(doc.report == 'General Ledger');", "fieldname": "group_by", "fieldtype": "Select", "label": "Group By", "options": "\nGroup by Voucher\nGroup by Voucher (Consolidated)" }, { + "depends_on": "eval: (doc.report == 'General Ledger');", "fieldname": "currency", "fieldtype": "Link", "label": "Currency", @@ -297,6 +307,7 @@ }, { "default": "0", + "depends_on": "eval: (doc.report == 'General Ledger');", "fieldname": "show_net_values_in_party_account", "fieldtype": "Check", "label": "Show Net Values in Party Account" @@ -310,10 +321,58 @@ { "fieldname": "column_break_ocfq", "fieldtype": "Column Break" + }, + { + "fieldname": "report", + "fieldtype": "Select", + "label": "Report", + "options": "General Ledger\nAccounts Receivable" + }, + { + "default": "Today", + "depends_on": "eval:(doc.report == 'Accounts Receivable');", + "fieldname": "posting_date", + "fieldtype": "Date", + "label": "Posting Date" + }, + { + "depends_on": "eval: (doc.report == 'Accounts Receivable');", + "fieldname": "payment_terms_template", + "fieldtype": "Link", + "label": "Payment Terms Template", + "options": "Payment Terms Template" + }, + { + "depends_on": "eval: (doc.report == 'Accounts Receivable');", + "fieldname": "sales_partner", + "fieldtype": "Link", + "label": "Sales Partner", + "options": "Sales Partner" + }, + { + "depends_on": "eval: (doc.report == 'Accounts Receivable');", + "fieldname": "sales_person", + "fieldtype": "Link", + "label": "Sales Person", + "options": "Sales Person" + }, + { + "depends_on": "eval: (doc.report == 'Accounts Receivable');", + "fieldname": "territory", + "fieldtype": "Link", + "label": "Territory", + "options": "Territory" + }, + { + "default": "0", + "depends_on": "eval:(doc.report == 'Accounts Receivable');", + "fieldname": "based_on_payment_terms", + "fieldtype": "Check", + "label": "Based On Payment Terms" } ], "links": [], - "modified": "2023-04-26 12:46:43.645455", + "modified": "2023-06-19 18:37:10.040570", "modified_by": "Administrator", "module": "Accounts", "name": "Process Statement Of Accounts", diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py index 67dbe09d0db..db186d81a2d 100644 --- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py +++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py @@ -15,6 +15,7 @@ from frappe.www.printview import get_print_style from erpnext import get_company_currency from erpnext.accounts.party import get_party_account_currency +from erpnext.accounts.report.accounts_receivable.accounts_receivable import execute as get_ar_soa from erpnext.accounts.report.accounts_receivable_summary.accounts_receivable_summary import ( execute as get_ageing, ) @@ -43,29 +44,10 @@ class ProcessStatementOfAccounts(Document): def get_report_pdf(doc, consolidated=True): statement_dict = {} ageing = "" - base_template_path = "frappe/www/printview.html" - template_path = ( - "erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html" - ) for entry in doc.customers: if doc.include_ageing: - ageing_filters = frappe._dict( - { - "company": doc.company, - "report_date": doc.to_date, - "ageing_based_on": doc.ageing_based_on, - "range1": 30, - "range2": 60, - "range3": 90, - "range4": 120, - "customer": entry.customer, - } - ) - col1, ageing = get_ageing(ageing_filters) - - if ageing: - ageing[0]["ageing_based_on"] = doc.ageing_based_on + ageing = set_ageing(doc, entry) tax_id = frappe.get_doc("Customer", entry.customer).tax_id presentation_currency = ( @@ -73,60 +55,25 @@ def get_report_pdf(doc, consolidated=True): or doc.currency or get_company_currency(doc.company) ) - if doc.letter_head: - from frappe.www.printview import get_letter_head - letter_head = get_letter_head(doc, 0) + filters = get_common_filters(doc) - filters = frappe._dict( - { - "from_date": doc.from_date, - "to_date": doc.to_date, - "company": doc.company, - "finance_book": doc.finance_book if doc.finance_book else None, - "account": [doc.account] if doc.account else None, - "party_type": "Customer", - "party": [entry.customer], - "party_name": [entry.customer_name] if entry.customer_name else None, - "presentation_currency": presentation_currency, - "group_by": doc.group_by, - "currency": doc.currency, - "cost_center": [cc.cost_center_name for cc in doc.cost_center], - "project": [p.project_name for p in doc.project], - "show_opening_entries": 0, - "include_default_book_entries": 0, - "tax_id": tax_id if tax_id else None, - "show_net_values_in_party_account": doc.show_net_values_in_party_account, - } - ) - col, res = get_soa(filters) + if doc.report == "General Ledger": + filters.update(get_gl_filters(doc, entry, tax_id, presentation_currency)) + else: + filters.update(get_ar_filters(doc, entry)) - for x in [0, -2, -1]: - res[x]["account"] = res[x]["account"].replace("'", "") + if doc.report == "General Ledger": + col, res = get_soa(filters) + for x in [0, -2, -1]: + res[x]["account"] = res[x]["account"].replace("'", "") + if len(res) == 3: + continue + else: + ar_res = get_ar_soa(filters) + col, res = ar_res[0], ar_res[1] - if len(res) == 3: - continue - - html = frappe.render_template( - template_path, - { - "filters": filters, - "data": res, - "ageing": ageing[0] if (doc.include_ageing and ageing) else None, - "letter_head": letter_head if doc.letter_head else None, - "terms_and_conditions": frappe.db.get_value( - "Terms and Conditions", doc.terms_and_conditions, "terms" - ) - if doc.terms_and_conditions - else None, - }, - ) - - html = frappe.render_template( - base_template_path, - {"body": html, "css": get_print_style(), "title": "Statement For " + entry.customer}, - ) - statement_dict[entry.customer] = html + statement_dict[entry.customer] = get_html(doc, filters, entry, col, res, ageing) if not bool(statement_dict): return False @@ -140,6 +87,110 @@ def get_report_pdf(doc, consolidated=True): return statement_dict +def set_ageing(doc, entry): + ageing_filters = frappe._dict( + { + "company": doc.company, + "report_date": doc.to_date, + "ageing_based_on": doc.ageing_based_on, + "range1": 30, + "range2": 60, + "range3": 90, + "range4": 120, + "customer": entry.customer, + } + ) + col1, ageing = get_ageing(ageing_filters) + + if ageing: + ageing[0]["ageing_based_on"] = doc.ageing_based_on + + return ageing + + +def get_common_filters(doc): + return frappe._dict( + { + "company": doc.company, + "finance_book": doc.finance_book if doc.finance_book else None, + "account": [doc.account] if doc.account else None, + "cost_center": [cc.cost_center_name for cc in doc.cost_center], + } + ) + + +def get_gl_filters(doc, entry, tax_id, presentation_currency): + return { + "from_date": doc.from_date, + "to_date": doc.to_date, + "party_type": "Customer", + "party": [entry.customer], + "party_name": [entry.customer_name] if entry.customer_name else None, + "presentation_currency": presentation_currency, + "group_by": doc.group_by, + "currency": doc.currency, + "project": [p.project_name for p in doc.project], + "show_opening_entries": 0, + "include_default_book_entries": 0, + "tax_id": tax_id if tax_id else None, + "show_net_values_in_party_account": doc.show_net_values_in_party_account, + } + + +def get_ar_filters(doc, entry): + return { + "report_date": doc.posting_date if doc.posting_date else None, + "customer_name": entry.customer, + "payment_terms_template": doc.payment_terms_template if doc.payment_terms_template else None, + "sales_partner": doc.sales_partner if doc.sales_partner else None, + "sales_person": doc.sales_person if doc.sales_person else None, + "territory": doc.territory if doc.territory else None, + "based_on_payment_terms": doc.based_on_payment_terms, + "report_name": "Accounts Receivable", + "ageing_based_on": doc.ageing_based_on, + "range1": 30, + "range2": 60, + "range3": 90, + "range4": 120, + } + + +def get_html(doc, filters, entry, col, res, ageing): + base_template_path = "frappe/www/printview.html" + template_path = ( + "erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html" + if doc.report == "General Ledger" + else "erpnext/accounts/report/accounts_receivable/accounts_receivable.html" + ) + + if doc.letter_head: + from frappe.www.printview import get_letter_head + + letter_head = get_letter_head(doc, 0) + + html = frappe.render_template( + template_path, + { + "filters": filters, + "data": res, + "report": {"report_name": doc.report, "columns": col}, + "ageing": ageing[0] if (doc.include_ageing and ageing) else None, + "letter_head": letter_head if doc.letter_head else None, + "terms_and_conditions": frappe.db.get_value( + "Terms and Conditions", doc.terms_and_conditions, "terms" + ) + if doc.terms_and_conditions + else None, + }, + ) + + html = frappe.render_template( + base_template_path, + {"body": html, "css": get_print_style(), "title": "Statement For " + entry.customer}, + ) + return html + + def get_customers_based_on_territory_or_customer_group(customer_collection, collection_name): fields_dict = { "Customer Group": "customer_group", diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.html b/erpnext/accounts/report/accounts_receivable/accounts_receivable.html index f2bf9424f72..07e1896292d 100644 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.html +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.html @@ -8,51 +8,56 @@ } -

{%= __(report.report_name) %}

+

{{ _(report.report_name) }}

- {% if (filters.customer_name) { %} - {%= filters.customer_name %} - {% } else { %} - {%= filters.customer || filters.supplier %} - {% } %} + {% if (filters.customer_name) %} + {{ filters.customer_name }} + {% else %} + {{ filters.customer ~ filters.supplier }} + {% endif %}

- {% if (filters.tax_id) { %} - {%= __("Tax Id: ")%} {%= filters.tax_id %} - {% } %} + {% if (filters.tax_id) %} + {{ _("Tax Id: ") }}{{ filters.tax_id }} + {% endif %}
- {%= __(filters.ageing_based_on) %} - {%= __("Until") %} - {%= frappe.datetime.str_to_user(filters.report_date) %} + {{ _(filters.ageing_based_on) }} + {{ _("Until") }} + {{ frappe.format(filters.report_date, 'Date') }}
- {% if(filters.payment_terms) { %} - {%= __("Payment Terms") %}: {%= filters.payment_terms %} - {% } %} + {% if(filters.payment_terms) %} + {{ _("Payment Terms") }}: {{ filters.payment_terms }} + {% endif %}
- {% if(filters.credit_limit) { %} - {%= __("Credit Limit") %}: {%= format_currency(filters.credit_limit) %} - {% } %} + {% if(filters.credit_limit) %} + {{ _("Credit Limit") }}: {{ frappe.utils.fmt_money(filters.credit_limit) }} + {% endif %}
- {% if(filters.show_future_payments) { %} - {% var balance_row = data.slice(-1).pop(); - var start = report.columns.findIndex((elem) => (elem.fieldname == 'age')); - var range1 = report.columns[start].label; - var range2 = report.columns[start+1].label; - var range3 = report.columns[start+2].label; - var range4 = report.columns[start+3].label; - var range5 = report.columns[start+4].label; - var range6 = report.columns[start+5].label; - %} - {% if(balance_row) { %} + {% if(filters.show_future_payments) %} + {% set balance_row = data.slice(-1).pop() %} + {% for i in report.columns %} + {% if i.fieldname == 'age' %} + {% set elem = i %} + {% endif %} + {% endfor %} + {% set start = report.columns.findIndex(elem) %} + {% set range1 = report.columns[start].label %} + {% set range2 = report.columns[start+1].label %} + {% set range3 = report.columns[start+2].label %} + {% set range4 = report.columns[start+3].label %} + {% set range5 = report.columns[start+4].label %} + {% set range6 = report.columns[start+5].label %} + + {% if(balance_row) %} - + @@ -66,42 +71,42 @@ - - - - - - - - + + + + + + + + - + - + @@ -109,10 +114,10 @@ - + @@ -120,168 +125,224 @@ + {{ frappe.utils.fmt_money(flt(balance_row["outstanding"] - balance_row[("future_amount")]), data[data.length-1]["currency"]) }}
(Amount in {%= data[0]["currency"] || "" %})(Amount in {{ data[0]["currency"] ~ "" }})
{%= __(" ") %}{%= __(range1) %}{%= __(range2) %}{%= __(range3) %}{%= __(range4) %}{%= __(range5) %}{%= __(range6) %}{%= __("Total") %}{{ _(" ") }}{{ _(range1) }}{{ _(range2) }}{{ _(range3) }}{{ _(range4) }}{{ _(range5) }}{{ _(range6) }}{{ _("Total") }}
{%= __("Total Outstanding") %}{{ _("Total Outstanding") }} - {%= format_number(balance_row["age"], null, 2) %} + {{ format_number(balance_row["age"], null, 2) }} - {%= format_currency(balance_row["range1"], data[data.length-1]["currency"]) %} + {{ frappe.utils.fmt_money(balance_row["range1"], data[data.length-1]["currency"]) }} - {%= format_currency(balance_row["range2"], data[data.length-1]["currency"]) %} + {{ frappe.utils.fmt_money(balance_row["range2"], data[data.length-1]["currency"]) }} - {%= format_currency(balance_row["range3"], data[data.length-1]["currency"]) %} + {{ frappe.utils.fmt_money(balance_row["range3"], data[data.length-1]["currency"]) }} - {%= format_currency(balance_row["range4"], data[data.length-1]["currency"]) %} + {{ frappe.utils.fmt_money(balance_row["range4"], data[data.length-1]["currency"]) }} - {%= format_currency(balance_row["range5"], data[data.length-1]["currency"]) %} + {{ frappe.utils.fmt_money(balance_row["range5"], data[data.length-1]["currency"]) }} - {%= format_currency(flt(balance_row["outstanding"]), data[data.length-1]["currency"]) %} + {{ frappe.utils.fmt_money(flt(balance_row["outstanding"]), data[data.length-1]["currency"]) }}
{%= __("Future Payments") %}{{ _("Future Payments") }} - {%= format_currency(flt(balance_row[("future_amount")]), data[data.length-1]["currency"]) %} + {{ frappe.utils.fmt_money(flt(balance_row[("future_amount")]), data[data.length-1]["currency"]) }}
- {% } %} - {% } %} + {% endif %} + {% endif %} - {% if(report.report_name === "Accounts Receivable" || report.report_name === "Accounts Payable") { %} - - + {% if(report.report_name == "Accounts Receivable" or report.report_name == "Accounts Payable") %} + + - {% if(report.report_name === "Accounts Receivable" && filters.show_sales_person) { %} - - - {% } else { %} - - {% } %} - {% if(!filters.show_future_payments) { %} - - {% } %} - - {% if(!filters.show_future_payments) { %} - - - {% } %} - - {% if(filters.show_future_payments) { %} - {% if(report.report_name === "Accounts Receivable") { %} - - {% } %} - - - - {% } %} - {% } else { %} - - - - - - {% } %} + {% if(report.report_name == "Accounts Receivable" and filters.show_sales_person) %} + + + {% else %} + + {% endif %} + {% if not(filters.show_future_payments) %} + + {% endif %} + + {% if not(filters.show_future_payments) %} + + + {% endif %} + + {% if(filters.show_future_payments) %} + {% if(report.report_name == "Accounts Receivable") %} + + {% endif %} + + + + {% endif %} + {% else %} + + + + + + {% endif %} - {% for(var i=0, l=data.length; i - {% if(report.report_name === "Accounts Receivable" || report.report_name === "Accounts Payable") { %} - {% if(data[i]["party"]) { %} - - + {% if(report.report_name == "Accounts Receivable" or report.report_name == "Accounts Payable") %} + {% if(data[i]["party"]) %} + + - {% if(report.report_name === "Accounts Receivable" && filters.show_sales_person) { %} - - {% } %} + {% if(report.report_name == "Accounts Receivable" and filters.show_sales_person) %} + + {% endif %} - {% if(!filters.show_future_payments) { %} + {% if not (filters.show_future_payments) %} - {% } %} + {% endif %} + {{ frappe.utils.fmt_money(data[i]["invoiced"], currency=data[i]["currency"]) }} - {% if(!filters.show_future_payments) { %} + {% if not(filters.show_future_payments) %} + {{ frappe.utils.fmt_money(data[i]["paid"], currency=data[i]["currency"]) }} - {% } %} + {{ frappe.utils.fmt_money(data[i]["credit_note"], currency=data[i]["currency"]) }} + {% endif %} + {{ frappe.utils.fmt_money(data[i]["outstanding"], currency=data[i]["currency"]) }} - {% if(filters.show_future_payments) { %} - {% if(report.report_name === "Accounts Receivable") { %} + {% if(filters.show_future_payments) %} + {% if(report.report_name == "Accounts Receivable") %} - {% } %} - - - - {% } %} - {% } else { %} + {{ data[i]["po_no"] }} + {% endif %} + + + + {% endif %} + {% else %} - {% if(!filters.show_future_payments) { %} + {% if not(filters.show_future_payments) %} - {% } %} - {% if(report.report_name === "Accounts Receivable" && filters.show_sales_person) { %} + {% endif %} + {% if(report.report_name == "Accounts Receivable" and filters.show_sales_person) %} - {% } %} + {% endif %} - + + {{ frappe.utils.fmt_money(data[i]["invoiced"], data[i]["currency"]) }} - {% if(!filters.show_future_payments) { %} + {% if not(filters.show_future_payments) %} - - {% } %} + {{ frappe.utils.fmt_money(data[i]["paid"], currency=data[i]["currency"]) }} + + {% endif %} + {{ frappe.utils.fmt_money(data[i]["outstanding"], currency=data[i]["currency"]) }} - {% if(filters.show_future_payments) { %} - {% if(report.report_name === "Accounts Receivable") { %} + {% if(filters.show_future_payments) %} + {% if(report.report_name == "Accounts Receivable") %} - {% } %} - - - - {% } %} - {% } %} - {% } else { %} - {% if(data[i]["party"]|| " ") { %} - {% if(!data[i]["is_total_row"]) { %} + {{ data[i]["po_no"] }} + {% endif %} + + + + {% endif %} + {% endif %} + {% else %} + {% if(data[i]["party"] or " ") %} + {% if not(data[i]["is_total_row"]) %} - {% } else { %} - - {% } %} - - - - - {% } %} - {% } %} + {% else %} + + {% endif %} + + + + + {% endif %} + {% endif %} - {% } %} + {% endfor %} + + + + + + + +
{%= __("Date") %}{%= __("Age (Days)") %}{{ _("Date") }}{{ _("Age (Days)") }}{%= __("Reference") %}{%= __("Sales Person") %}{%= __("Reference") %}{%= (filters.customer || filters.supplier) ? __("Remarks"): __("Party") %}{%= __("Invoiced Amount") %}{%= __("Paid Amount") %}{%= report.report_name === "Accounts Receivable" ? __('Credit Note') : __('Debit Note') %}{%= __("Outstanding Amount") %}{%= __("Customer LPO No.") %}{%= __("Future Payment Ref") %}{%= __("Future Payment Amount") %}{%= __("Remaining Balance") %}{%= (filters.customer || filters.supplier) ? __("Remarks"): __("Party") %}{%= __("Total Invoiced Amount") %}{%= __("Total Paid Amount") %}{%= report.report_name === "Accounts Receivable Summary" ? __('Credit Note Amount') : __('Debit Note Amount') %}{%= __("Total Outstanding Amount") %}{{ _("Reference") }}{{ _("Sales Person") }}{{ _("Reference") }} + {% if (filters.customer or filters.supplier or filters.customer_name) %} + {{ _("Remarks") }} + {% else %} + {{ _("Party") }} + {% endif %} + {{ _("Invoiced Amount") }}{{ _("Paid Amount") }} + {% if report.report_name == "Accounts Receivable" %} + {{ _('Credit Note') }} + {% else %} + {{ _('Debit Note') }} + {% endif %} + {{ _("Outstanding Amount") }}{{ _("Customer LPO No.") }}{{ _("Future Payment Ref") }}{{ _("Future Payment Amount") }}{{ _("Remaining Balance") }} + {% if (filters.customer or filters.supplier or filters.customer_name) %} + {{ _("Remarks")}} + {% else %} + {{ _("Party") }} + {% endif %} + {{ _("Total Invoiced Amount") }}{{ _("Total Paid Amount") }} + {% if report.report_name == "Accounts Receivable Summary" %} + {{ _('Credit Note Amount') }} + {% else %} + {{ _('Debit Note Amount') }} + {% endif %} + {{ _("Total Outstanding Amount") }}
{%= frappe.datetime.str_to_user(data[i]["posting_date"]) %}{%= data[i]["age"] %}{{ (data[i]["posting_date"]) }}{{ data[i]["age"] }} - {% if(!filters.show_future_payments) { %} - {%= data[i]["voucher_type"] %} + {% if not(filters.show_future_payments) %} + {{ data[i]["voucher_type"] }}
- {% } %} - {%= data[i]["voucher_no"] %} + {% endif %} + {{ data[i]["voucher_no"] }}
{%= data[i]["sales_person"] %}{{ data[i]["sales_person"] }} - {% if(!(filters.customer || filters.supplier)) { %} - {%= data[i]["party"] %} - {% if(data[i]["customer_name"] && data[i]["customer_name"] != data[i]["party"]) { %} -
{%= data[i]["customer_name"] %} - {% } else if(data[i]["supplier_name"] != data[i]["party"]) { %} -
{%= data[i]["supplier_name"] %} - {% } %} - {% } %} + {% if(not(filters.customer or filters.supplier or filters.customer_name)) %} + {{ data[i]["party"] }} + {% if(data[i]["customer_name"] and data[i]["customer_name"] != data[i]["party"]) %} +
{{ data[i]["customer_name"] }} + {% elif(data[i]["supplier_name"] != data[i]["party"]) %} +
{{ data[i]["supplier_name"] }} + {% endif %} + {% endif %}
{% if data[i]["remarks"] %} - {%= __("Remarks") %}: - {%= data[i]["remarks"] %} - {% } %} + {{ _("Remarks") }}: + {{ data[i]["remarks"] }} + {% endif %}
- {%= format_currency(data[i]["invoiced"], data[i]["currency"]) %} - {%= format_currency(data[i]["paid"], data[i]["currency"]) %} - {%= format_currency(data[i]["credit_note"], data[i]["currency"]) %} - {%= format_currency(data[i]["outstanding"], data[i]["currency"]) %} - {%= data[i]["po_no"] %}{%= data[i]["future_ref"] %}{%= format_currency(data[i]["future_amount"], data[i]["currency"]) %}{%= format_currency(data[i]["remaining_balance"], data[i]["currency"]) %}{{ data[i]["future_ref"] }}{{ frappe.utils.fmt_money(data[i]["future_amount"], currency=data[i]["currency"]) }}{{ frappe.utils.fmt_money(data[i]["remaining_balance"], currency=data[i]["currency"]) }}{%= __("Total") %}{{ _("Total") }} - {%= format_currency(data[i]["invoiced"], data[i]["currency"] ) %} - {%= format_currency(data[i]["paid"], data[i]["currency"]) %}{%= format_currency(data[i]["credit_note"], data[i]["currency"]) %} {{ frappe.utils.fmt_money(data[i]["credit_note"], currency=data[i]["currency"]) }} - {%= format_currency(data[i]["outstanding"], data[i]["currency"]) %} - {%= data[i]["po_no"] %}{%= data[i]["future_ref"] %}{%= format_currency(data[i]["future_amount"], data[i]["currency"]) %}{%= format_currency(data[i]["remaining_balance"], data[i]["currency"]) %}{{ data[i]["future_ref"] }}{{ frappe.utils.fmt_money(data[i]["future_amount"], currency=data[i]["currency"]) }}{{ frappe.utils.fmt_money(data[i]["remaining_balance"], currency=data[i]["currency"]) }} - {% if(!(filters.customer || filters.supplier)) { %} - {%= data[i]["party"] %} - {% if(data[i]["customer_name"] && data[i]["customer_name"] != data[i]["party"]) { %} -
{%= data[i]["customer_name"] %} - {% } else if(data[i]["supplier_name"] != data[i]["party"]) { %} -
{%= data[i]["supplier_name"] %} - {% } %} - {% } %} -
{%= __("Remarks") %}: - {%= data[i]["remarks"] %} + {% if(not(filters.customer | filters.supplier)) %} + {{ data[i]["party"] }} + {% if(data[i]["customer_name"] and data[i]["customer_name"] != data[i]["party"]) %} +
{{ data[i]["customer_name"] }} + {% elif(data[i]["supplier_name"] != data[i]["party"]) %} +
{{ data[i]["supplier_name"] }} + {% endif %} + {% endif %} +
{{ _("Remarks") }}: + {{ data[i]["remarks"] }}
{%= __("Total") %}{%= format_currency(data[i]["invoiced"], data[i]["currency"]) %}{%= format_currency(data[i]["paid"], data[i]["currency"]) %}{%= format_currency(data[i]["credit_note"], data[i]["currency"]) %}{%= format_currency(data[i]["outstanding"], data[i]["currency"]) %}{{ _("Total") }}{{ frappe.utils.fmt_money(data[i]["invoiced"], currency=data[i]["currency"]) }}{{ frappe.utils.fmt_money(data[i]["paid"], currency=data[i]["currency"]) }}{{ frappe.utils.fmt_money(data[i]["credit_note"], currency=data[i]["currency"]) }}{{ frappe.utils.fmt_money(data[i]["outstanding"], currency=data[i]["currency"]) }}
{{ frappe.utils.fmt_money(data|sum(attribute="invoiced"), currency=data[0]["currency"]) }}{{ frappe.utils.fmt_money(data|sum(attribute="paid"), currency=data[0]["currency"]) }}{{ frappe.utils.fmt_money(data|sum(attribute="credit_note"), currency=data[0]["currency"]) }}{{ frappe.utils.fmt_money(data|sum(attribute="outstanding"), currency=data[0]["currency"]) }}
-

{{ __("Printed On ") }}{%= frappe.datetime.str_to_user(frappe.datetime.get_datetime_as_string()) %}

+
+ {% if ageing %} +

{{ _("Ageing Report based on ") }} {{ ageing.ageing_based_on }} + {{ _("up to " ) }} {{ frappe.format(filters.report_date, 'Date')}} +

+ + + + + + + + + + + + + + + + + +
30 Days60 Days90 Days120 Days
{{ frappe.utils.fmt_money(ageing.range1, currency=data[0]["currency"]) }}{{ frappe.utils.fmt_money(ageing.range2, currency=data[0]["currency"]) }}{{ frappe.utils.fmt_money(ageing.range3, currency=data[0]["currency"]) }}{{ frappe.utils.fmt_money(ageing.range4, currency=data[0]["currency"]) }}
+ {% endif %} +

{{ _("Printed On ") }}{{ frappe.utils.now() }}

From 1b33afd69987f04c1c92e236c8eeb86bf9e3e76d Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 20 Jun 2023 07:19:59 +0530 Subject: [PATCH 13/63] fix: for zero bal accounts, dr/cr only on currency that has balance --- .../exchange_rate_revaluation.py | 48 +++++++++++++------ 1 file changed, 33 insertions(+), 15 deletions(-) diff --git a/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py b/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py index 5d239c91f71..4926006c965 100644 --- a/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py +++ b/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py @@ -373,6 +373,24 @@ class ExchangeRateRevaluation(Document): "credit": 0, } ) + + journal_entry_accounts.append(journal_account) + + journal_entry_accounts.append( + { + "account": unrealized_exchange_gain_loss_account, + "balance": get_balance_on(unrealized_exchange_gain_loss_account), + "debit": 0, + "credit": 0, + "debit_in_account_currency": abs(d.gain_loss) if d.gain_loss < 0 else 0, + "credit_in_account_currency": abs(d.gain_loss) if d.gain_loss > 0 else 0, + "cost_center": erpnext.get_default_cost_center(self.company), + "exchange_rate": 1, + "reference_type": "Exchange Rate Revaluation", + "reference_name": self.name, + } + ) + elif d.get("balance_in_base_currency") and not d.get("new_balance_in_base_currency"): # Base currency has balance dr_or_cr = "credit" if d.get("balance_in_base_currency") > 0 else "debit" @@ -388,22 +406,22 @@ class ExchangeRateRevaluation(Document): } ) - journal_entry_accounts.append(journal_account) + journal_entry_accounts.append(journal_account) - journal_entry_accounts.append( - { - "account": unrealized_exchange_gain_loss_account, - "balance": get_balance_on(unrealized_exchange_gain_loss_account), - "debit": abs(self.gain_loss_booked) if self.gain_loss_booked < 0 else 0, - "credit": abs(self.gain_loss_booked) if self.gain_loss_booked > 0 else 0, - "debit_in_account_currency": abs(self.gain_loss_booked) if self.gain_loss_booked < 0 else 0, - "credit_in_account_currency": self.gain_loss_booked if self.gain_loss_booked > 0 else 0, - "cost_center": erpnext.get_default_cost_center(self.company), - "exchange_rate": 1, - "reference_type": "Exchange Rate Revaluation", - "reference_name": self.name, - } - ) + journal_entry_accounts.append( + { + "account": unrealized_exchange_gain_loss_account, + "balance": get_balance_on(unrealized_exchange_gain_loss_account), + "debit": abs(d.gain_loss) if d.gain_loss < 0 else 0, + "credit": abs(d.gain_loss) if d.gain_loss > 0 else 0, + "debit_in_account_currency": 0, + "credit_in_account_currency": 0, + "cost_center": erpnext.get_default_cost_center(self.company), + "exchange_rate": 1, + "reference_type": "Exchange Rate Revaluation", + "reference_name": self.name, + } + ) journal_entry.set("accounts", journal_entry_accounts) journal_entry.set_total_debit_credit() From 9d04af9ecc865b77e9408f621eed0c4d933aa543 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 20 Jun 2023 07:21:51 +0530 Subject: [PATCH 14/63] refactor: allow higher precision for new exchange rate --- .../exchange_rate_revaluation_account.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/exchange_rate_revaluation_account/exchange_rate_revaluation_account.json b/erpnext/accounts/doctype/exchange_rate_revaluation_account/exchange_rate_revaluation_account.json index 2968359a0d0..0a7d0579b15 100644 --- a/erpnext/accounts/doctype/exchange_rate_revaluation_account/exchange_rate_revaluation_account.json +++ b/erpnext/accounts/doctype/exchange_rate_revaluation_account/exchange_rate_revaluation_account.json @@ -92,6 +92,7 @@ "fieldtype": "Float", "in_list_view": 1, "label": "New Exchange Rate", + "precision": "9", "reqd": 1 }, { @@ -147,7 +148,7 @@ ], "istable": 1, "links": [], - "modified": "2022-12-29 19:38:52.915295", + "modified": "2023-06-20 07:21:40.743460", "modified_by": "Administrator", "module": "Accounts", "name": "Exchange Rate Revaluation Account", From 4567474418d159e6334c7b69c3a7f7f6ccc9741d Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 20 Jun 2023 07:23:51 +0530 Subject: [PATCH 15/63] refactor: allow '0' rounding allowance --- .../exchange_rate_revaluation/exchange_rate_revaluation.js | 2 +- .../exchange_rate_revaluation/exchange_rate_revaluation.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.js b/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.js index f51b90d8f6a..1ef5c837402 100644 --- a/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.js +++ b/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.js @@ -37,7 +37,7 @@ frappe.ui.form.on('Exchange Rate Revaluation', { validate_rounding_loss: function(frm) { let allowance = frm.doc.rounding_loss_allowance; - if (!(allowance > 0 && allowance < 1)) { + if (!(allowance >= 0 && allowance < 1)) { frappe.throw(__("Rounding Loss Allowance should be between 0 and 1")); } }, diff --git a/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py b/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py index 4926006c965..598db642f33 100644 --- a/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py +++ b/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py @@ -22,7 +22,7 @@ class ExchangeRateRevaluation(Document): self.set_total_gain_loss() def validate_rounding_loss_allowance(self): - if not (self.rounding_loss_allowance > 0 and self.rounding_loss_allowance < 1): + if not (self.rounding_loss_allowance >= 0 and self.rounding_loss_allowance < 1): frappe.throw(_("Rounding Loss Allowance should be between 0 and 1")) def set_total_gain_loss(self): From 6694175a517dc942f056e5b9b9918e55419f85b0 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 20 Jun 2023 07:29:21 +0530 Subject: [PATCH 16/63] refactor: higher precision for rounding loss and allow '0' --- .../exchange_rate_revaluation.json | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.json b/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.json index 2310d1272cd..79428d591b4 100644 --- a/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.json +++ b/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.json @@ -100,15 +100,16 @@ }, { "default": "0.05", - "description": "Only values between 0 and 1 are allowed. \nEx: If allowance is set at 0.07, accounts that have balance of 0.07 in either of the currencies will be considered as zero balance account", + "description": "Only values between [0,1) are allowed. Like {0.00, 0.04, 0.09, ...}\nEx: If allowance is set at 0.07, accounts that have balance of 0.07 in either of the currencies will be considered as zero balance account", "fieldname": "rounding_loss_allowance", "fieldtype": "Float", - "label": "Rounding Loss Allowance" + "label": "Rounding Loss Allowance", + "precision": "9" } ], "is_submittable": 1, "links": [], - "modified": "2023-06-12 21:02:09.818208", + "modified": "2023-06-20 07:29:06.972434", "modified_by": "Administrator", "module": "Accounts", "name": "Exchange Rate Revaluation", From a627d2a38cc6b194694066da256dda28d3c7ddba Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Tue, 20 Jun 2023 15:55:18 +0530 Subject: [PATCH 17/63] fix: keyerror while checking the stock balance report --- erpnext/stock/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/stock/utils.py b/erpnext/stock/utils.py index 402f998677d..02444064c17 100644 --- a/erpnext/stock/utils.py +++ b/erpnext/stock/utils.py @@ -475,7 +475,7 @@ def add_additional_uom_columns(columns, result, include_uom, conversion_factors) for row_idx, row in enumerate(result): for convertible_col, data in convertible_column_map.items(): - conversion_factor = conversion_factors[row.get("item_code")] or 1 + conversion_factor = conversion_factors.get(row.get("item_code")) or 1.0 for_type = data.for_type value_before_conversion = row.get(convertible_col) if for_type == "rate": From 32965f1af9403b20b8d783c6e8f609a565b2864c Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Tue, 20 Jun 2023 16:27:23 +0530 Subject: [PATCH 18/63] fix: stock error for service item --- erpnext/e_commerce/variant_selector/utils.py | 1 + erpnext/templates/generators/item/item_configure.js | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/erpnext/e_commerce/variant_selector/utils.py b/erpnext/e_commerce/variant_selector/utils.py index 1a3e7379281..4466c457436 100644 --- a/erpnext/e_commerce/variant_selector/utils.py +++ b/erpnext/e_commerce/variant_selector/utils.py @@ -162,6 +162,7 @@ def get_next_attribute_and_values(item_code, selected_attributes): product_info = get_item_variant_price_dict(exact_match[0], cart_settings) if product_info: + product_info["is_stock_item"] = frappe.get_cached_value("Item", exact_match[0], "is_stock_item") product_info["allow_items_not_in_stock"] = cint(cart_settings.allow_items_not_in_stock) else: product_info = None diff --git a/erpnext/templates/generators/item/item_configure.js b/erpnext/templates/generators/item/item_configure.js index 613c967e3d6..9beba3fd01e 100644 --- a/erpnext/templates/generators/item/item_configure.js +++ b/erpnext/templates/generators/item/item_configure.js @@ -219,7 +219,8 @@ class ItemConfigure { : '' } - ${available_qty === 0 ? '(' + __('Out of Stock') + ')' : ''} + ${available_qty === 0 && product_info && product_info?.is_stock_item + ? '(' + __('Out of Stock') + ')' : ''}
@@ -236,7 +237,8 @@ class ItemConfigure { `; /* eslint-disable indent */ - if (!product_info?.allow_items_not_in_stock && available_qty === 0) { + if (!product_info?.allow_items_not_in_stock && available_qty === 0 + && product_info && product_info?.is_stock_item) { item_add_to_cart = ''; } From 96c5c7b1dfb953e665c2683ac68ad67b55474bcb Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Thu, 8 Jun 2023 17:08:22 +0530 Subject: [PATCH 19/63] fix: don't allow to make reposting entry for closing stock balance period --- .../repost_item_valuation.py | 41 ++++++++++++++++++- .../test_repost_item_valuation.py | 30 ++++++++++++++ 2 files changed, 70 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py index d5fc710625a..27066b825c1 100644 --- a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py +++ b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py @@ -13,6 +13,7 @@ from frappe.utils.user import get_users_with_role from rq.timeouts import JobTimeoutException import erpnext +from erpnext.accounts.general_ledger import validate_accounting_period from erpnext.accounts.utils import get_future_stock_vouchers, repost_gle_for_stock_vouchers from erpnext.stock.stock_ledger import ( get_affected_transactions, @@ -44,11 +45,49 @@ class RepostItemValuation(Document): self.validate_accounts_freeze() def validate_period_closing_voucher(self): + # Period Closing Voucher year_end_date = self.get_max_year_end_date(self.company) if year_end_date and getdate(self.posting_date) <= getdate(year_end_date): - msg = f"Due to period closing, you cannot repost item valuation before {year_end_date}" + date = frappe.format(year_end_date, "Date") + msg = f"Due to period closing, you cannot repost item valuation before {date}" frappe.throw(_(msg)) + # Accounting Period + if self.voucher_type: + validate_accounting_period( + [ + frappe._dict( + { + "posting_date": self.posting_date, + "company": self.company, + "voucher_type": self.voucher_type, + } + ) + ] + ) + + # Closing Stock Balance + closing_stock = self.get_closing_stock_balance() + if closing_stock and closing_stock[0].name: + name = get_link_to_form("Closing Stock Balance", closing_stock[0].name) + to_date = frappe.format(closing_stock[0].to_date, "Date") + msg = f"Due to closing stock balance {name}, you cannot repost item valuation before {to_date}" + frappe.throw(_(msg)) + + def get_closing_stock_balance(self): + filters = { + "company": self.company, + "status": "Completed", + "docstatus": 1, + "to_date": (">=", self.posting_date), + } + + for field in ["warehouse", "item_code"]: + if self.get(field): + filters.update({field: ("in", ["", self.get(field)])}) + + return frappe.get_all("Closing Stock Balance", fields=["name", "to_date"], filters=filters) + @staticmethod def get_max_year_end_date(company): data = frappe.get_all( diff --git a/erpnext/stock/doctype/repost_item_valuation/test_repost_item_valuation.py b/erpnext/stock/doctype/repost_item_valuation/test_repost_item_valuation.py index 9c4d997b316..1853f45f583 100644 --- a/erpnext/stock/doctype/repost_item_valuation/test_repost_item_valuation.py +++ b/erpnext/stock/doctype/repost_item_valuation/test_repost_item_valuation.py @@ -392,3 +392,33 @@ class TestRepostItemValuation(FrappeTestCase, StockTestMixin): pr.cancel() self.assertTrue(pr.docstatus == 2) self.assertTrue(frappe.db.exists("Repost Item Valuation", {"voucher_no": pr.name})) + + def test_repost_item_valuation_for_closing_stock_balance(self): + from erpnext.stock.doctype.closing_stock_balance.closing_stock_balance import ( + prepare_closing_stock_balance, + ) + + doc = frappe.new_doc("Closing Stock Balance") + doc.company = "_Test Company" + doc.from_date = today() + doc.to_date = today() + doc.submit() + + prepare_closing_stock_balance(doc.name) + doc.load_from_db() + self.assertEqual(doc.docstatus, 1) + self.assertEqual(doc.status, "Completed") + + riv = frappe.new_doc("Repost Item Valuation") + riv.update( + { + "item_code": "_Test Item", + "warehouse": "_Test Warehouse - _TC", + "based_on": "Item and Warehouse", + "posting_date": today(), + "posting_time": "00:01:00", + } + ) + + self.assertRaises(frappe.ValidationError, riv.save) + doc.cancel() From ad758b8d8511853934b7cfcfde42eddeff9a333c Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Wed, 21 Jun 2023 14:19:02 +0530 Subject: [PATCH 20/63] fix: no permission for accounts settings on payment reconciliation --- .../payment_reconciliation.js | 40 ++++++++++--------- .../payment_reconciliation.py | 4 ++ 2 files changed, 26 insertions(+), 18 deletions(-) diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js index 22836776345..89fa15172f1 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js +++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js @@ -85,25 +85,29 @@ erpnext.accounts.PaymentReconciliationController = class PaymentReconciliationCo // check for any running reconciliation jobs if (this.frm.doc.receivable_payable_account) { - frappe.db.get_single_value("Accounts Settings", "auto_reconcile_payments").then((enabled) => { - if(enabled) { - this.frm.call({ - 'method': "erpnext.accounts.doctype.process_payment_reconciliation.process_payment_reconciliation.is_any_doc_running", - "args": { - for_filter: { - company: this.frm.doc.company, - party_type: this.frm.doc.party_type, - party: this.frm.doc.party, - receivable_payable_account: this.frm.doc.receivable_payable_account + this.frm.call({ + doc: this.frm.doc, + method: 'is_auto_process_enabled', + callback: (r) => { + if (r.message) { + this.frm.call({ + 'method': "erpnext.accounts.doctype.process_payment_reconciliation.process_payment_reconciliation.is_any_doc_running", + "args": { + for_filter: { + company: this.frm.doc.company, + party_type: this.frm.doc.party_type, + party: this.frm.doc.party, + receivable_payable_account: this.frm.doc.receivable_payable_account + } } - } - }).then(r => { - if (r.message) { - let doc_link = frappe.utils.get_form_link("Process Payment Reconciliation", r.message, true); - let msg = __("Payment Reconciliation Job: {0} is running for this party. Can't reconcile now.", [doc_link]); - this.frm.dashboard.add_comment(msg, "yellow"); - } - }); + }).then(r => { + if (r.message) { + let doc_link = frappe.utils.get_form_link("Process Payment Reconciliation", r.message, true); + let msg = __("Payment Reconciliation Job: {0} is running for this party. Can't reconcile now.", [doc_link]); + this.frm.dashboard.add_comment(msg, "yellow"); + } + }); + } } }); } diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py index 2c8faecf4b1..2e4e3b0e078 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py +++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py @@ -252,6 +252,10 @@ class PaymentReconciliation(Document): return difference_amount + @frappe.whitelist() + def is_auto_process_enabled(self): + return frappe.db.get_single_value("Accounts Settings", "auto_reconcile_payments") + @frappe.whitelist() def calculate_difference_on_allocation_change(self, payment_entry, invoice, allocated_amount): invoice_exchange_map = self.get_invoice_exchange_map(invoice, payment_entry) From 39a1f4a4c1dbe95ca784f307fd2c6d66b0ec4175 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 21 Jun 2023 15:38:06 +0530 Subject: [PATCH 21/63] fix: issue of asset value_after_depreciation field getting updated twice if workflow is enabled in Journal Entry (backport #35821) (#35826) * Fixes issue of asset value_after_depreciation field getting updated twice if workflow is enabled in Journal Entry (#35821) * Fixes issue of asset value_after_depreciation field getting updated twice if workflow is enabled in Journal Entry * chore: remove unnecessary line break * chore: formatting --------- Co-authored-by: Anand Baburajan (cherry picked from commit 000ebe447991185e4d5a4050b066fa0781575e65) # Conflicts: # erpnext/assets/doctype/asset/depreciation.py * chore: resolve conflicts --------- Co-authored-by: saeedkola Co-authored-by: Anand Baburajan --- erpnext/assets/doctype/asset/depreciation.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/erpnext/assets/doctype/asset/depreciation.py b/erpnext/assets/doctype/asset/depreciation.py index bfef57e4947..259568a24b1 100644 --- a/erpnext/assets/doctype/asset/depreciation.py +++ b/erpnext/assets/doctype/asset/depreciation.py @@ -159,15 +159,15 @@ def make_depreciation_entry(asset_depr_schedule_name, date=None): je.flags.ignore_permissions = True je.flags.planned_depr_entry = True je.save() - if not je.meta.get_workflow(): - je.submit() d.db_set("journal_entry", je.name) - idx = cint(asset_depr_schedule_doc.finance_book_id) - row = asset.get("finance_books")[idx - 1] - row.value_after_depreciation -= d.depreciation_amount - row.db_update() + if not je.meta.get_workflow(): + je.submit() + idx = cint(asset_depr_schedule_doc.finance_book_id) + row = asset.get("finance_books")[idx - 1] + row.value_after_depreciation -= d.depreciation_amount + row.db_update() asset.db_set("depr_entry_posting_status", "Successful") From dcfc86e3afa53e393c4dec74fa3f7d3a01be2aa4 Mon Sep 17 00:00:00 2001 From: phot0n Date: Wed, 21 Jun 2023 16:49:54 +0530 Subject: [PATCH 22/63] fix: use correct fieldname for purchase receipt column in item_wise_purcchase_register report --- .../item_wise_purchase_register.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py b/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py index d34c21348c8..924c14bdb94 100644 --- a/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py +++ b/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py @@ -87,7 +87,7 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum "project": d.project, "company": d.company, "purchase_order": d.purchase_order, - "purchase_receipt": d.purchase_receipt, + "purchase_receipt": purchase_receipt, "expense_account": expense_account, "stock_qty": d.stock_qty, "stock_uom": d.stock_uom, @@ -241,7 +241,7 @@ def get_columns(additional_table_columns, filters): }, { "label": _("Purchase Receipt"), - "fieldname": "Purchase Receipt", + "fieldname": "purchase_receipt", "fieldtype": "Link", "options": "Purchase Receipt", "width": 100, From ed76ee3e161454af1eafa310cd6b207dd20e19fc Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 21 Jun 2023 17:15:46 +0530 Subject: [PATCH 23/63] fix: Move ledger display to dialog --- .../doctype/sales_invoice/sales_invoice.json | 32 ++--------------- .../public/js/controllers/stock_controller.js | 35 +++++++++++++++++-- 2 files changed, 34 insertions(+), 33 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json index 15be2e71baa..61c10d9cd58 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json @@ -211,12 +211,7 @@ "is_discounted", "remarks", "repost_required", - "connections_tab", - "ledger_preview", - "accounting_ledger_section", - "accounting_ledger_preview_html", - "stock_ledger_section", - "stock_ledger_preview_html" + "connections_tab" ], "fields": [ { @@ -2147,29 +2142,6 @@ "fieldname": "use_company_roundoff_cost_center", "fieldtype": "Check", "label": "Use Company default Cost Center for Round off" - }, - { - "fieldname": "ledger_preview", - "fieldtype": "Tab Break", - "label": "Ledger Preview" - }, - { - "fieldname": "accounting_ledger_section", - "fieldtype": "Section Break", - "label": "Accounting Ledger" - }, - { - "fieldname": "accounting_ledger_preview_html", - "fieldtype": "HTML" - }, - { - "fieldname": "stock_ledger_section", - "fieldtype": "Section Break", - "label": "Stock Ledger" - }, - { - "fieldname": "stock_ledger_preview_html", - "fieldtype": "HTML" } ], "icon": "fa fa-file-text", @@ -2182,7 +2154,7 @@ "link_fieldname": "consolidated_invoice" } ], - "modified": "2023-06-11 11:18:14.024258", + "modified": "2023-06-21 16:02:18.988799", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice", diff --git a/erpnext/public/js/controllers/stock_controller.js b/erpnext/public/js/controllers/stock_controller.js index 0a14ed79359..ff593485be2 100644 --- a/erpnext/public/js/controllers/stock_controller.js +++ b/erpnext/public/js/controllers/stock_controller.js @@ -94,15 +94,44 @@ erpnext.stock.StockController = class StockController extends frappe.ui.form.Con "docname": me.frm.doc.name }, "callback": function(response) { - me.get_datatable(response.message.gl_columns, response.message.gl_data, me.frm.get_field("accounting_ledger_preview_html").wrapper); - me.get_datatable(response.message.sl_columns, response.message.sl_data, me.frm.get_field("stock_ledger_preview_html").wrapper); - me.frm.scroll_to_field("accounting_ledger_preview_html"); + me.make_dialog(response.message); } }) }, __("View")); } } + make_dialog(data) { + let me = this; + let gl_columns = data.gl_columns; + let gl_data = data.gl_data; + let sl_columns = data.sl_columns; + let sl_data = data.sl_data; + + let dialog = new frappe.ui.Dialog({ + "size": "extra-large", + "title": __("Ledger Preview"), + "fields": [ + { + "fieldtype": "HTML", + "fieldname": "accounting_ledger_preview_html", + "label": __("Accounting Ledger"), + }, + { + "fieldtype": "HTML", + "fieldname": "stock_ledger_preview_html", + "label": __("Stock Ledger"), + } + ] + }); + + setTimeout(function() { + me.get_datatable(gl_columns, gl_data, dialog.get_field("accounting_ledger_preview_html").wrapper); + }, 200); + + dialog.show(); + } + get_datatable(columns, data, wrapper) { const datatable_options = { columns: columns, From 756dbe7ce89838a9cbff2e13e03c67f6f7c6e0ff Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Wed, 21 Jun 2023 17:40:48 +0530 Subject: [PATCH 24/63] refactor: return against rejected warehouse --- .../controllers/sales_and_purchase_return.py | 13 +++++- .../purchase_receipt/purchase_receipt.js | 46 +++++++++++++++++-- .../purchase_receipt/purchase_receipt.py | 7 +++ .../purchase_receipt/test_purchase_receipt.py | 27 +++++++++++ 4 files changed, 88 insertions(+), 5 deletions(-) diff --git a/erpnext/controllers/sales_and_purchase_return.py b/erpnext/controllers/sales_and_purchase_return.py index 818c7894b7d..954668055e1 100644 --- a/erpnext/controllers/sales_and_purchase_return.py +++ b/erpnext/controllers/sales_and_purchase_return.py @@ -320,7 +320,9 @@ def get_returned_qty_map_for_row(return_against, party, row_name, doctype): return data[0] -def make_return_doc(doctype: str, source_name: str, target_doc=None): +def make_return_doc( + doctype: str, source_name: str, target_doc=None, return_against_rejected_qty=False +): from frappe.model.mapper import get_mapped_doc company = frappe.db.get_value("Delivery Note", source_name, "company") @@ -471,7 +473,7 @@ def make_return_doc(doctype: str, source_name: str, target_doc=None): target_doc.qty = -1 * flt(source_doc.qty - (returned_qty_map.get("qty") or 0)) - if hasattr(target_doc, "stock_qty"): + if hasattr(target_doc, "stock_qty") and not return_against_rejected_qty: target_doc.stock_qty = -1 * flt( source_doc.stock_qty - (returned_qty_map.get("stock_qty") or 0) ) @@ -490,6 +492,13 @@ def make_return_doc(doctype: str, source_name: str, target_doc=None): target_doc.rejected_warehouse = source_doc.rejected_warehouse target_doc.purchase_receipt_item = source_doc.name + if doctype == "Purchase Receipt" and return_against_rejected_qty: + target_doc.qty = -1 * flt(source_doc.rejected_qty - (returned_qty_map.get("qty") or 0)) + target_doc.rejected_qty = 0.0 + target_doc.rejected_warehouse = "" + target_doc.warehouse = source_doc.rejected_warehouse + target_doc.received_qty = target_doc.qty + elif doctype == "Purchase Invoice": returned_qty_map = get_returned_qty_map_for_row( source_parent.name, source_parent.supplier, source_doc.name, doctype diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js index 312c166f8b7..dc3e0d99351 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js @@ -209,10 +209,43 @@ erpnext.stock.PurchaseReceiptController = class PurchaseReceiptController extend } make_purchase_return() { - frappe.model.open_mapped_doc({ - method: "erpnext.stock.doctype.purchase_receipt.purchase_receipt.make_purchase_return", - frm: cur_frm + let me = this; + + let has_rejected_items = cur_frm.doc.items.filter((item) => { + if (item.rejected_qty > 0) { + return true; + } }) + + if (has_rejected_items && has_rejected_items.length > 0) { + frappe.prompt([ + { + label: __("Return Qty from Rejected Warehouse"), + fieldtype: "Check", + fieldname: "return_for_rejected_warehouse", + default: 1 + }, + ], function(values){ + if (values.return_for_rejected_warehouse) { + frappe.call({ + method: "erpnext.stock.doctype.purchase_receipt.purchase_receipt.make_purchase_return_against_rejected_warehouse", + args: { + source_name: cur_frm.doc.name + }, + callback: function(r) { + if(r.message) { + frappe.model.sync(r.message); + frappe.set_route("Form", r.message.doctype, r.message.name); + } + } + }) + } else { + cur_frm.cscript._make_purchase_return(); + } + }, __("Return Qty"), __("Make Return Entry")); + } else { + cur_frm.cscript._make_purchase_return(); + } } close_purchase_receipt() { @@ -322,6 +355,13 @@ frappe.ui.form.on('Purchase Receipt Item', { }, }); +cur_frm.cscript._make_purchase_return = function() { + frappe.model.open_mapped_doc({ + method: "erpnext.stock.doctype.purchase_receipt.purchase_receipt.make_purchase_return", + frm: cur_frm + }); +} + cur_frm.cscript['Make Stock Entry'] = function() { frappe.model.open_mapped_doc({ method: "erpnext.stock.doctype.purchase_receipt.purchase_receipt.make_stock_entry", diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index 387f0313804..0b5dc05c3ab 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -1136,6 +1136,13 @@ def get_returned_qty_map(purchase_receipt): return returned_qty_map +@frappe.whitelist() +def make_purchase_return_against_rejected_warehouse(source_name): + from erpnext.controllers.sales_and_purchase_return import make_return_doc + + return make_return_doc("Purchase Receipt", source_name, return_against_rejected_qty=True) + + @frappe.whitelist() def make_purchase_return(source_name, target_doc=None): from erpnext.controllers.sales_and_purchase_return import make_return_doc diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index ddc055656f2..19867225876 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -1827,6 +1827,33 @@ class TestPurchaseReceipt(FrappeTestCase): self.assertEqual(abs(data["stock_value_difference"]), 400.00) + def test_return_from_rejected_warehouse(self): + from erpnext.stock.doctype.purchase_receipt.purchase_receipt import ( + make_purchase_return_against_rejected_warehouse, + ) + + item_code = "_Test Item Return from Rejected Warehouse" + create_item(item_code) + + warehouse = create_warehouse("_Test Warehouse Return Qty Warehouse") + rejected_warehouse = create_warehouse("_Test Rejected Warehouse Return Qty Warehouse") + + # Step 1: Create Purchase Receipt with valuation rate 100 + pr = make_purchase_receipt( + item_code=item_code, + warehouse=warehouse, + qty=10, + rate=100, + rejected_qty=2, + rejected_warehouse=rejected_warehouse, + ) + + pr_return = make_purchase_return_against_rejected_warehouse(pr.name) + self.assertEqual(pr_return.items[0].warehouse, rejected_warehouse) + self.assertEqual(pr_return.items[0].qty, 2.0 * -1) + self.assertEqual(pr_return.items[0].rejected_qty, 0.0) + self.assertEqual(pr_return.items[0].rejected_warehouse, "") + def prepare_data_for_internal_transfer(): from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier From 41b9e928680a5dca42e10c383d0b9301482540fb Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Wed, 21 Jun 2023 21:52:44 +0530 Subject: [PATCH 25/63] fix: incorrect cost center error in bank reconciliation --- .../bank_reconciliation_tool/bank_reconciliation_tool.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.py b/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.py index c4a23a640c3..0eef3e9a67b 100644 --- a/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.py +++ b/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.py @@ -10,6 +10,7 @@ from frappe.model.document import Document from frappe.query_builder.custom import ConstantColumn from frappe.utils import cint, flt +from erpnext import get_default_cost_center from erpnext.accounts.doctype.bank_transaction.bank_transaction import get_total_allocated_amount from erpnext.accounts.report.bank_reconciliation_statement.bank_reconciliation_statement import ( get_amounts_not_reflected_in_system, @@ -140,6 +141,9 @@ def create_journal_entry_bts( second_account ) ) + + company = frappe.get_value("Account", company_account, "company") + accounts = [] # Multi Currency? accounts.append( @@ -149,6 +153,7 @@ def create_journal_entry_bts( "debit_in_account_currency": bank_transaction.withdrawal, "party_type": party_type, "party": party, + "cost_center": get_default_cost_center(company), } ) @@ -158,11 +163,10 @@ def create_journal_entry_bts( "bank_account": bank_transaction.bank_account, "credit_in_account_currency": bank_transaction.withdrawal, "debit_in_account_currency": bank_transaction.deposit, + "cost_center": get_default_cost_center(company), } ) - company = frappe.get_value("Account", company_account, "company") - journal_entry_dict = { "voucher_type": entry_type, "company": company, From b4db25dd188134dba30e22977c0fa4665ba749be Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 22 Jun 2023 12:40:02 +0530 Subject: [PATCH 26/63] refactor: increase precision for current exc rate in ERR --- .../exchange_rate_revaluation_account.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/exchange_rate_revaluation_account/exchange_rate_revaluation_account.json b/erpnext/accounts/doctype/exchange_rate_revaluation_account/exchange_rate_revaluation_account.json index 0a7d0579b15..fd2d931315c 100644 --- a/erpnext/accounts/doctype/exchange_rate_revaluation_account/exchange_rate_revaluation_account.json +++ b/erpnext/accounts/doctype/exchange_rate_revaluation_account/exchange_rate_revaluation_account.json @@ -73,6 +73,7 @@ "fieldname": "current_exchange_rate", "fieldtype": "Float", "label": "Current Exchange Rate", + "precision": "9", "read_only": 1 }, { @@ -148,7 +149,7 @@ ], "istable": 1, "links": [], - "modified": "2023-06-20 07:21:40.743460", + "modified": "2023-06-22 12:39:56.446722", "modified_by": "Administrator", "module": "Accounts", "name": "Exchange Rate Revaluation Account", From 0e68da5a2ab2855635cd292ea478d727a0ab7601 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Thu, 22 Jun 2023 15:43:32 +0530 Subject: [PATCH 27/63] feat: Show stock ledger preview --- .../doctype/payment_entry/payment_entry.js | 1 + .../purchase_invoice/purchase_invoice.js | 4 +- .../doctype/sales_invoice/sales_invoice.js | 7 +- erpnext/controllers/stock_controller.py | 34 +++++++-- .../public/js/controllers/stock_controller.js | 65 ---------------- erpnext/public/js/erpnext.bundle.js | 1 + erpnext/public/js/utils/ledger_preview.js | 76 +++++++++++++++++++ .../doctype/delivery_note/delivery_note.js | 3 + .../purchase_receipt/purchase_receipt.js | 4 + 9 files changed, 119 insertions(+), 76 deletions(-) create mode 100644 erpnext/public/js/utils/ledger_preview.js diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js index 9f55ba1167f..bac84db2315 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.js +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js @@ -155,6 +155,7 @@ frappe.ui.form.on('Payment Entry', { frm.events.hide_unhide_fields(frm); frm.events.set_dynamic_labels(frm); frm.events.show_general_ledger(frm); + erpnext.accounts.ledger_preview.show_accounting_ledger_preview(frm); }, validate_company: (frm) => { diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js index ab7884d5209..6a558ca606b 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js @@ -54,9 +54,11 @@ erpnext.accounts.PurchaseInvoice = class PurchaseInvoice extends erpnext.buying. hide_fields(this.frm.doc); // Show / Hide button this.show_general_ledger(); + erpnext.accounts.ledger_preview.show_accounting_ledger_preview(this.frm); - if(doc.update_stock==1 && doc.docstatus==1) { + if(doc.update_stock==1) { this.show_stock_ledger(); + erpnext.accounts.ledger_preview.show_stock_ledger_preview(this.frm); } if(!doc.is_return && doc.docstatus == 1 && doc.outstanding_amount != 0){ diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js index 1ef0c51cbac..68407e02210 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js @@ -88,9 +88,12 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends e } this.show_general_ledger(); - this.show_ledger_preview(); + erpnext.accounts.ledger_preview.show_accounting_ledger_preview(this.frm); - if(doc.update_stock) this.show_stock_ledger(); + if(doc.update_stock){ + this.show_stock_ledger(); + erpnext.accounts.ledger_preview.show_stock_ledger_preview(this.frm); + } if (doc.docstatus == 1 && doc.outstanding_amount!=0 && !(cint(doc.is_return) && doc.return_against)) { diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index 0b057bc7486..903ef658e3c 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -846,20 +846,31 @@ class StockController(AccountsController): @frappe.whitelist() -def show_ledger_preview(company, doctype, docname): - frappe.db.savepoint("show_ledger_preview") +def show_accounting_ledger_preview(company, doctype, docname): + frappe.db.savepoint("show_accounting_ledger_preview") + + filters = {"company": company, "include_dimensions": 1} + doc = frappe.get_doc(doctype, docname) + + gl_columns, gl_data = get_accounting_ledger_preview(doc, filters) + + frappe.db.rollback(save_point="show_accounting_ledger_preview") + + return {"gl_columns": gl_columns, "gl_data": gl_data} + + +@frappe.whitelist() +def show_stock_ledger_preview(company, doctype, docname): + frappe.db.savepoint("show_stock_ledger_preview") filters = {"company": company} doc = frappe.get_doc(doctype, docname) sl_columns, sl_data = get_stock_ledger_preview(doc, filters) - gl_columns, gl_data = get_accounting_ledger_preview(doc, filters) - frappe.db.rollback(save_point="show_ledger_preview") + frappe.db.rollback(save_point="show_stock_ledger_preview") return { - "gl_columns": gl_columns, - "gl_data": gl_data, "sl_columns": sl_columns, "sl_data": sl_data, } @@ -877,11 +888,16 @@ def get_accounting_ledger_preview(doc, filters): "against", "party", "party_type", + "cost_center", "against_voucher_type", "against_voucher", ] doc.docstatus = 1 + + if doc.get("update_stock") or doc.doctype in ("Purchase Receipt", "Delivery Note"): + doc.update_stock_ledger() + doc.make_gl_entries() columns = get_gl_columns(filters) gl_entries = get_gl_entries_for_preview(doc.doctype, doc.name, fields) @@ -915,12 +931,12 @@ def get_stock_ledger_preview(doc, filters): "qty_after_transaction", "warehouse", "incoming_rate", - "valuation_rate", + "in_out_rate", "stock_value", "stock_value_difference", ] - if doc.update_stock or doc.doctype in ("Purchase Receipt", "Delivery Note"): + if doc.get("update_stock") or doc.doctype in ("Purchase Receipt", "Delivery Note"): doc.docstatus = 1 doc.update_stock_ledger() columns = get_sl_columns(filters) @@ -945,6 +961,8 @@ def get_sl_entries_for_preview(doctype, docname, fields): entry["out_qty"] = abs(entry.actual_qty) entry["in_qty"] = 0 + entry["in_out_rate"] = entry["valuation_rate"] + return sl_entries diff --git a/erpnext/public/js/controllers/stock_controller.js b/erpnext/public/js/controllers/stock_controller.js index ff593485be2..720423b0a49 100644 --- a/erpnext/public/js/controllers/stock_controller.js +++ b/erpnext/public/js/controllers/stock_controller.js @@ -81,69 +81,4 @@ erpnext.stock.StockController = class StockController extends frappe.ui.form.Con }, __("View")); } } - - show_ledger_preview() { - let me = this - if(this.frm.doc.docstatus == 0) { - cur_frm.add_custom_button(__('Accounting Ledger Preview'), function() { - frappe.call({ - "method": "erpnext.controllers.stock_controller.show_ledger_preview", - "args": { - "company": me.frm.doc.company, - "doctype": me.frm.doc.doctype, - "docname": me.frm.doc.name - }, - "callback": function(response) { - me.make_dialog(response.message); - } - }) - }, __("View")); - } - } - - make_dialog(data) { - let me = this; - let gl_columns = data.gl_columns; - let gl_data = data.gl_data; - let sl_columns = data.sl_columns; - let sl_data = data.sl_data; - - let dialog = new frappe.ui.Dialog({ - "size": "extra-large", - "title": __("Ledger Preview"), - "fields": [ - { - "fieldtype": "HTML", - "fieldname": "accounting_ledger_preview_html", - "label": __("Accounting Ledger"), - }, - { - "fieldtype": "HTML", - "fieldname": "stock_ledger_preview_html", - "label": __("Stock Ledger"), - } - ] - }); - - setTimeout(function() { - me.get_datatable(gl_columns, gl_data, dialog.get_field("accounting_ledger_preview_html").wrapper); - }, 200); - - dialog.show(); - } - - get_datatable(columns, data, wrapper) { - const datatable_options = { - columns: columns, - data: data, - dynamicRowHeight: true, - checkboxColumn: false, - inlineFilters: true, - }; - - new frappe.DataTable( - wrapper, - datatable_options - ); - } }; diff --git a/erpnext/public/js/erpnext.bundle.js b/erpnext/public/js/erpnext.bundle.js index cc020fc2f11..4e028e4c313 100644 --- a/erpnext/public/js/erpnext.bundle.js +++ b/erpnext/public/js/erpnext.bundle.js @@ -17,6 +17,7 @@ import "./utils/customer_quick_entry"; import "./utils/supplier_quick_entry"; import "./call_popup/call_popup"; import "./utils/dimension_tree_filter"; +import "./utils/ledger_preview.js" import "./utils/barcode_scanner"; import "./telephony"; import "./templates/call_link.html"; diff --git a/erpnext/public/js/utils/ledger_preview.js b/erpnext/public/js/utils/ledger_preview.js new file mode 100644 index 00000000000..d1ee67b9c23 --- /dev/null +++ b/erpnext/public/js/utils/ledger_preview.js @@ -0,0 +1,76 @@ +frappe.provide('erpnext.accounts'); + +erpnext.accounts.ledger_preview = { + show_accounting_ledger_preview(frm) { + let me = this; + if(!frm.is_new() && frm.doc.docstatus == 0) { + frm.add_custom_button(__('Accounting Ledger'), function() { + frappe.call({ + "method": "erpnext.controllers.stock_controller.show_accounting_ledger_preview", + "args": { + "company": frm.doc.company, + "doctype": frm.doc.doctype, + "docname": frm.doc.name + }, + "callback": function(response) { + me.make_dialog("Accounting Ledger Preview", "accounting_ledger_preview_html", response.message.gl_columns, response.message.gl_data); + } + }) + }, __("Preview")); + } + }, + + show_stock_ledger_preview(frm) { + let me = this + if(!frm.is_new() && frm.doc.docstatus == 0) { + frm.add_custom_button(__('Stock Ledger'), function() { + frappe.call({ + "method": "erpnext.controllers.stock_controller.show_stock_ledger_preview", + "args": { + "company": frm.doc.company, + "doctype": frm.doc.doctype, + "docname": frm.doc.name + }, + "callback": function(response) { + me.make_dialog("Stock Ledger Preview", "stock_ledger_preview_html", response.message.sl_columns, response.message.sl_data); + } + }) + }, __("Preview")); + } + }, + + make_dialog(label, fieldname, columns, data) { + let me = this; + let dialog = new frappe.ui.Dialog({ + "size": "extra-large", + "title": __(label), + "fields": [ + { + "fieldtype": "HTML", + "fieldname": fieldname, + }, + ] + }); + + setTimeout(function() { + me.get_datatable(columns, data, dialog.get_field(fieldname).wrapper); + }, 200); + + dialog.show(); + }, + + get_datatable(columns, data, wrapper) { + const datatable_options = { + columns: columns, + data: data, + dynamicRowHeight: true, + checkboxColumn: false, + inlineFilters: true, + }; + + new frappe.DataTable( + wrapper, + datatable_options + ); + } +} \ No newline at end of file diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.js b/erpnext/stock/doctype/delivery_note/delivery_note.js index 77545e0e1ad..a648195933b 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.js +++ b/erpnext/stock/doctype/delivery_note/delivery_note.js @@ -200,6 +200,9 @@ erpnext.stock.DeliveryNoteController = class DeliveryNoteController extends erpn } } + erpnext.accounts.ledger_preview.show_accounting_ledger_preview(this.frm); + erpnext.accounts.ledger_preview.show_stock_ledger_preview(this.frm); + if (doc.docstatus > 0) { this.show_stock_ledger(); if (erpnext.is_perpetual_inventory_enabled(doc.company)) { diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js index 312c166f8b7..685209c02e4 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js @@ -121,6 +121,10 @@ erpnext.stock.PurchaseReceiptController = class PurchaseReceiptController extend refresh() { var me = this; super.refresh(); + + erpnext.accounts.ledger_preview.show_accounting_ledger_preview(this.frm); + erpnext.accounts.ledger_preview.show_stock_ledger_preview(this.frm); + if(this.frm.doc.docstatus > 0) { this.show_stock_ledger(); //removed for temporary From 80fffbd64bce62cc61a6d5df3865cad18b46a730 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Thu, 22 Jun 2023 15:55:18 +0530 Subject: [PATCH 28/63] fix: multiple Work Orders agaist same production plan --- .../doctype/production_plan/production_plan.js | 2 +- .../doctype/production_plan/production_plan.py | 6 ++++++ .../doctype/production_plan/test_production_plan.py | 7 +++++++ 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.js b/erpnext/manufacturing/doctype/production_plan/production_plan.js index 45a59cf7325..48986910b07 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.js +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.js @@ -99,7 +99,7 @@ frappe.ui.form.on('Production Plan', { }, __('Create')); } - if (frm.doc.mr_items && !in_list(['Material Requested', 'Closed'], frm.doc.status)) { + if (frm.doc.mr_items && frm.doc.mr_items.length && !in_list(['Material Requested', 'Closed'], frm.doc.status)) { frm.add_custom_button(__("Material Request"), ()=> { frm.trigger("make_material_request"); }, __('Create')); diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py index 0800bdd2af9..6dc1ff6a49f 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -515,6 +515,9 @@ class ProductionPlan(Document): self.show_list_created_message("Work Order", wo_list) self.show_list_created_message("Purchase Order", po_list) + if not wo_list: + frappe.msgprint(_("No Work Orders were created")) + def make_work_order_for_finished_goods(self, wo_list, default_warehouses): items_data = self.get_production_items() @@ -618,6 +621,9 @@ class ProductionPlan(Document): def create_work_order(self, item): from erpnext.manufacturing.doctype.work_order.work_order import OverProductionError + if item.get("qty") <= 0: + return + wo = frappe.new_doc("Work Order") wo.update(item) wo.planned_start_date = item.get("planned_start_date") or item.get("schedule_date") diff --git a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py index 75b43ec1c30..fcfba7fca56 100644 --- a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py @@ -76,6 +76,13 @@ class TestProductionPlan(FrappeTestCase): "Work Order", fields=["name"], filters={"production_plan": pln.name}, as_list=1 ) + pln.make_work_order() + nwork_orders = frappe.get_all( + "Work Order", fields=["name"], filters={"production_plan": pln.name}, as_list=1 + ) + + self.assertTrue(len(work_orders), len(nwork_orders)) + self.assertTrue(len(work_orders), len(pln.po_items)) for name in material_requests: From 5c6e3269fba8060935181cda7a952ea565d3abad Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Thu, 22 Jun 2023 15:58:41 +0530 Subject: [PATCH 29/63] fix: Use GET request --- erpnext/public/js/utils/ledger_preview.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erpnext/public/js/utils/ledger_preview.js b/erpnext/public/js/utils/ledger_preview.js index d1ee67b9c23..85d4a7d51e9 100644 --- a/erpnext/public/js/utils/ledger_preview.js +++ b/erpnext/public/js/utils/ledger_preview.js @@ -6,6 +6,7 @@ erpnext.accounts.ledger_preview = { if(!frm.is_new() && frm.doc.docstatus == 0) { frm.add_custom_button(__('Accounting Ledger'), function() { frappe.call({ + "type": "GET", "method": "erpnext.controllers.stock_controller.show_accounting_ledger_preview", "args": { "company": frm.doc.company, @@ -25,6 +26,7 @@ erpnext.accounts.ledger_preview = { if(!frm.is_new() && frm.doc.docstatus == 0) { frm.add_custom_button(__('Stock Ledger'), function() { frappe.call({ + "type": "GET", "method": "erpnext.controllers.stock_controller.show_stock_ledger_preview", "args": { "company": frm.doc.company, From d9e7bc545e3988b2af6e1b896286ba8673598c28 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Thu, 22 Jun 2023 16:07:32 +0530 Subject: [PATCH 30/63] fix: Do full rollback --- erpnext/controllers/stock_controller.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index 903ef658e3c..5137e030582 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -847,28 +847,24 @@ class StockController(AccountsController): @frappe.whitelist() def show_accounting_ledger_preview(company, doctype, docname): - frappe.db.savepoint("show_accounting_ledger_preview") - filters = {"company": company, "include_dimensions": 1} doc = frappe.get_doc(doctype, docname) gl_columns, gl_data = get_accounting_ledger_preview(doc, filters) - frappe.db.rollback(save_point="show_accounting_ledger_preview") + frappe.db.rollback() return {"gl_columns": gl_columns, "gl_data": gl_data} @frappe.whitelist() def show_stock_ledger_preview(company, doctype, docname): - frappe.db.savepoint("show_stock_ledger_preview") - filters = {"company": company} doc = frappe.get_doc(doctype, docname) sl_columns, sl_data = get_stock_ledger_preview(doc, filters) - frappe.db.rollback(save_point="show_stock_ledger_preview") + frappe.db.rollback() return { "sl_columns": sl_columns, From e745312a10dc56615a885dcaa97a4467a3705988 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 22 Jun 2023 19:21:52 +0530 Subject: [PATCH 31/63] fix: asset capitalization (backport #35832) (#35843) * fix: asset capitalization (#35832) * fix: misc asset capitalisation fixes * chore: add location in tests and remove unnecessary code * chore: more fixes and removals * chore: show company and fix tests * chore: make target qty read only on capitalization (cherry picked from commit fb823b53d1d9a631035016fa3b33003bf8ce297a) # Conflicts: # erpnext/assets/doctype/asset_capitalization/asset_capitalization.json # erpnext/assets/doctype/asset_capitalization/asset_capitalization.py * chore: fixing conflicts --------- Co-authored-by: Anand Baburajan --- .../asset_capitalization.js | 25 ---- .../asset_capitalization.json | 28 ++-- .../asset_capitalization.py | 137 ++++++------------ .../test_asset_capitalization.py | 25 +--- 4 files changed, 73 insertions(+), 142 deletions(-) diff --git a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.js b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.js index 01fcb11d817..6d55d7772b2 100644 --- a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.js +++ b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.js @@ -15,7 +15,6 @@ erpnext.assets.AssetCapitalization = class AssetCapitalization extends erpnext.s } refresh() { - erpnext.hide_company(); this.show_general_ledger(); if ((this.frm.doc.stock_items && this.frm.doc.stock_items.length) || !this.frm.doc.target_is_fixed_asset) { this.show_stock_ledger(); @@ -129,10 +128,6 @@ erpnext.assets.AssetCapitalization = class AssetCapitalization extends erpnext.s return this.get_target_item_details(); } - target_asset() { - return this.get_target_asset_details(); - } - item_code(doc, cdt, cdn) { var row = frappe.get_doc(cdt, cdn); if (cdt === "Asset Capitalization Stock Item") { @@ -247,26 +242,6 @@ erpnext.assets.AssetCapitalization = class AssetCapitalization extends erpnext.s } } - get_target_asset_details() { - var me = this; - - if (me.frm.doc.target_asset) { - return me.frm.call({ - method: "erpnext.assets.doctype.asset_capitalization.asset_capitalization.get_target_asset_details", - child: me.frm.doc, - args: { - asset: me.frm.doc.target_asset, - company: me.frm.doc.company, - }, - callback: function (r) { - if (!r.exc) { - me.frm.refresh_fields(); - } - } - }); - } - } - get_consumed_stock_item_details(row) { var me = this; diff --git a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.json b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.json index 01b35f64ab0..04b0c4e5132 100644 --- a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.json +++ b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.json @@ -11,13 +11,14 @@ "naming_series", "entry_type", "target_item_code", + "target_asset", "target_item_name", "target_is_fixed_asset", "target_has_batch_no", "target_has_serial_no", "column_break_9", - "target_asset", "target_asset_name", + "target_asset_location", "target_warehouse", "target_qty", "target_stock_uom", @@ -85,14 +86,13 @@ "fieldtype": "Column Break" }, { - "depends_on": "eval:doc.entry_type=='Capitalization'", "fieldname": "target_asset", "fieldtype": "Link", "in_standard_filter": 1, "label": "Target Asset", - "mandatory_depends_on": "eval:doc.entry_type=='Capitalization'", "no_copy": 1, - "options": "Asset" + "options": "Asset", + "read_only": 1 }, { "depends_on": "eval:doc.entry_type=='Capitalization'", @@ -108,11 +108,11 @@ "fieldtype": "Column Break" }, { - "fetch_from": "asset.company", "fieldname": "company", "fieldtype": "Link", "label": "Company", "options": "Company", + "remember_last_selected_value": 1, "reqd": 1 }, { @@ -158,7 +158,7 @@ "read_only": 1 }, { - "depends_on": "eval:doc.docstatus == 0 || (doc.stock_items && doc.stock_items.length)", + "depends_on": "eval:doc.entry_type=='Capitalization' && (doc.docstatus == 0 || (doc.stock_items && doc.stock_items.length))", "fieldname": "section_break_16", "fieldtype": "Section Break", "label": "Consumed Stock Items" @@ -189,7 +189,7 @@ "fieldname": "target_qty", "fieldtype": "Float", "label": "Target Qty", - "read_only_depends_on": "target_is_fixed_asset" + "read_only_depends_on": "eval:doc.entry_type=='Capitalization'" }, { "fetch_from": "target_item_code.stock_uom", @@ -227,7 +227,7 @@ "depends_on": "eval:doc.docstatus == 0 || (doc.asset_items && doc.asset_items.length)", "fieldname": "section_break_26", "fieldtype": "Section Break", - "label": "Consumed Asset Items" + "label": "Consumed Assets" }, { "fieldname": "asset_items", @@ -266,7 +266,7 @@ "options": "Finance Book" }, { - "depends_on": "eval:doc.docstatus == 0 || (doc.service_items && doc.service_items.length)", + "depends_on": "eval:doc.entry_type=='Capitalization' && (doc.docstatus == 0 || (doc.service_items && doc.service_items.length))", "fieldname": "service_expenses_section", "fieldtype": "Section Break", "label": "Service Expenses" @@ -329,12 +329,20 @@ "label": "Target Fixed Asset Account", "options": "Account", "read_only": 1 + }, + { + "depends_on": "eval:doc.entry_type=='Capitalization'", + "fieldname": "target_asset_location", + "fieldtype": "Link", + "label": "Target Asset Location", + "mandatory_depends_on": "eval:doc.entry_type=='Capitalization'", + "options": "Location" } ], "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2022-10-12 15:09:40.771332", + "modified": "2023-06-22 14:17:07.995120", "modified_by": "Administrator", "module": "Assets", "name": "Asset Capitalization", diff --git a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py index 6841c56b108..a883bec71b0 100644 --- a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py +++ b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py @@ -19,9 +19,6 @@ from erpnext.assets.doctype.asset.depreciation import ( reverse_depreciation_entry_made_after_disposal, ) from erpnext.assets.doctype.asset_category.asset_category import get_asset_category_account -from erpnext.assets.doctype.asset_depreciation_schedule.asset_depreciation_schedule import ( - make_new_active_asset_depr_schedules_and_cancel_current_ones, -) from erpnext.controllers.stock_controller import StockController from erpnext.setup.doctype.brand.brand import get_brand_defaults from erpnext.setup.doctype.item_group.item_group import get_item_group_defaults @@ -45,7 +42,6 @@ force_fields = [ "target_has_batch_no", "target_stock_uom", "stock_uom", - "target_fixed_asset_account", "fixed_asset_account", "valuation_rate", ] @@ -56,7 +52,6 @@ class AssetCapitalization(StockController): self.validate_posting_time() self.set_missing_values(for_validate=True) self.validate_target_item() - self.validate_target_asset() self.validate_consumed_stock_item() self.validate_consumed_asset_item() self.validate_service_item() @@ -71,11 +66,12 @@ class AssetCapitalization(StockController): def before_submit(self): self.validate_source_mandatory() + if self.entry_type == "Capitalization": + self.create_target_asset() def on_submit(self): self.update_stock_ledger() self.make_gl_entries() - self.update_target_asset() def on_cancel(self): self.ignore_linked_doctypes = ( @@ -86,7 +82,7 @@ class AssetCapitalization(StockController): ) self.update_stock_ledger() self.make_gl_entries() - self.update_target_asset() + self.restore_consumed_asset_items() def set_title(self): self.title = self.target_asset_name or self.target_item_name or self.target_item_code @@ -97,15 +93,6 @@ class AssetCapitalization(StockController): if self.meta.has_field(k) and (not self.get(k) or k in force_fields): self.set(k, v) - # Remove asset if item not a fixed asset - if not self.target_is_fixed_asset: - self.target_asset = None - - target_asset_details = get_target_asset_details(self.target_asset, self.company) - for k, v in target_asset_details.items(): - if self.meta.has_field(k) and (not self.get(k) or k in force_fields): - self.set(k, v) - for d in self.stock_items: args = self.as_dict() args.update(d.as_dict()) @@ -157,9 +144,6 @@ class AssetCapitalization(StockController): if not target_item.is_stock_item: self.target_warehouse = None - if not target_item.is_fixed_asset: - self.target_asset = None - self.target_fixed_asset_account = None if not target_item.has_batch_no: self.target_batch_no = None if not target_item.has_serial_no: @@ -170,17 +154,6 @@ class AssetCapitalization(StockController): self.validate_item(target_item) - def validate_target_asset(self): - if self.target_asset: - target_asset = self.get_asset_for_validation(self.target_asset) - - if target_asset.item_code != self.target_item_code: - frappe.throw( - _("Asset {0} does not belong to Item {1}").format(self.target_asset, self.target_item_code) - ) - - self.validate_asset(target_asset) - def validate_consumed_stock_item(self): for d in self.stock_items: if d.item_code: @@ -386,7 +359,11 @@ class AssetCapitalization(StockController): gl_entries, target_account, target_against, precision ) + if not self.stock_items and not self.service_items and self.are_all_asset_items_non_depreciable: + return [] + self.get_gl_entries_for_target_item(gl_entries, target_against, precision) + return gl_entries def get_target_account(self): @@ -429,11 +406,14 @@ class AssetCapitalization(StockController): def get_gl_entries_for_consumed_asset_items( self, gl_entries, target_account, target_against, precision ): + self.are_all_asset_items_non_depreciable = True + # Consumed Assets for item in self.asset_items: - asset = self.get_asset(item) + asset = frappe.get_doc("Asset", item.asset) if asset.calculate_depreciation: + self.are_all_asset_items_non_depreciable = False notes = _( "This schedule was created when Asset {0} was consumed through Asset Capitalization {1}." ).format( @@ -519,40 +499,46 @@ class AssetCapitalization(StockController): ) ) - def update_target_asset(self): + def create_target_asset(self): total_target_asset_value = flt(self.total_value, self.precision("total_value")) - if self.docstatus == 1 and self.entry_type == "Capitalization": - asset_doc = frappe.get_doc("Asset", self.target_asset) - asset_doc.purchase_date = self.posting_date - asset_doc.gross_purchase_amount = total_target_asset_value - asset_doc.purchase_receipt_amount = total_target_asset_value - notes = _( - "This schedule was created when target Asset {0} was updated through Asset Capitalization {1}." - ).format( - get_link_to_form(asset_doc.doctype, asset_doc.name), get_link_to_form(self.doctype, self.name) - ) - make_new_active_asset_depr_schedules_and_cancel_current_ones(asset_doc, notes) - asset_doc.flags.ignore_validate_update_after_submit = True - asset_doc.save() - elif self.docstatus == 2: - for item in self.asset_items: - asset = self.get_asset(item) - asset.db_set("disposal_date", None) - self.set_consumed_asset_status(asset) + asset_doc = frappe.new_doc("Asset") + asset_doc.company = self.company + asset_doc.item_code = self.target_item_code + asset_doc.is_existing_asset = 1 + asset_doc.location = self.target_asset_location + asset_doc.available_for_use_date = self.posting_date + asset_doc.purchase_date = self.posting_date + asset_doc.gross_purchase_amount = total_target_asset_value + asset_doc.purchase_receipt_amount = total_target_asset_value + asset_doc.flags.ignore_validate = True + asset_doc.insert() - if asset.calculate_depreciation: - reverse_depreciation_entry_made_after_disposal(asset, self.posting_date) - notes = _( - "This schedule was created when Asset {0} was restored on Asset Capitalization {1}'s cancellation." - ).format( - get_link_to_form(asset.doctype, asset.name), get_link_to_form(self.doctype, self.name) - ) - reset_depreciation_schedule(asset, self.posting_date, notes) + self.target_asset = asset_doc.name - def get_asset(self, item): - asset = frappe.get_doc("Asset", item.asset) - self.check_finance_books(item, asset) - return asset + self.target_fixed_asset_account = get_asset_category_account( + "fixed_asset_account", item=self.target_item_code, company=asset_doc.company + ) + + frappe.msgprint( + _( + "Asset {0} has been created. Please set the depreciation details if any and submit it." + ).format(get_link_to_form("Asset", asset_doc.name)) + ) + + def restore_consumed_asset_items(self): + for item in self.asset_items: + asset = frappe.get_doc("Asset", item.asset) + asset.db_set("disposal_date", None) + self.set_consumed_asset_status(asset) + + if asset.calculate_depreciation: + reverse_depreciation_entry_made_after_disposal(asset, self.posting_date) + notes = _( + "This schedule was created when Asset {0} was restored on Asset Capitalization {1}'s cancellation." + ).format( + get_link_to_form(asset.doctype, asset.name), get_link_to_form(self.doctype, self.name) + ) + reset_depreciation_schedule(asset, self.posting_date, notes) def set_consumed_asset_status(self, asset): if self.docstatus == 1: @@ -602,33 +588,6 @@ def get_target_item_details(item_code=None, company=None): return out -@frappe.whitelist() -def get_target_asset_details(asset=None, company=None): - out = frappe._dict() - - # Get Asset Details - asset_details = frappe._dict() - if asset: - asset_details = frappe.db.get_value("Asset", asset, ["asset_name", "item_code"], as_dict=1) - if not asset_details: - frappe.throw(_("Asset {0} does not exist").format(asset)) - - # Re-set item code from Asset - out.target_item_code = asset_details.item_code - - # Set Asset Details - out.asset_name = asset_details.asset_name - - if asset_details.item_code: - out.target_fixed_asset_account = get_asset_category_account( - "fixed_asset_account", item=asset_details.item_code, company=company - ) - else: - out.target_fixed_asset_account = None - - return out - - @frappe.whitelist() def get_consumed_stock_item_details(args): if isinstance(args, str): diff --git a/erpnext/assets/doctype/asset_capitalization/test_asset_capitalization.py b/erpnext/assets/doctype/asset_capitalization/test_asset_capitalization.py index 5345d0e7f2b..6e0a6856f56 100644 --- a/erpnext/assets/doctype/asset_capitalization/test_asset_capitalization.py +++ b/erpnext/assets/doctype/asset_capitalization/test_asset_capitalization.py @@ -47,13 +47,6 @@ class TestAssetCapitalization(unittest.TestCase): total_amount = 103000 - # Create assets - target_asset = create_asset( - asset_name="Asset Capitalization Target Asset", - submit=1, - warehouse="Stores - TCP1", - company=company, - ) consumed_asset = create_asset( asset_name="Asset Capitalization Consumable Asset", asset_value=consumed_asset_value, @@ -65,7 +58,8 @@ class TestAssetCapitalization(unittest.TestCase): # Create and submit Asset Captitalization asset_capitalization = create_asset_capitalization( entry_type="Capitalization", - target_asset=target_asset.name, + target_item_code="Macbook Pro", + target_asset_location="Test Location", stock_qty=stock_qty, stock_rate=stock_rate, consumed_asset=consumed_asset.name, @@ -94,7 +88,7 @@ class TestAssetCapitalization(unittest.TestCase): self.assertEqual(asset_capitalization.target_incoming_rate, total_amount) # Test Target Asset values - target_asset.reload() + target_asset = frappe.get_doc("Asset", asset_capitalization.target_asset) self.assertEqual(target_asset.gross_purchase_amount, total_amount) self.assertEqual(target_asset.purchase_receipt_amount, total_amount) @@ -142,13 +136,6 @@ class TestAssetCapitalization(unittest.TestCase): total_amount = 103000 - # Create assets - target_asset = create_asset( - asset_name="Asset Capitalization Target Asset", - submit=1, - warehouse="Stores - _TC", - company=company, - ) consumed_asset = create_asset( asset_name="Asset Capitalization Consumable Asset", asset_value=consumed_asset_value, @@ -160,7 +147,8 @@ class TestAssetCapitalization(unittest.TestCase): # Create and submit Asset Captitalization asset_capitalization = create_asset_capitalization( entry_type="Capitalization", - target_asset=target_asset.name, + target_item_code="Macbook Pro", + target_asset_location="Test Location", stock_qty=stock_qty, stock_rate=stock_rate, consumed_asset=consumed_asset.name, @@ -189,7 +177,7 @@ class TestAssetCapitalization(unittest.TestCase): self.assertEqual(asset_capitalization.target_incoming_rate, total_amount) # Test Target Asset values - target_asset.reload() + target_asset = frappe.get_doc("Asset", asset_capitalization.target_asset) self.assertEqual(target_asset.gross_purchase_amount, total_amount) self.assertEqual(target_asset.purchase_receipt_amount, total_amount) @@ -364,6 +352,7 @@ def create_asset_capitalization(**args): "posting_time": args.posting_time or now.strftime("%H:%M:%S.%f"), "target_item_code": target_item_code, "target_asset": target_asset.name, + "target_asset_location": "Test Location", "target_warehouse": target_warehouse, "target_qty": flt(args.target_qty) or 1, "target_batch_no": args.target_batch_no, From 56e81ada56332b7dfb721610575ee1bffb2a97d3 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Thu, 22 Jun 2023 19:57:23 +0530 Subject: [PATCH 32/63] ci: use multiple python version in patch test --- .github/workflows/patch.yml | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/.github/workflows/patch.yml b/.github/workflows/patch.yml index d5f00527444..e2d89573cbf 100644 --- a/.github/workflows/patch.yml +++ b/.github/workflows/patch.yml @@ -43,9 +43,11 @@ jobs: fi - name: Setup Python - uses: "gabrielfalcao/pyenv-action@v9" + uses: "actions/setup-python@v4" with: - versions: 3.10:latest, 3.7:latest + python-version: | + 3.7 + 3.10 - name: Setup Node uses: actions/setup-node@v2 @@ -92,7 +94,6 @@ jobs: - name: Install run: | pip install frappe-bench - pyenv global $(pyenv versions | grep '3.10') bash ${GITHUB_WORKSPACE}/.github/helper/install.sh env: DB: mariadb @@ -107,7 +108,6 @@ jobs: git -C "apps/frappe" remote set-url upstream https://github.com/frappe/frappe.git git -C "apps/erpnext" remote set-url upstream https://github.com/frappe/erpnext.git - pyenv global $(pyenv versions | grep '3.7') for version in $(seq 12 13) do echo "Updating to v$version" @@ -120,7 +120,7 @@ jobs: git -C "apps/erpnext" checkout -q -f $branch_name rm -rf ~/frappe-bench/env - bench setup env + bench setup env --python python3.7 bench pip install -e ./apps/payments bench pip install -e ./apps/erpnext @@ -132,9 +132,8 @@ jobs: git -C "apps/frappe" checkout -q -f "${GITHUB_BASE_REF:-${GITHUB_REF##*/}}" git -C "apps/erpnext" checkout -q -f "$GITHUB_SHA" - pyenv global $(pyenv versions | grep '3.10') rm -rf ~/frappe-bench/env - bench -v setup env + bench -v setup env --python python3.10 bench pip install -e ./apps/payments bench pip install -e ./apps/erpnext From f37484c6fe6d95cd854befbf2f056a6d02244d5e Mon Sep 17 00:00:00 2001 From: Anand Baburajan Date: Thu, 22 Jun 2023 22:32:06 +0530 Subject: [PATCH 33/63] chore: better err msg on cancelling JE for asset scrap [dev] (#35850) chore: better err msg on cancelling JE for asset scrap --- .../doctype/journal_entry/journal_entry.js | 2 +- .../doctype/journal_entry/journal_entry.py | 15 +++++++++++---- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.js b/erpnext/accounts/doctype/journal_entry/journal_entry.js index 6d9e3202f10..a51e38eefea 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.js +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.js @@ -8,7 +8,7 @@ frappe.provide("erpnext.journal_entry"); frappe.ui.form.on("Journal Entry", { setup: function(frm) { frm.add_fetch("bank_account", "account", "account"); - frm.ignore_doctypes_on_cancel_all = ['Sales Invoice', 'Purchase Invoice', 'Journal Entry', "Repost Payment Ledger", 'Asset Depreciation Schedule']; + frm.ignore_doctypes_on_cancel_all = ['Sales Invoice', 'Purchase Invoice', 'Journal Entry', "Repost Payment Ledger", 'Asset', 'Asset Movement', 'Asset Depreciation Schedule']; }, refresh: function(frm) { diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py index 74fd5596123..83312dbd229 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py @@ -326,12 +326,10 @@ class JournalEntry(AccountsController): d.db_update() def unlink_asset_reference(self): - if self.voucher_type != "Depreciation Entry": - return - for d in self.get("accounts"): if ( - d.reference_type == "Asset" + self.voucher_type == "Depreciation Entry" + and d.reference_type == "Asset" and d.reference_name and d.account_type == "Depreciation" and d.debit @@ -370,6 +368,15 @@ class JournalEntry(AccountsController): else: asset.db_set("value_after_depreciation", asset.value_after_depreciation + d.debit) asset.set_status() + elif self.voucher_type == "Journal Entry" and d.reference_type == "Asset" and d.reference_name: + journal_entry_for_scrap = frappe.db.get_value( + "Asset", d.reference_name, "journal_entry_for_scrap" + ) + + if journal_entry_for_scrap == self.name: + frappe.throw( + _("Journal Entry for Asset scrapping cannot be cancelled. Please restore the Asset.") + ) def unlink_inter_company_jv(self): if ( From 9a993b0364fcee14a9887a5a7b91a23aa69701f1 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Fri, 23 Jun 2023 08:25:00 +0530 Subject: [PATCH 34/63] fix: show non-depreciable assets in fixed asset register (backport #35858) (#35860) fix: show non-depreciable assets in fixed asset register (#35858) fix: show non-depr assets in fixed asset register (cherry picked from commit 42d09448eed316268d9a5a2c9e25791bc48fa326) Co-authored-by: Anand Baburajan --- .../report/fixed_asset_register/fixed_asset_register.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) 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 f810819b4fc..6911f94bbbb 100644 --- a/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py +++ b/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py @@ -115,7 +115,11 @@ def get_data(filters): depreciation_amount_map = get_asset_depreciation_amount_map(filters, finance_book) for asset in assets_record: - if assets_linked_to_fb and asset.asset_id not in assets_linked_to_fb: + if ( + assets_linked_to_fb + and asset.calculate_depreciation + and asset.asset_id not in assets_linked_to_fb + ): continue asset_value = get_asset_value_after_depreciation( From 555c126eb9b2badeacfa742fa379f33038b7085b Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Fri, 23 Jun 2023 10:52:25 +0530 Subject: [PATCH 35/63] fix: add patch for setting default value of report field --- .../process_statement_of_accounts.json | 5 +- .../process_statement_of_accounts.py | 2 +- ...ement_of_accounts_accounts_receivable.html | 348 +++++++++++++++ .../accounts_receivable.html | 399 ++++++++---------- erpnext/patches.txt | 1 + .../v14_0/set_report_in_process_SOA.py | 10 + 6 files changed, 532 insertions(+), 233 deletions(-) create mode 100644 erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts_accounts_receivable.html create mode 100644 erpnext/patches/v14_0/set_report_in_process_SOA.py diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.json b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.json index 70e810439c2..8004659065e 100644 --- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.json +++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.json @@ -326,7 +326,8 @@ "fieldname": "report", "fieldtype": "Select", "label": "Report", - "options": "General Ledger\nAccounts Receivable" + "options": "General Ledger\nAccounts Receivable", + "reqd": 1 }, { "default": "Today", @@ -372,7 +373,7 @@ } ], "links": [], - "modified": "2023-06-19 18:37:10.040570", + "modified": "2023-06-23 10:13:15.051950", "modified_by": "Administrator", "module": "Accounts", "name": "Process Statement Of Accounts", diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py index db186d81a2d..08f4cf45d62 100644 --- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py +++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py @@ -160,7 +160,7 @@ def get_html(doc, filters, entry, col, res, ageing): template_path = ( "erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html" if doc.report == "General Ledger" - else "erpnext/accounts/report/accounts_receivable/accounts_receivable.html" + else "erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts_accounts_receivable.html" ) if doc.letter_head: diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts_accounts_receivable.html b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts_accounts_receivable.html new file mode 100644 index 00000000000..07e1896292d --- /dev/null +++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts_accounts_receivable.html @@ -0,0 +1,348 @@ + + +

{{ _(report.report_name) }}

+

+ {% if (filters.customer_name) %} + {{ filters.customer_name }} + {% else %} + {{ filters.customer ~ filters.supplier }} + {% endif %} +

+
+ {% if (filters.tax_id) %} + {{ _("Tax Id: ") }}{{ filters.tax_id }} + {% endif %} +
+
+ {{ _(filters.ageing_based_on) }} + {{ _("Until") }} + {{ frappe.format(filters.report_date, 'Date') }} +
+ +
+
+ {% if(filters.payment_terms) %} + {{ _("Payment Terms") }}: {{ filters.payment_terms }} + {% endif %} +
+
+ {% if(filters.credit_limit) %} + {{ _("Credit Limit") }}: {{ frappe.utils.fmt_money(filters.credit_limit) }} + {% endif %} +
+
+ + {% if(filters.show_future_payments) %} + {% set balance_row = data.slice(-1).pop() %} + {% for i in report.columns %} + {% if i.fieldname == 'age' %} + {% set elem = i %} + {% endif %} + {% endfor %} + {% set start = report.columns.findIndex(elem) %} + {% set range1 = report.columns[start].label %} + {% set range2 = report.columns[start+1].label %} + {% set range3 = report.columns[start+2].label %} + {% set range4 = report.columns[start+3].label %} + {% set range5 = report.columns[start+4].label %} + {% set range6 = report.columns[start+5].label %} + + {% if(balance_row) %} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
(Amount in {{ data[0]["currency"] ~ "" }})
{{ _(" ") }}{{ _(range1) }}{{ _(range2) }}{{ _(range3) }}{{ _(range4) }}{{ _(range5) }}{{ _(range6) }}{{ _("Total") }}
{{ _("Total Outstanding") }} + {{ format_number(balance_row["age"], null, 2) }} + + {{ frappe.utils.fmt_money(balance_row["range1"], data[data.length-1]["currency"]) }} + + {{ frappe.utils.fmt_money(balance_row["range2"], data[data.length-1]["currency"]) }} + + {{ frappe.utils.fmt_money(balance_row["range3"], data[data.length-1]["currency"]) }} + + {{ frappe.utils.fmt_money(balance_row["range4"], data[data.length-1]["currency"]) }} + + {{ frappe.utils.fmt_money(balance_row["range5"], data[data.length-1]["currency"]) }} + + {{ frappe.utils.fmt_money(flt(balance_row["outstanding"]), data[data.length-1]["currency"]) }} +
{{ _("Future Payments") }} + {{ frappe.utils.fmt_money(flt(balance_row[("future_amount")]), data[data.length-1]["currency"]) }} +
+ {% endif %} + {% endif %} + + + + {% if(report.report_name == "Accounts Receivable" or report.report_name == "Accounts Payable") %} + + + + {% if(report.report_name == "Accounts Receivable" and filters.show_sales_person) %} + + + {% else %} + + {% endif %} + {% if not(filters.show_future_payments) %} + + {% endif %} + + {% if not(filters.show_future_payments) %} + + + {% endif %} + + {% if(filters.show_future_payments) %} + {% if(report.report_name == "Accounts Receivable") %} + + {% endif %} + + + + {% endif %} + {% else %} + + + + + + {% endif %} + + + + {% for i in range(data|length) %} + + {% if(report.report_name == "Accounts Receivable" or report.report_name == "Accounts Payable") %} + {% if(data[i]["party"]) %} + + + + + {% if(report.report_name == "Accounts Receivable" and filters.show_sales_person) %} + + {% endif %} + + {% if not (filters.show_future_payments) %} + + {% endif %} + + + + {% if not(filters.show_future_payments) %} + + + {% endif %} + + + {% if(filters.show_future_payments) %} + {% if(report.report_name == "Accounts Receivable") %} + + {% endif %} + + + + {% endif %} + {% else %} + + {% if not(filters.show_future_payments) %} + + {% endif %} + {% if(report.report_name == "Accounts Receivable" and filters.show_sales_person) %} + + {% endif %} + + + + + {% if not(filters.show_future_payments) %} + + + {% endif %} + + + {% if(filters.show_future_payments) %} + {% if(report.report_name == "Accounts Receivable") %} + + {% endif %} + + + + {% endif %} + {% endif %} + {% else %} + {% if(data[i]["party"] or " ") %} + {% if not(data[i]["is_total_row"]) %} + + {% else %} + + {% endif %} + + + + + {% endif %} + {% endif %} + + {% endfor %} + + + + + + + + + +
{{ _("Date") }}{{ _("Age (Days)") }}{{ _("Reference") }}{{ _("Sales Person") }}{{ _("Reference") }} + {% if (filters.customer or filters.supplier or filters.customer_name) %} + {{ _("Remarks") }} + {% else %} + {{ _("Party") }} + {% endif %} + {{ _("Invoiced Amount") }}{{ _("Paid Amount") }} + {% if report.report_name == "Accounts Receivable" %} + {{ _('Credit Note') }} + {% else %} + {{ _('Debit Note') }} + {% endif %} + {{ _("Outstanding Amount") }}{{ _("Customer LPO No.") }}{{ _("Future Payment Ref") }}{{ _("Future Payment Amount") }}{{ _("Remaining Balance") }} + {% if (filters.customer or filters.supplier or filters.customer_name) %} + {{ _("Remarks")}} + {% else %} + {{ _("Party") }} + {% endif %} + {{ _("Total Invoiced Amount") }}{{ _("Total Paid Amount") }} + {% if report.report_name == "Accounts Receivable Summary" %} + {{ _('Credit Note Amount') }} + {% else %} + {{ _('Debit Note Amount') }} + {% endif %} + {{ _("Total Outstanding Amount") }}
{{ (data[i]["posting_date"]) }}{{ data[i]["age"] }} + {% if not(filters.show_future_payments) %} + {{ data[i]["voucher_type"] }} +
+ {% endif %} + {{ data[i]["voucher_no"] }} +
{{ data[i]["sales_person"] }} + {% if(not(filters.customer or filters.supplier or filters.customer_name)) %} + {{ data[i]["party"] }} + {% if(data[i]["customer_name"] and data[i]["customer_name"] != data[i]["party"]) %} +
{{ data[i]["customer_name"] }} + {% elif(data[i]["supplier_name"] != data[i]["party"]) %} +
{{ data[i]["supplier_name"] }} + {% endif %} + {% endif %} +
+ {% if data[i]["remarks"] %} + {{ _("Remarks") }}: + {{ data[i]["remarks"] }} + {% endif %} +
+
+ {{ frappe.utils.fmt_money(data[i]["invoiced"], currency=data[i]["currency"]) }} + {{ frappe.utils.fmt_money(data[i]["paid"], currency=data[i]["currency"]) }} + {{ frappe.utils.fmt_money(data[i]["credit_note"], currency=data[i]["currency"]) }} + {{ frappe.utils.fmt_money(data[i]["outstanding"], currency=data[i]["currency"]) }} + {{ data[i]["po_no"] }}{{ data[i]["future_ref"] }}{{ frappe.utils.fmt_money(data[i]["future_amount"], currency=data[i]["currency"]) }}{{ frappe.utils.fmt_money(data[i]["remaining_balance"], currency=data[i]["currency"]) }}{{ _("Total") }} + {{ frappe.utils.fmt_money(data[i]["invoiced"], data[i]["currency"]) }} + {{ frappe.utils.fmt_money(data[i]["paid"], currency=data[i]["currency"]) }}{{ frappe.utils.fmt_money(data[i]["credit_note"], currency=data[i]["currency"]) }} + {{ frappe.utils.fmt_money(data[i]["outstanding"], currency=data[i]["currency"]) }} + {{ data[i]["po_no"] }}{{ data[i]["future_ref"] }}{{ frappe.utils.fmt_money(data[i]["future_amount"], currency=data[i]["currency"]) }}{{ frappe.utils.fmt_money(data[i]["remaining_balance"], currency=data[i]["currency"]) }} + {% if(not(filters.customer | filters.supplier)) %} + {{ data[i]["party"] }} + {% if(data[i]["customer_name"] and data[i]["customer_name"] != data[i]["party"]) %} +
{{ data[i]["customer_name"] }} + {% elif(data[i]["supplier_name"] != data[i]["party"]) %} +
{{ data[i]["supplier_name"] }} + {% endif %} + {% endif %} +
{{ _("Remarks") }}: + {{ data[i]["remarks"] }} +
{{ _("Total") }}{{ frappe.utils.fmt_money(data[i]["invoiced"], currency=data[i]["currency"]) }}{{ frappe.utils.fmt_money(data[i]["paid"], currency=data[i]["currency"]) }}{{ frappe.utils.fmt_money(data[i]["credit_note"], currency=data[i]["currency"]) }}{{ frappe.utils.fmt_money(data[i]["outstanding"], currency=data[i]["currency"]) }}
{{ frappe.utils.fmt_money(data|sum(attribute="invoiced"), currency=data[0]["currency"]) }}{{ frappe.utils.fmt_money(data|sum(attribute="paid"), currency=data[0]["currency"]) }}{{ frappe.utils.fmt_money(data|sum(attribute="credit_note"), currency=data[0]["currency"]) }}{{ frappe.utils.fmt_money(data|sum(attribute="outstanding"), currency=data[0]["currency"]) }}
+
+ {% if ageing %} +

{{ _("Ageing Report based on ") }} {{ ageing.ageing_based_on }} + {{ _("up to " ) }} {{ frappe.format(filters.report_date, 'Date')}} +

+ + + + + + + + + + + + + + + + + +
30 Days60 Days90 Days120 Days
{{ frappe.utils.fmt_money(ageing.range1, currency=data[0]["currency"]) }}{{ frappe.utils.fmt_money(ageing.range2, currency=data[0]["currency"]) }}{{ frappe.utils.fmt_money(ageing.range3, currency=data[0]["currency"]) }}{{ frappe.utils.fmt_money(ageing.range4, currency=data[0]["currency"]) }}
+ {% endif %} +

{{ _("Printed On ") }}{{ frappe.utils.now() }}

diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.html b/erpnext/accounts/report/accounts_receivable/accounts_receivable.html index 07e1896292d..ed3b9915591 100644 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.html +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.html @@ -8,56 +8,51 @@ } -

{{ _(report.report_name) }}

+

{%= __(report.report_name) %}

- {% if (filters.customer_name) %} - {{ filters.customer_name }} - {% else %} - {{ filters.customer ~ filters.supplier }} - {% endif %} + {% if (filters.customer_name) { %} + {%= filters.customer_name %} + {% } else { %} + {%= filters.customer || filters.supplier %} + {% } %}

- {% if (filters.tax_id) %} - {{ _("Tax Id: ") }}{{ filters.tax_id }} - {% endif %} + {% if (filters.tax_id) { %} + {%= __("Tax Id: ")%} {%= filters.tax_id %} + {% } %}
- {{ _(filters.ageing_based_on) }} - {{ _("Until") }} - {{ frappe.format(filters.report_date, 'Date') }} + {%= __(filters.ageing_based_on) %} + {%= __("Until") %} + {%= frappe.datetime.str_to_user(filters.report_date) %}
- {% if(filters.payment_terms) %} - {{ _("Payment Terms") }}: {{ filters.payment_terms }} - {% endif %} + {% if(filters.payment_terms) { %} + {%= __("Payment Terms") %}: {%= filters.payment_terms %} + {% } %}
- {% if(filters.credit_limit) %} - {{ _("Credit Limit") }}: {{ frappe.utils.fmt_money(filters.credit_limit) }} - {% endif %} + {% if(filters.credit_limit) { %} + {%= __("Credit Limit") %}: {%= format_currency(filters.credit_limit) %} + {% } %}
- {% if(filters.show_future_payments) %} - {% set balance_row = data.slice(-1).pop() %} - {% for i in report.columns %} - {% if i.fieldname == 'age' %} - {% set elem = i %} - {% endif %} - {% endfor %} - {% set start = report.columns.findIndex(elem) %} - {% set range1 = report.columns[start].label %} - {% set range2 = report.columns[start+1].label %} - {% set range3 = report.columns[start+2].label %} - {% set range4 = report.columns[start+3].label %} - {% set range5 = report.columns[start+4].label %} - {% set range6 = report.columns[start+5].label %} - - {% if(balance_row) %} + {% if(filters.show_future_payments) { %} + {% var balance_row = data.slice(-1).pop(); + var start = report.columns.findIndex((elem) => (elem.fieldname == 'age')); + var range1 = report.columns[start].label; + var range2 = report.columns[start+1].label; + var range3 = report.columns[start+2].label; + var range4 = report.columns[start+3].label; + var range5 = report.columns[start+4].label; + var range6 = report.columns[start+5].label; + %} + {% if(balance_row) { %} - + @@ -71,42 +66,42 @@ - - - - - - - - + + + + + + + + - + - + @@ -114,10 +109,10 @@ - + @@ -125,224 +120,168 @@ + {%= format_currency(flt(balance_row["outstanding"] - balance_row[("future_amount")]), data[data.length-1]["currency"]) %}
(Amount in {{ data[0]["currency"] ~ "" }})(Amount in {%= data[0]["currency"] || "" %})
{{ _(" ") }}{{ _(range1) }}{{ _(range2) }}{{ _(range3) }}{{ _(range4) }}{{ _(range5) }}{{ _(range6) }}{{ _("Total") }}{%= __(" ") %}{%= __(range1) %}{%= __(range2) %}{%= __(range3) %}{%= __(range4) %}{%= __(range5) %}{%= __(range6) %}{%= __("Total") %}
{{ _("Total Outstanding") }}{%= __("Total Outstanding") %} - {{ format_number(balance_row["age"], null, 2) }} + {%= format_number(balance_row["age"], null, 2) %} - {{ frappe.utils.fmt_money(balance_row["range1"], data[data.length-1]["currency"]) }} + {%= format_currency(balance_row["range1"], data[data.length-1]["currency"]) %} - {{ frappe.utils.fmt_money(balance_row["range2"], data[data.length-1]["currency"]) }} + {%= format_currency(balance_row["range2"], data[data.length-1]["currency"]) %} - {{ frappe.utils.fmt_money(balance_row["range3"], data[data.length-1]["currency"]) }} + {%= format_currency(balance_row["range3"], data[data.length-1]["currency"]) %} - {{ frappe.utils.fmt_money(balance_row["range4"], data[data.length-1]["currency"]) }} + {%= format_currency(balance_row["range4"], data[data.length-1]["currency"]) %} - {{ frappe.utils.fmt_money(balance_row["range5"], data[data.length-1]["currency"]) }} + {%= format_currency(balance_row["range5"], data[data.length-1]["currency"]) %} - {{ frappe.utils.fmt_money(flt(balance_row["outstanding"]), data[data.length-1]["currency"]) }} + {%= format_currency(flt(balance_row["outstanding"]), data[data.length-1]["currency"]) %}
{{ _("Future Payments") }}{%= __("Future Payments") %} - {{ frappe.utils.fmt_money(flt(balance_row[("future_amount")]), data[data.length-1]["currency"]) }} + {%= format_currency(flt(balance_row[("future_amount")]), data[data.length-1]["currency"]) %}
- {% endif %} - {% endif %} + {% } %} + {% } %} - {% if(report.report_name == "Accounts Receivable" or report.report_name == "Accounts Payable") %} - - + {% if(report.report_name === "Accounts Receivable" || report.report_name === "Accounts Payable") { %} + + - {% if(report.report_name == "Accounts Receivable" and filters.show_sales_person) %} - - - {% else %} - - {% endif %} - {% if not(filters.show_future_payments) %} - - {% endif %} - - {% if not(filters.show_future_payments) %} - - - {% endif %} - - {% if(filters.show_future_payments) %} - {% if(report.report_name == "Accounts Receivable") %} - - {% endif %} - - - - {% endif %} - {% else %} - - - - - - {% endif %} + {% if(report.report_name === "Accounts Receivable" && filters.show_sales_person) { %} + + + {% } else { %} + + {% } %} + {% if(!filters.show_future_payments) { %} + + {% } %} + + {% if(!filters.show_future_payments) { %} + + + {% } %} + + {% if(filters.show_future_payments) { %} + {% if(report.report_name === "Accounts Receivable") { %} + + {% } %} + + + + {% } %} + {% } else { %} + + + + + + {% } %} - {% for i in range(data|length) %} + {% for(var i=0, l=data.length; i - {% if(report.report_name == "Accounts Receivable" or report.report_name == "Accounts Payable") %} - {% if(data[i]["party"]) %} - - + {% if(report.report_name === "Accounts Receivable" || report.report_name === "Accounts Payable") { %} + {% if(data[i]["party"]) { %} + + - {% if(report.report_name == "Accounts Receivable" and filters.show_sales_person) %} - - {% endif %} + {% if(report.report_name === "Accounts Receivable" && filters.show_sales_person) { %} + + {% } %} - {% if not (filters.show_future_payments) %} + {% if(!filters.show_future_payments) { %} - {% endif %} + {% } %} + {%= format_currency(data[i]["invoiced"], data[i]["currency"]) %} - {% if not(filters.show_future_payments) %} + {% if(!filters.show_future_payments) { %} + {%= format_currency(data[i]["paid"], data[i]["currency"]) %} - {% endif %} + {%= format_currency(data[i]["credit_note"], data[i]["currency"]) %} + {% } %} + {%= format_currency(data[i]["outstanding"], data[i]["currency"]) %} - {% if(filters.show_future_payments) %} - {% if(report.report_name == "Accounts Receivable") %} + {% if(filters.show_future_payments) { %} + {% if(report.report_name === "Accounts Receivable") { %} - {% endif %} - - - - {% endif %} - {% else %} + {%= data[i]["po_no"] %} + {% } %} + + + + {% } %} + {% } else { %} - {% if not(filters.show_future_payments) %} + {% if(!filters.show_future_payments) { %} - {% endif %} - {% if(report.report_name == "Accounts Receivable" and filters.show_sales_person) %} + {% } %} + {% if(report.report_name === "Accounts Receivable" && filters.show_sales_person) { %} - {% endif %} + {% } %} - + + {%= format_currency(data[i]["invoiced"], data[i]["currency"] ) %} - {% if not(filters.show_future_payments) %} + {% if(!filters.show_future_payments) { %} - - {% endif %} + {%= format_currency(data[i]["paid"], data[i]["currency"]) %} + + {% } %} + {%= format_currency(data[i]["outstanding"], data[i]["currency"]) %} - {% if(filters.show_future_payments) %} - {% if(report.report_name == "Accounts Receivable") %} + {% if(filters.show_future_payments) { %} + {% if(report.report_name === "Accounts Receivable") { %} - {% endif %} - - - - {% endif %} - {% endif %} - {% else %} - {% if(data[i]["party"] or " ") %} - {% if not(data[i]["is_total_row"]) %} + {%= data[i]["po_no"] %} + {% } %} + + + + {% } %} + {% } %} + {% } else { %} + {% if(data[i]["party"]|| " ") { %} + {% if(!data[i]["is_total_row"]) { %} - {% else %} - - {% endif %} - - - - - {% endif %} - {% endif %} + {% } else { %} + + {% } %} + + + + + {% } %} + {% } %} - {% endfor %} - - - - - - - - + {% } %}
{{ _("Date") }}{{ _("Age (Days)") }}{%= __("Date") %}{%= __("Age (Days)") %}{{ _("Reference") }}{{ _("Sales Person") }}{{ _("Reference") }} - {% if (filters.customer or filters.supplier or filters.customer_name) %} - {{ _("Remarks") }} - {% else %} - {{ _("Party") }} - {% endif %} - {{ _("Invoiced Amount") }}{{ _("Paid Amount") }} - {% if report.report_name == "Accounts Receivable" %} - {{ _('Credit Note') }} - {% else %} - {{ _('Debit Note') }} - {% endif %} - {{ _("Outstanding Amount") }}{{ _("Customer LPO No.") }}{{ _("Future Payment Ref") }}{{ _("Future Payment Amount") }}{{ _("Remaining Balance") }} - {% if (filters.customer or filters.supplier or filters.customer_name) %} - {{ _("Remarks")}} - {% else %} - {{ _("Party") }} - {% endif %} - {{ _("Total Invoiced Amount") }}{{ _("Total Paid Amount") }} - {% if report.report_name == "Accounts Receivable Summary" %} - {{ _('Credit Note Amount') }} - {% else %} - {{ _('Debit Note Amount') }} - {% endif %} - {{ _("Total Outstanding Amount") }}{%= __("Reference") %}{%= __("Sales Person") %}{%= __("Reference") %}{%= (filters.customer || filters.supplier) ? __("Remarks"): __("Party") %}{%= __("Invoiced Amount") %}{%= __("Paid Amount") %}{%= report.report_name === "Accounts Receivable" ? __('Credit Note') : __('Debit Note') %}{%= __("Outstanding Amount") %}{%= __("Customer LPO No.") %}{%= __("Future Payment Ref") %}{%= __("Future Payment Amount") %}{%= __("Remaining Balance") %}{%= (filters.customer || filters.supplier) ? __("Remarks"): __("Party") %}{%= __("Total Invoiced Amount") %}{%= __("Total Paid Amount") %}{%= report.report_name === "Accounts Receivable Summary" ? __('Credit Note Amount') : __('Debit Note Amount') %}{%= __("Total Outstanding Amount") %}
{{ (data[i]["posting_date"]) }}{{ data[i]["age"] }}{%= frappe.datetime.str_to_user(data[i]["posting_date"]) %}{%= data[i]["age"] %} - {% if not(filters.show_future_payments) %} - {{ data[i]["voucher_type"] }} + {% if(!filters.show_future_payments) { %} + {%= data[i]["voucher_type"] %}
- {% endif %} - {{ data[i]["voucher_no"] }} + {% } %} + {%= data[i]["voucher_no"] %}
{{ data[i]["sales_person"] }}{%= data[i]["sales_person"] %} - {% if(not(filters.customer or filters.supplier or filters.customer_name)) %} - {{ data[i]["party"] }} - {% if(data[i]["customer_name"] and data[i]["customer_name"] != data[i]["party"]) %} -
{{ data[i]["customer_name"] }} - {% elif(data[i]["supplier_name"] != data[i]["party"]) %} -
{{ data[i]["supplier_name"] }} - {% endif %} - {% endif %} + {% if(!(filters.customer || filters.supplier)) { %} + {%= data[i]["party"] %} + {% if(data[i]["customer_name"] && data[i]["customer_name"] != data[i]["party"]) { %} +
{%= data[i]["customer_name"] %} + {% } else if(data[i]["supplier_name"] != data[i]["party"]) { %} +
{%= data[i]["supplier_name"] %} + {% } %} + {% } %}
{% if data[i]["remarks"] %} - {{ _("Remarks") }}: - {{ data[i]["remarks"] }} - {% endif %} + {%= __("Remarks") %}: + {%= data[i]["remarks"] %} + {% } %}
- {{ frappe.utils.fmt_money(data[i]["invoiced"], currency=data[i]["currency"]) }} - {{ frappe.utils.fmt_money(data[i]["paid"], currency=data[i]["currency"]) }} - {{ frappe.utils.fmt_money(data[i]["credit_note"], currency=data[i]["currency"]) }} - {{ frappe.utils.fmt_money(data[i]["outstanding"], currency=data[i]["currency"]) }} - {{ data[i]["po_no"] }}{{ data[i]["future_ref"] }}{{ frappe.utils.fmt_money(data[i]["future_amount"], currency=data[i]["currency"]) }}{{ frappe.utils.fmt_money(data[i]["remaining_balance"], currency=data[i]["currency"]) }}{%= data[i]["future_ref"] %}{%= format_currency(data[i]["future_amount"], data[i]["currency"]) %}{%= format_currency(data[i]["remaining_balance"], data[i]["currency"]) %}{{ _("Total") }}{%= __("Total") %} - {{ frappe.utils.fmt_money(data[i]["invoiced"], data[i]["currency"]) }} - {{ frappe.utils.fmt_money(data[i]["paid"], currency=data[i]["currency"]) }}{{ frappe.utils.fmt_money(data[i]["credit_note"], currency=data[i]["currency"]) }} {%= format_currency(data[i]["credit_note"], data[i]["currency"]) %} - {{ frappe.utils.fmt_money(data[i]["outstanding"], currency=data[i]["currency"]) }} - {{ data[i]["po_no"] }}{{ data[i]["future_ref"] }}{{ frappe.utils.fmt_money(data[i]["future_amount"], currency=data[i]["currency"]) }}{{ frappe.utils.fmt_money(data[i]["remaining_balance"], currency=data[i]["currency"]) }}{%= data[i]["future_ref"] %}{%= format_currency(data[i]["future_amount"], data[i]["currency"]) %}{%= format_currency(data[i]["remaining_balance"], data[i]["currency"]) %} - {% if(not(filters.customer | filters.supplier)) %} - {{ data[i]["party"] }} - {% if(data[i]["customer_name"] and data[i]["customer_name"] != data[i]["party"]) %} -
{{ data[i]["customer_name"] }} - {% elif(data[i]["supplier_name"] != data[i]["party"]) %} -
{{ data[i]["supplier_name"] }} - {% endif %} - {% endif %} -
{{ _("Remarks") }}: - {{ data[i]["remarks"] }} + {% if(!(filters.customer || filters.supplier)) { %} + {%= data[i]["party"] %} + {% if(data[i]["customer_name"] && data[i]["customer_name"] != data[i]["party"]) { %} +
{%= data[i]["customer_name"] %} + {% } else if(data[i]["supplier_name"] != data[i]["party"]) { %} +
{%= data[i]["supplier_name"] %} + {% } %} + {% } %} +
{%= __("Remarks") %}: + {%= data[i]["remarks"] %}
{{ _("Total") }}{{ frappe.utils.fmt_money(data[i]["invoiced"], currency=data[i]["currency"]) }}{{ frappe.utils.fmt_money(data[i]["paid"], currency=data[i]["currency"]) }}{{ frappe.utils.fmt_money(data[i]["credit_note"], currency=data[i]["currency"]) }}{{ frappe.utils.fmt_money(data[i]["outstanding"], currency=data[i]["currency"]) }}{%= __("Total") %}{%= format_currency(data[i]["invoiced"], data[i]["currency"]) %}{%= format_currency(data[i]["paid"], data[i]["currency"]) %}{%= format_currency(data[i]["credit_note"], data[i]["currency"]) %}{%= format_currency(data[i]["outstanding"], data[i]["currency"]) %}
{{ frappe.utils.fmt_money(data|sum(attribute="invoiced"), currency=data[0]["currency"]) }}{{ frappe.utils.fmt_money(data|sum(attribute="paid"), currency=data[0]["currency"]) }}{{ frappe.utils.fmt_money(data|sum(attribute="credit_note"), currency=data[0]["currency"]) }}{{ frappe.utils.fmt_money(data|sum(attribute="outstanding"), currency=data[0]["currency"]) }}
-
- {% if ageing %} -

{{ _("Ageing Report based on ") }} {{ ageing.ageing_based_on }} - {{ _("up to " ) }} {{ frappe.format(filters.report_date, 'Date')}} -

- - - - - - - - - - - - - - - - - -
30 Days60 Days90 Days120 Days
{{ frappe.utils.fmt_money(ageing.range1, currency=data[0]["currency"]) }}{{ frappe.utils.fmt_money(ageing.range2, currency=data[0]["currency"]) }}{{ frappe.utils.fmt_money(ageing.range3, currency=data[0]["currency"]) }}{{ frappe.utils.fmt_money(ageing.range4, currency=data[0]["currency"]) }}
- {% endif %} -

{{ _("Printed On ") }}{{ frappe.utils.now() }}

+

{{ __("Printed On ") }}{%= frappe.datetime.str_to_user(frappe.datetime.get_datetime_as_string()) %}

\ No newline at end of file diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 18bd10f45f8..8c0fa6bbcda 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -339,3 +339,4 @@ execute:frappe.delete_doc('DocType', 'Cash Flow Mapper', ignore_missing=True) execute:frappe.delete_doc('DocType', 'Cash Flow Mapping Template', ignore_missing=True) execute:frappe.delete_doc('DocType', 'Cash Flow Mapping Accounts', ignore_missing=True) erpnext.patches.v14_0.cleanup_workspaces +erpnext.patches.v14_0.set_report_in_process_SOA diff --git a/erpnext/patches/v14_0/set_report_in_process_SOA.py b/erpnext/patches/v14_0/set_report_in_process_SOA.py new file mode 100644 index 00000000000..1cb7e415d83 --- /dev/null +++ b/erpnext/patches/v14_0/set_report_in_process_SOA.py @@ -0,0 +1,10 @@ +# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and Contributors +# License: MIT. See LICENSE + +import frappe + + +def execute(): + process_soa = frappe.qb.DocType("Process Statement of Accounts") + q = frappe.qb.update(process_soa).set(process_soa.report, "General Ledger") + q.run() From cde82bc0cc978c3f4fec183825b709460062f8a8 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Fri, 23 Jun 2023 12:12:32 +0530 Subject: [PATCH 36/63] fix: modify patch --- erpnext/patches/v14_0/set_report_in_process_SOA.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/patches/v14_0/set_report_in_process_SOA.py b/erpnext/patches/v14_0/set_report_in_process_SOA.py index 1cb7e415d83..0f65b364046 100644 --- a/erpnext/patches/v14_0/set_report_in_process_SOA.py +++ b/erpnext/patches/v14_0/set_report_in_process_SOA.py @@ -5,6 +5,7 @@ import frappe def execute(): + frappe.reload_doc("accounts", "doctype", "Process Statement of Accounts", force=True) process_soa = frappe.qb.DocType("Process Statement of Accounts") q = frappe.qb.update(process_soa).set(process_soa.report, "General Ledger") q.run() From 9e73af891d9ff514521a2b4613aab938ef74437f Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 23 Jun 2023 14:50:14 +0530 Subject: [PATCH 37/63] fix: get base grand total while pulling reference details in PE --- erpnext/accounts/doctype/payment_entry/payment_entry.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 1f23fe1d542..712023fdf96 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -1735,7 +1735,7 @@ 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("grand_total") or ref_doc.get("base_grand_total") + total_amount = ref_doc.get("base_grand_total") or ref_doc.get("grand_total") exchange_rate = 1 else: total_amount = ref_doc.get("grand_total") From 2868baebab6a3c76ca4121c7098ebc27d0f4b04a Mon Sep 17 00:00:00 2001 From: Marica Date: Fri, 23 Jun 2023 16:00:20 +0530 Subject: [PATCH 38/63] fix: Payment Term must be mandatory if `Allocate Payment based on ..` is checked (#35798) - Front and Back end validation of condition - Fix test to accomodate fix --- .../payment_terms_template/payment_terms_template.js | 6 +++++- .../payment_terms_template/payment_terms_template.py | 7 +++++-- .../doctype/purchase_receipt/test_purchase_receipt.py | 7 +++++++ 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/erpnext/accounts/doctype/payment_terms_template/payment_terms_template.js b/erpnext/accounts/doctype/payment_terms_template/payment_terms_template.js index ea18adefa35..6046c13e146 100644 --- a/erpnext/accounts/doctype/payment_terms_template/payment_terms_template.js +++ b/erpnext/accounts/doctype/payment_terms_template/payment_terms_template.js @@ -2,7 +2,11 @@ // For license information, please see license.txt frappe.ui.form.on('Payment Terms Template', { - setup: function(frm) { + refresh: function(frm) { + frm.fields_dict.terms.grid.toggle_reqd("payment_term", frm.doc.allocate_payment_based_on_payment_terms); + }, + allocate_payment_based_on_payment_terms: function(frm) { + frm.fields_dict.terms.grid.toggle_reqd("payment_term", frm.doc.allocate_payment_based_on_payment_terms); } }); diff --git a/erpnext/accounts/doctype/payment_terms_template/payment_terms_template.py b/erpnext/accounts/doctype/payment_terms_template/payment_terms_template.py index ea3b76c5243..7b04a68e89a 100644 --- a/erpnext/accounts/doctype/payment_terms_template/payment_terms_template.py +++ b/erpnext/accounts/doctype/payment_terms_template/payment_terms_template.py @@ -11,7 +11,7 @@ from frappe.utils import flt class PaymentTermsTemplate(Document): def validate(self): self.validate_invoice_portion() - self.check_duplicate_terms() + self.validate_terms() def validate_invoice_portion(self): total_portion = 0 @@ -23,9 +23,12 @@ class PaymentTermsTemplate(Document): _("Combined invoice portion must equal 100%"), raise_exception=1, indicator="red" ) - def check_duplicate_terms(self): + def validate_terms(self): terms = [] for term in self.terms: + if self.allocate_payment_based_on_payment_terms and not term.payment_term: + frappe.throw(_("Row {0}: Payment Term is mandatory").format(term.idx)) + term_info = (term.payment_term, term.credit_days, term.credit_months, term.due_date_based_on) if term_info in terms: frappe.msgprint( diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index 19867225876..c6c84cadc81 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -72,6 +72,11 @@ class TestPurchaseReceipt(FrappeTestCase): self.assertEqual(sl_entry_cancelled[1].actual_qty, -0.5) def test_make_purchase_invoice(self): + from erpnext.accounts.doctype.payment_entry.test_payment_entry import create_payment_term + + create_payment_term("_Test Payment Term 1 for Purchase Invoice") + create_payment_term("_Test Payment Term 2 for Purchase Invoice") + if not frappe.db.exists( "Payment Terms Template", "_Test Payment Terms Template For Purchase Invoice" ): @@ -83,12 +88,14 @@ class TestPurchaseReceipt(FrappeTestCase): "terms": [ { "doctype": "Payment Terms Template Detail", + "payment_term": "_Test Payment Term 1 for Purchase Invoice", "invoice_portion": 50.00, "credit_days_based_on": "Day(s) after invoice date", "credit_days": 00, }, { "doctype": "Payment Terms Template Detail", + "payment_term": "_Test Payment Term 2 for Purchase Invoice", "invoice_portion": 50.00, "credit_days_based_on": "Day(s) after invoice date", "credit_days": 30, From 9655d786428467c5ce27f556612f9bf1ea5f802c Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 23 Jun 2023 16:35:44 +0530 Subject: [PATCH 39/63] test: test reference details response --- .../payment_entry/test_payment_entry.py | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py index 278b12f6595..ae2625b6539 100644 --- a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py @@ -11,6 +11,7 @@ from frappe.utils import flt, nowdate from erpnext.accounts.doctype.payment_entry.payment_entry import ( InvalidPaymentEntry, get_payment_entry, + get_reference_details, ) from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import ( make_purchase_invoice, @@ -1037,6 +1038,29 @@ class TestPaymentEntry(FrappeTestCase): self.assertRaises(frappe.ValidationError, pe_draft.submit) + def test_details_update_on_reference_table(self): + so = make_sales_order( + customer="_Test Customer USD", currency="USD", qty=1, rate=100, do_not_submit=True + ) + so.conversion_rate = 50 + so.submit() + pe = get_payment_entry("Sales Order", so.name) + pe.references.clear() + pe.paid_from = "Debtors - _TC" + pe.paid_from_account_currency = "INR" + pe.source_exchange_rate = 50 + pe.save() + + ref_details = get_reference_details(so.doctype, so.name, pe.paid_from_account_currency) + expected_response = { + "total_amount": 5000.0, + "outstanding_amount": 5000.0, + "exchange_rate": 1.0, + "due_date": None, + "bill_no": None, + } + self.assertDictEqual(ref_details, expected_response) + def create_payment_entry(**args): payment_entry = frappe.new_doc("Payment Entry") From 802c89ffb33aa7ff8971a00db5a48c4b8ae737de Mon Sep 17 00:00:00 2001 From: Devin Slauenwhite Date: Sat, 24 Jun 2023 03:01:16 -0400 Subject: [PATCH 40/63] feat: allow Sales Invoice as data source (#35855) * feat: allow Sales Invoice as data source * chore: linter --- .../exponential_smoothing_forecasting.js | 2 +- .../exponential_smoothing_forecasting.py | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/erpnext/manufacturing/report/exponential_smoothing_forecasting/exponential_smoothing_forecasting.js b/erpnext/manufacturing/report/exponential_smoothing_forecasting/exponential_smoothing_forecasting.js index 123a82a3882..a3f0d008772 100644 --- a/erpnext/manufacturing/report/exponential_smoothing_forecasting/exponential_smoothing_forecasting.js +++ b/erpnext/manufacturing/report/exponential_smoothing_forecasting/exponential_smoothing_forecasting.js @@ -30,7 +30,7 @@ frappe.query_reports["Exponential Smoothing Forecasting"] = { "fieldname":"based_on_document", "label": __("Based On Document"), "fieldtype": "Select", - "options": ["Sales Order", "Delivery Note", "Quotation"], + "options": ["Sales Order", "Sales Invoice", "Delivery Note", "Quotation"], "default": "Sales Order", "reqd": 1 }, diff --git a/erpnext/manufacturing/report/exponential_smoothing_forecasting/exponential_smoothing_forecasting.py b/erpnext/manufacturing/report/exponential_smoothing_forecasting/exponential_smoothing_forecasting.py index d3bce831551..daef7f6cca7 100644 --- a/erpnext/manufacturing/report/exponential_smoothing_forecasting/exponential_smoothing_forecasting.py +++ b/erpnext/manufacturing/report/exponential_smoothing_forecasting/exponential_smoothing_forecasting.py @@ -99,7 +99,9 @@ class ForecastingReport(ExponentialSmoothingForecast): parent = frappe.qb.DocType(self.doctype) child = frappe.qb.DocType(self.child_doctype) - date_field = "posting_date" if self.doctype == "Delivery Note" else "transaction_date" + date_field = ( + "posting_date" if self.doctype in ("Delivery Note", "Sales Invoice") else "transaction_date" + ) query = ( frappe.qb.from_(parent) From f9ed8c10ab97e39e655c345a66779e3bf3387359 Mon Sep 17 00:00:00 2001 From: Devin Slauenwhite Date: Sat, 24 Jun 2023 06:33:15 -0400 Subject: [PATCH 41/63] fix: make reorder_level not required (#35831) * fix: make reorder_level not required * fix: allow material request to be made if projected_qty <= reorder_level --- erpnext/stock/doctype/item_reorder/item_reorder.json | 6 +++--- erpnext/stock/reorder_item.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/erpnext/stock/doctype/item_reorder/item_reorder.json b/erpnext/stock/doctype/item_reorder/item_reorder.json index fb4c558cfd7..a03bd458d4f 100644 --- a/erpnext/stock/doctype/item_reorder/item_reorder.json +++ b/erpnext/stock/doctype/item_reorder/item_reorder.json @@ -81,7 +81,7 @@ "print_hide_if_no_value": 0, "read_only": 0, "report_hide": 0, - "reqd": 1, + "reqd": 0, "search_index": 0, "set_only_once": 0, "unique": 0 @@ -147,7 +147,7 @@ "issingle": 0, "istable": 1, "max_attachments": 0, - "modified": "2016-07-28 19:15:38.270046", + "modified": "2023-06-21 15:13:38.270046", "modified_by": "Administrator", "module": "Stock", "name": "Item Reorder", @@ -158,4 +158,4 @@ "read_only_onload": 0, "sort_order": "ASC", "track_seen": 0 -} \ No newline at end of file +} diff --git a/erpnext/stock/reorder_item.py b/erpnext/stock/reorder_item.py index 136c78ff586..907560826b3 100644 --- a/erpnext/stock/reorder_item.py +++ b/erpnext/stock/reorder_item.py @@ -67,7 +67,7 @@ def _reorder_item(): else: projected_qty = flt(item_warehouse_projected_qty.get(item_code, {}).get(warehouse)) - if (reorder_level or reorder_qty) and projected_qty < reorder_level: + if (reorder_level or reorder_qty) and projected_qty <= reorder_level: deficiency = reorder_level - projected_qty if deficiency > reorder_qty: reorder_qty = deficiency From 4de7a4c5718acbc144729f143cdfc0f61a3693f7 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sat, 24 Jun 2023 16:31:19 +0530 Subject: [PATCH 42/63] chore: update typo in patch --- erpnext/patches/v14_0/set_report_in_process_SOA.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/erpnext/patches/v14_0/set_report_in_process_SOA.py b/erpnext/patches/v14_0/set_report_in_process_SOA.py index 0f65b364046..9eb5e3ab9bd 100644 --- a/erpnext/patches/v14_0/set_report_in_process_SOA.py +++ b/erpnext/patches/v14_0/set_report_in_process_SOA.py @@ -5,7 +5,6 @@ import frappe def execute(): - frappe.reload_doc("accounts", "doctype", "Process Statement of Accounts", force=True) - process_soa = frappe.qb.DocType("Process Statement of Accounts") + process_soa = frappe.qb.DocType("Process Statement Of Accounts") q = frappe.qb.update(process_soa).set(process_soa.report, "General Ledger") q.run() From 1e2001605973741f7cf7d919b79d7f618cac632a Mon Sep 17 00:00:00 2001 From: HarryPaulo Date: Sat, 24 Jun 2023 08:04:24 -0300 Subject: [PATCH 43/63] fix: POS Closing Entry load all invoices with one request on save (#35819) fix: load all invoices with one request --- .../pos_closing_entry/pos_closing_entry.js | 39 +++++++++++-------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.js b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.js index e6d9fe2b54d..a6c0102a7f9 100644 --- a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.js +++ b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.js @@ -123,22 +123,29 @@ frappe.ui.form.on('POS Closing Entry', { row.expected_amount = row.opening_amount; } - const pos_inv_promises = frm.doc.pos_transactions.map( - row => frappe.db.get_doc("POS Invoice", row.pos_invoice) - ); - - const pos_invoices = await Promise.all(pos_inv_promises); - - for (let doc of pos_invoices) { - frm.doc.grand_total += flt(doc.grand_total); - frm.doc.net_total += flt(doc.net_total); - frm.doc.total_quantity += flt(doc.total_qty); - refresh_payments(doc, frm); - refresh_taxes(doc, frm); - refresh_fields(frm); - set_html_data(frm); - } - + await Promise.all([ + frappe.call({ + method: 'erpnext.accounts.doctype.pos_closing_entry.pos_closing_entry.get_pos_invoices', + args: { + start: frappe.datetime.get_datetime_as_string(frm.doc.period_start_date), + end: frappe.datetime.get_datetime_as_string(frm.doc.period_end_date), + pos_profile: frm.doc.pos_profile, + user: frm.doc.user + }, + callback: (r) => { + let pos_invoices = r.message; + for (let doc of pos_invoices) { + frm.doc.grand_total += flt(doc.grand_total); + frm.doc.net_total += flt(doc.net_total); + frm.doc.total_quantity += flt(doc.total_qty); + refresh_payments(doc, frm); + refresh_taxes(doc, frm); + refresh_fields(frm); + set_html_data(frm); + } + } + }) + ]) frappe.dom.unfreeze(); } }); From feafa956f70e448033a216fdfc77782bcbd08f58 Mon Sep 17 00:00:00 2001 From: Patrick Eissler <77415730+PatrickDenis-stack@users.noreply.github.com> Date: Sat, 24 Jun 2023 13:05:52 +0200 Subject: [PATCH 44/63] feat: add German translations for new email template feature (#35865) --- erpnext/translations/de.csv | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/erpnext/translations/de.csv b/erpnext/translations/de.csv index 68358c6b106..f9ec68935c3 100644 --- a/erpnext/translations/de.csv +++ b/erpnext/translations/de.csv @@ -9905,3 +9905,7 @@ Select Alternative Items for Sales Order,Alternativpositionen für Auftragsbest Select an item from each set to be used in the Sales Order.,"Wählen Sie aus den Alternativen jeweils einen Artikel aus, der in die Auftragsbestätigung übernommen werden soll.", Is Alternative,Ist Alternative, Alternative Items,Alternativpositionen, +Add Template,Vorlage einfügen, +Prepend the template to the email message,Vorlage oberhalb der Email-Nachricht einfügen, +Clear & Add Template,Leeren und Vorlage einfügen, +Clear the email message and add the template,Email-Feld leeren und Vorlage einfügen, From bcff4b0e5afea4d1abd17af8ef2138fa0a93bdd2 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sun, 25 Jun 2023 16:03:58 +0530 Subject: [PATCH 45/63] chore: linting issues --- .../accounting_dimension_filter/accounting_dimension_filter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/accounting_dimension_filter/accounting_dimension_filter.py b/erpnext/accounts/doctype/accounting_dimension_filter/accounting_dimension_filter.py index 2a6c76dd4ae..a79f13df055 100644 --- a/erpnext/accounts/doctype/accounting_dimension_filter/accounting_dimension_filter.py +++ b/erpnext/accounts/doctype/accounting_dimension_filter/accounting_dimension_filter.py @@ -13,7 +13,7 @@ class AccountingDimensionFilter(Document): # If restriction is not applied on values, then remove all the dimensions and set allow_or_restrict to Restrict if not self.apply_restriction_on_values: self.allow_or_restrict = "Restrict" - self.set('dimensions', []) + self.set("dimensions", []) def validate(self): self.validate_applicable_accounts() From 63b126967ea685e9d2cecc525b97864b91b48363 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sun, 25 Jun 2023 16:24:22 +0530 Subject: [PATCH 46/63] chore: Linting Issues --- .../accounting_dimension_filter/accounting_dimension_filter.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/accounting_dimension_filter/accounting_dimension_filter.py b/erpnext/accounts/doctype/accounting_dimension_filter/accounting_dimension_filter.py index a79f13df055..de1b82c1d5f 100644 --- a/erpnext/accounts/doctype/accounting_dimension_filter/accounting_dimension_filter.py +++ b/erpnext/accounts/doctype/accounting_dimension_filter/accounting_dimension_filter.py @@ -8,13 +8,12 @@ from frappe.model.document import Document class AccountingDimensionFilter(Document): - def before_save(self): # If restriction is not applied on values, then remove all the dimensions and set allow_or_restrict to Restrict if not self.apply_restriction_on_values: self.allow_or_restrict = "Restrict" self.set("dimensions", []) - + def validate(self): self.validate_applicable_accounts() From 36d26d40a0e8e38b5a76dd13147060fd5a7af980 Mon Sep 17 00:00:00 2001 From: Anand Baburajan Date: Mon, 26 Jun 2023 10:58:36 +0530 Subject: [PATCH 47/63] perf: improve asset depr schedule creation patch (#35867) --- ...sset_depreciation_schedules_from_assets.py | 74 ++++++++++++++++--- 1 file changed, 62 insertions(+), 12 deletions(-) diff --git a/erpnext/patches/v15_0/create_asset_depreciation_schedules_from_assets.py b/erpnext/patches/v15_0/create_asset_depreciation_schedules_from_assets.py index 5c46bf32807..a53adf1a83a 100644 --- a/erpnext/patches/v15_0/create_asset_depreciation_schedules_from_assets.py +++ b/erpnext/patches/v15_0/create_asset_depreciation_schedules_from_assets.py @@ -6,10 +6,14 @@ def execute(): assets = get_details_of_draft_or_submitted_depreciable_assets() - for asset in assets: - finance_book_rows = get_details_of_asset_finance_books_rows(asset.name) + asset_finance_books_map = get_asset_finance_books_map() - for fb_row in finance_book_rows: + asset_depreciation_schedules_map = get_asset_depreciation_schedules_map() + + for asset in assets: + depreciation_schedules = asset_depreciation_schedules_map[asset.name] + + for fb_row in asset_finance_books_map[asset.name]: asset_depr_schedule_doc = frappe.new_doc("Asset Depreciation Schedule") asset_depr_schedule_doc.set_draft_asset_depr_schedule_details(asset, fb_row) @@ -19,7 +23,11 @@ def execute(): if asset.docstatus == 1: asset_depr_schedule_doc.submit() - update_depreciation_schedules(asset.name, asset_depr_schedule_doc.name, fb_row.idx) + depreciation_schedules_of_fb_row = [ + ds for ds in depreciation_schedules if ds["finance_book_id"] == str(fb_row.idx) + ] + + update_depreciation_schedules(depreciation_schedules_of_fb_row, asset_depr_schedule_doc.name) def get_details_of_draft_or_submitted_depreciable_assets(): @@ -41,12 +49,33 @@ def get_details_of_draft_or_submitted_depreciable_assets(): return records -def get_details_of_asset_finance_books_rows(asset_name): +def group_records_by_asset_name(records): + grouped_dict = {} + + for item in records: + key = list(item.keys())[0] + value = item[key] + + if value not in grouped_dict: + grouped_dict[value] = [] + + del item["asset_name"] + + grouped_dict[value].append(item) + + return grouped_dict + + +def get_asset_finance_books_map(): afb = frappe.qb.DocType("Asset Finance Book") + asset = frappe.qb.DocType("Asset") records = ( frappe.qb.from_(afb) + .join(asset) + .on(afb.parent == asset.name) .select( + asset.name.as_("asset_name"), afb.finance_book, afb.idx, afb.depreciation_method, @@ -55,23 +84,44 @@ def get_details_of_asset_finance_books_rows(asset_name): afb.rate_of_depreciation, afb.expected_value_after_useful_life, ) - .where(afb.parent == asset_name) + .where(asset.docstatus < 2) + .orderby(afb.idx) ).run(as_dict=True) - return records + asset_finance_books_map = group_records_by_asset_name(records) + + return asset_finance_books_map -def update_depreciation_schedules(asset_name, asset_depr_schedule_name, fb_row_idx): +def get_asset_depreciation_schedules_map(): ds = frappe.qb.DocType("Depreciation Schedule") + asset = frappe.qb.DocType("Asset") - depr_schedules = ( + records = ( frappe.qb.from_(ds) - .select(ds.name) - .where((ds.parent == asset_name) & (ds.finance_book_id == str(fb_row_idx))) + .join(asset) + .on(ds.parent == asset.name) + .select( + asset.name.as_("asset_name"), + ds.name, + ds.finance_book_id, + ) + .where(asset.docstatus < 2) .orderby(ds.idx) ).run(as_dict=True) - for idx, depr_schedule in enumerate(depr_schedules, start=1): + asset_depreciation_schedules_map = group_records_by_asset_name(records) + + return asset_depreciation_schedules_map + + +def update_depreciation_schedules( + depreciation_schedules, + asset_depr_schedule_name, +): + ds = frappe.qb.DocType("Depreciation Schedule") + + for idx, depr_schedule in enumerate(depreciation_schedules, start=1): ( frappe.qb.update(ds) .set(ds.idx, idx) From 881e95b44067b3de6aa7782804ed9ce7773e8324 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Mon, 26 Jun 2023 13:02:08 +0530 Subject: [PATCH 48/63] chore: Update required node version to v18 --- .github/workflows/patch.yml | 2 +- .github/workflows/semantic-commits.yml | 2 +- .github/workflows/server-tests-mariadb.yml | 2 +- .github/workflows/server-tests-postgres.yml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/patch.yml b/.github/workflows/patch.yml index e2d89573cbf..aae2928bf0d 100644 --- a/.github/workflows/patch.yml +++ b/.github/workflows/patch.yml @@ -52,7 +52,7 @@ jobs: - name: Setup Node uses: actions/setup-node@v2 with: - node-version: 14 + node-version: 18 check-latest: true - name: Add to Hosts diff --git a/.github/workflows/semantic-commits.yml b/.github/workflows/semantic-commits.yml index 1744bc33a9e..0e478d551d9 100644 --- a/.github/workflows/semantic-commits.yml +++ b/.github/workflows/semantic-commits.yml @@ -21,7 +21,7 @@ jobs: - uses: actions/setup-node@v3 with: - node-version: 14 + node-version: 18 check-latest: true - name: Check commit titles diff --git a/.github/workflows/server-tests-mariadb.yml b/.github/workflows/server-tests-mariadb.yml index 8959f7fd45a..9b4db49d084 100644 --- a/.github/workflows/server-tests-mariadb.yml +++ b/.github/workflows/server-tests-mariadb.yml @@ -71,7 +71,7 @@ jobs: - name: Setup Node uses: actions/setup-node@v2 with: - node-version: 14 + node-version: 18 check-latest: true - name: Add to Hosts diff --git a/.github/workflows/server-tests-postgres.yml b/.github/workflows/server-tests-postgres.yml index df438014789..a6887066570 100644 --- a/.github/workflows/server-tests-postgres.yml +++ b/.github/workflows/server-tests-postgres.yml @@ -59,7 +59,7 @@ jobs: - name: Setup Node uses: actions/setup-node@v2 with: - node-version: 14 + node-version: 18 check-latest: true - name: Add to Hosts From 9cf645e07f4f711d9761129fa6e5d7f6eb2ac04b Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Mon, 26 Jun 2023 16:00:53 +0530 Subject: [PATCH 49/63] fix: serial and batch bundle values in the standard print format --- erpnext/controllers/print_settings.py | 1 + ...receipt_serial_and_batch_bundle_print.json | 4 +-- erpnext/stock/serial_batch_bundle.py | 30 ++++++++++++++++++- .../includes/serial_and_batch_bundle.html | 4 +++ 4 files changed, 36 insertions(+), 3 deletions(-) create mode 100644 erpnext/templates/print_formats/includes/serial_and_batch_bundle.html diff --git a/erpnext/controllers/print_settings.py b/erpnext/controllers/print_settings.py index c951154a9e0..d86607d8dbc 100644 --- a/erpnext/controllers/print_settings.py +++ b/erpnext/controllers/print_settings.py @@ -10,6 +10,7 @@ def set_print_templates_for_item_table(doc, settings): doc.child_print_templates = { "items": { "qty": "templates/print_formats/includes/item_table_qty.html", + "serial_and_batch_bundle": "templates/print_formats/includes/serial_and_batch_bundle.html", } } diff --git a/erpnext/stock/print_format/purchase_receipt_serial_and_batch_bundle_print/purchase_receipt_serial_and_batch_bundle_print.json b/erpnext/stock/print_format/purchase_receipt_serial_and_batch_bundle_print/purchase_receipt_serial_and_batch_bundle_print.json index 21132e070c5..a8ab8f6ac7d 100644 --- a/erpnext/stock/print_format/purchase_receipt_serial_and_batch_bundle_print/purchase_receipt_serial_and_batch_bundle_print.json +++ b/erpnext/stock/print_format/purchase_receipt_serial_and_batch_bundle_print/purchase_receipt_serial_and_batch_bundle_print.json @@ -8,14 +8,14 @@ "docstatus": 0, "doctype": "Print Format", "font_size": 14, - "format_data": "[{\"fieldname\": \"print_heading_template\", \"fieldtype\": \"Custom HTML\", \"options\": \"
\\t\\t\\t\\t

Purchase Receipt

{{ doc.name }}\\t\\t\\t\\t

\"}, {\"fieldtype\": \"Section Break\", \"label\": \"\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"supplier_name\", \"print_hide\": 0, \"label\": \"Supplier Name\"}, {\"fieldname\": \"supplier_delivery_note\", \"print_hide\": 0, \"label\": \"Supplier Delivery Note\"}, {\"fieldname\": \"rack\", \"print_hide\": 0, \"label\": \"Rack\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"posting_date\", \"print_hide\": 0, \"label\": \"Date\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"apply_putaway_rule\", \"print_hide\": 0, \"label\": \"Apply Putaway Rule\"}, {\"fieldtype\": \"Section Break\", \"label\": \"Accounting Dimensions\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"region\", \"print_hide\": 0, \"label\": \"Region\"}, {\"fieldname\": \"function\", \"print_hide\": 0, \"label\": \"Function\"}, {\"fieldname\": \"depot\", \"print_hide\": 0, \"label\": \"Depot\"}, {\"fieldname\": \"cost_center\", \"print_hide\": 0, \"label\": \"Cost Center\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"location\", \"print_hide\": 0, \"label\": \"Location\"}, {\"fieldname\": \"country\", \"print_hide\": 0, \"label\": \"Country\"}, {\"fieldname\": \"project\", \"print_hide\": 0, \"label\": \"Project\"}, {\"fieldtype\": \"Section Break\", \"label\": \"Items\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"scan_barcode\", \"print_hide\": 0, \"label\": \"Scan Barcode\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"set_from_warehouse\", \"print_hide\": 0, \"label\": \"Set From Warehouse\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldtype\": \"Section Break\", \"label\": \"\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"_custom_html\", \"print_hide\": 0, \"label\": \"Custom HTML\", \"fieldtype\": \"HTML\", \"options\": \"\\n\\t\\n\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\n\\t\\t{%- for row in doc.items -%}\\n\\t\\t\\n\\t\\t {% set bundle_data = get_serial_or_batch_nos(row.serial_and_batch_bundle) %}\\n\\t\\t {% set serial_nos = [] %}\\n {% set batches = {} %}\\n\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\n\\t\\t{%- endfor -%}\\n\\t\\n
SrItem NameDescriptionQtyRateAmount
{{ row.idx }}\\n\\t\\t\\t\\t{{ row.item_name }}\\n\\t\\t\\t\\t{% if row.item_code != row.item_name -%}\\n\\t\\t\\t\\t
Item Code: {{ row.item_code}}\\n\\t\\t\\t\\t{%- endif %}\\n\\t\\t\\t
\\n\\t\\t\\t\\t
{{ row.description }}
{{ row.qty }} {{ row.uom or row.stock_uom }}{{\\n\\t\\t\\t\\trow.get_formatted(\\\"rate\\\", doc) }}{{\\n\\t\\t\\t\\trow.get_formatted(\\\"amount\\\", doc) }}
\\n\"}, {\"fieldtype\": \"Section Break\", \"label\": \"\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"total_qty\", \"print_hide\": 0, \"label\": \"Total Quantity\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"total\", \"print_hide\": 0, \"label\": \"Total\"}, {\"fieldtype\": \"Section Break\", \"label\": \"\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"taxes\", \"print_hide\": 0, \"label\": \"Purchase Taxes and Charges\", \"visible_columns\": [{\"fieldname\": \"category\", \"print_width\": \"\", \"print_hide\": 0}, {\"fieldname\": \"add_deduct_tax\", \"print_width\": \"\", \"print_hide\": 0}, {\"fieldname\": \"charge_type\", \"print_width\": \"\", \"print_hide\": 0}, {\"fieldname\": \"row_id\", \"print_width\": \"\", \"print_hide\": 0}, {\"fieldname\": \"included_in_print_rate\", \"print_width\": \"\", \"print_hide\": 0}, {\"fieldname\": \"included_in_paid_amount\", \"print_width\": \"\", \"print_hide\": 0}, {\"fieldname\": \"account_head\", \"print_width\": \"\", \"print_hide\": 0}, {\"fieldname\": \"description\", \"print_width\": \"300px\", \"print_hide\": 0}, {\"fieldname\": \"rate\", \"print_width\": \"\", \"print_hide\": 0}, {\"fieldname\": \"region\", \"print_width\": \"\", \"print_hide\": 0}, {\"fieldname\": \"function\", \"print_width\": \"\", \"print_hide\": 0}, {\"fieldname\": \"location\", \"print_width\": \"\", \"print_hide\": 0}, {\"fieldname\": \"cost_center\", \"print_width\": \"\", \"print_hide\": 0}, {\"fieldname\": \"depot\", \"print_width\": \"\", \"print_hide\": 0}, {\"fieldname\": \"country\", \"print_width\": \"\", \"print_hide\": 0}, {\"fieldname\": \"account_currency\", \"print_width\": \"\", \"print_hide\": 0}, {\"fieldname\": \"tax_amount\", \"print_width\": \"\", \"print_hide\": 0}, {\"fieldname\": \"total\", \"print_width\": \"\", \"print_hide\": 0}]}, {\"fieldtype\": \"Section Break\", \"label\": \"Totals\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"grand_total\", \"print_hide\": 0, \"label\": \"Grand Total\"}, {\"fieldname\": \"rounded_total\", \"print_hide\": 0, \"label\": \"Rounded Total\"}, {\"fieldname\": \"in_words\", \"print_hide\": 0, \"label\": \"In Words\"}, {\"fieldname\": \"disable_rounded_total\", \"print_hide\": 0, \"label\": \"Disable Rounded Total\"}, {\"fieldtype\": \"Section Break\", \"label\": \"Supplier Address\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"address_display\", \"print_hide\": 0, \"label\": \"Address\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"contact_display\", \"print_hide\": 0, \"label\": \"Contact\"}, {\"fieldname\": \"contact_mobile\", \"print_hide\": 0, \"label\": \"Mobile No\"}, {\"fieldtype\": \"Section Break\", \"label\": \"Company Billing Address\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"billing_address\", \"print_hide\": 0, \"label\": \"Billing Address\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"billing_address_display\", \"print_hide\": 0, \"label\": \"Billing Address\"}, {\"fieldname\": \"terms\", \"print_hide\": 0, \"label\": \"Terms and Conditions\"}, {\"fieldtype\": \"Section Break\", \"label\": \"\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"_custom_html\", \"print_hide\": 0, \"label\": \"Custom HTML\", \"fieldtype\": \"HTML\", \"options\": \"\\n\\t\\n\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\n\\t\\t{%- for row in doc.items -%}\\n\\t\\t\\n\\t\\t {% set bundle_data = get_serial_or_batch_nos(row.serial_and_batch_bundle) %}\\n\\t\\t {% set serial_nos = [] %}\\n {% set batches = {} %}\\n \\n {% if bundle_data %}\\n\\t\\t\\t {% for data in bundle_data %}\\n\\t\\t\\t {% if data.serial_no %}\\n\\t\\t\\t {{ serial_nos.append(data.serial_no) or \\\"\\\" }}\\n\\t\\t\\t {% endif %}\\n\\t\\t\\t \\n\\t\\t\\t {% if data.batch_no %}\\n\\t\\t\\t {{ batches.update({data.batch_no: data.qty}) or \\\"\\\" }}\\n\\t\\t\\t {% endif %}\\n\\t\\t\\t {% endfor %}\\n\\t\\t\\t{% endif %}\\n\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\n\\t\\t{%- endfor -%}\\n\\t\\n
SrItem NameQtySerial NosBatch Nos (Qty)
{{ row.idx }}\\n\\t\\t\\t\\t{{ row.item_name }}\\n\\t\\t\\t\\t{% if row.item_code != row.item_name -%}\\n\\t\\t\\t\\t
Item Code: {{ row.item_code}}\\n\\t\\t\\t\\t{%- endif %}\\n\\t\\t\\t
{{ row.qty }} {{ row.uom or row.stock_uom }}{{ serial_nos|join(',') }}\\n\\t\\t\\t {% if batches %}\\n {% for batch_no, qty in batches.items() %}\\n

{{batch_no}} : {{qty}} {{ row.uom or row.stock_uom }}

\\n {% endfor %}\\n {% endif %}\\n\\t\\t\\t
\\n\"}]", + "format_data": "[{\"fieldname\": \"print_heading_template\", \"fieldtype\": \"Custom HTML\", \"options\": \"
\\t\\t\\t\\t

Purchase Receipt

{{ doc.name }}\\t\\t\\t\\t

\"}, {\"fieldtype\": \"Section Break\", \"label\": \"\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"supplier_name\", \"print_hide\": 0, \"label\": \"Supplier Name\"}, {\"fieldname\": \"supplier_delivery_note\", \"print_hide\": 0, \"label\": \"Supplier Delivery Note\"}, {\"fieldname\": \"rack\", \"print_hide\": 0, \"label\": \"Rack\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"posting_date\", \"print_hide\": 0, \"label\": \"Date\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"apply_putaway_rule\", \"print_hide\": 0, \"label\": \"Apply Putaway Rule\"}, {\"fieldtype\": \"Section Break\", \"label\": \"Accounting Dimensions\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"region\", \"print_hide\": 0, \"label\": \"Region\"}, {\"fieldname\": \"function\", \"print_hide\": 0, \"label\": \"Function\"}, {\"fieldname\": \"depot\", \"print_hide\": 0, \"label\": \"Depot\"}, {\"fieldname\": \"cost_center\", \"print_hide\": 0, \"label\": \"Cost Center\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"location\", \"print_hide\": 0, \"label\": \"Location\"}, {\"fieldname\": \"country\", \"print_hide\": 0, \"label\": \"Country\"}, {\"fieldname\": \"project\", \"print_hide\": 0, \"label\": \"Project\"}, {\"fieldtype\": \"Section Break\", \"label\": \"Items\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"scan_barcode\", \"print_hide\": 0, \"label\": \"Scan Barcode\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"set_from_warehouse\", \"print_hide\": 0, \"label\": \"Set From Warehouse\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldtype\": \"Section Break\", \"label\": \"\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"_custom_html\", \"print_hide\": 0, \"label\": \"Custom HTML\", \"fieldtype\": \"HTML\", \"options\": \"\\n\\t\\n\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\n\\t\\t{%- for row in doc.items -%}\\n\\t\\t\\n\\t\\t {% set bundle_data = get_serial_or_batch_nos(row.serial_and_batch_bundle) %}\\n\\t\\t {% set serial_nos = [] %}\\n {% set batches = {} %}\\n\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\n\\t\\t{%- endfor -%}\\n\\t\\n
SrItem NameDescriptionQtyRateAmount
{{ row.idx }}\\n\\t\\t\\t\\t{{ row.item_name }}\\n\\t\\t\\t\\t{% if row.item_code != row.item_name -%}\\n\\t\\t\\t\\t
Item Code: {{ row.item_code}}\\n\\t\\t\\t\\t{%- endif %}\\n\\t\\t\\t
\\n\\t\\t\\t\\t
{{ row.description }}
{{ row.qty }} {{ row.uom or row.stock_uom }}{{\\n\\t\\t\\t\\trow.get_formatted(\\\"rate\\\", doc) }}{{\\n\\t\\t\\t\\trow.get_formatted(\\\"amount\\\", doc) }}
\\n\"}, {\"fieldtype\": \"Section Break\", \"label\": \"\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"total_qty\", \"print_hide\": 0, \"label\": \"Total Quantity\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"total\", \"print_hide\": 0, \"label\": \"Total\"}, {\"fieldtype\": \"Section Break\", \"label\": \"\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"taxes\", \"print_hide\": 0, \"label\": \"Purchase Taxes and Charges\", \"visible_columns\": [{\"fieldname\": \"category\", \"print_width\": \"\", \"print_hide\": 0}, {\"fieldname\": \"add_deduct_tax\", \"print_width\": \"\", \"print_hide\": 0}, {\"fieldname\": \"charge_type\", \"print_width\": \"\", \"print_hide\": 0}, {\"fieldname\": \"row_id\", \"print_width\": \"\", \"print_hide\": 0}, {\"fieldname\": \"included_in_print_rate\", \"print_width\": \"\", \"print_hide\": 0}, {\"fieldname\": \"included_in_paid_amount\", \"print_width\": \"\", \"print_hide\": 0}, {\"fieldname\": \"account_head\", \"print_width\": \"\", \"print_hide\": 0}, {\"fieldname\": \"description\", \"print_width\": \"300px\", \"print_hide\": 0}, {\"fieldname\": \"rate\", \"print_width\": \"\", \"print_hide\": 0}, {\"fieldname\": \"region\", \"print_width\": \"\", \"print_hide\": 0}, {\"fieldname\": \"function\", \"print_width\": \"\", \"print_hide\": 0}, {\"fieldname\": \"location\", \"print_width\": \"\", \"print_hide\": 0}, {\"fieldname\": \"cost_center\", \"print_width\": \"\", \"print_hide\": 0}, {\"fieldname\": \"depot\", \"print_width\": \"\", \"print_hide\": 0}, {\"fieldname\": \"country\", \"print_width\": \"\", \"print_hide\": 0}, {\"fieldname\": \"account_currency\", \"print_width\": \"\", \"print_hide\": 0}, {\"fieldname\": \"tax_amount\", \"print_width\": \"\", \"print_hide\": 0}, {\"fieldname\": \"total\", \"print_width\": \"\", \"print_hide\": 0}]}, {\"fieldtype\": \"Section Break\", \"label\": \"Totals\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"grand_total\", \"print_hide\": 0, \"label\": \"Grand Total\"}, {\"fieldname\": \"rounded_total\", \"print_hide\": 0, \"label\": \"Rounded Total\"}, {\"fieldname\": \"in_words\", \"print_hide\": 0, \"label\": \"In Words\"}, {\"fieldname\": \"disable_rounded_total\", \"print_hide\": 0, \"label\": \"Disable Rounded Total\"}, {\"fieldtype\": \"Section Break\", \"label\": \"Supplier Address\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"address_display\", \"print_hide\": 0, \"label\": \"Address\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"contact_display\", \"print_hide\": 0, \"label\": \"Contact\"}, {\"fieldname\": \"contact_mobile\", \"print_hide\": 0, \"label\": \"Mobile No\"}, {\"fieldtype\": \"Section Break\", \"label\": \"Company Billing Address\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"billing_address\", \"print_hide\": 0, \"label\": \"Billing Address\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"billing_address_display\", \"print_hide\": 0, \"label\": \"Billing Address\"}, {\"fieldname\": \"terms\", \"print_hide\": 0, \"label\": \"Terms and Conditions\"}, {\"fieldtype\": \"Section Break\", \"label\": \"\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"_custom_html\", \"print_hide\": 0, \"label\": \"Custom HTML\", \"fieldtype\": \"HTML\", \"options\": \"\\n\\t\\n\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\n\\t\\t{%- for row in doc.items -%}\\n\\t\\t\\n\\t\\t {% set bundle_data = frappe.get_all(\\\"Serial and Batch Entry\\\", \\n\\t\\t fields=[\\\"serial_no\\\", \\\"batch_no\\\", \\\"qty\\\"], \\n\\t\\t filters={\\\"parent\\\": row.serial_and_batch_bundle}) %}\\n\\t\\t {% set serial_nos = [] %}\\n {% set batches = {} %}\\n \\n {% if bundle_data %}\\n\\t\\t\\t {% for data in bundle_data %}\\n\\t\\t\\t {% if data.serial_no %}\\n\\t\\t\\t {{ serial_nos.append(data.serial_no) or \\\"\\\" }}\\n\\t\\t\\t {% endif %}\\n\\t\\t\\t \\n\\t\\t\\t {% if data.batch_no %}\\n\\t\\t\\t {{ batches.update({data.batch_no: data.qty}) or \\\"\\\" }}\\n\\t\\t\\t {% endif %}\\n\\t\\t\\t {% endfor %}\\n\\t\\t\\t{% endif %}\\n\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\n\\t\\t{%- endfor -%}\\n\\t\\n
SrItem NameQtySerial NosBatch Nos (Qty)
{{ row.idx }}\\n\\t\\t\\t\\t{{ row.item_name }}\\n\\t\\t\\t\\t{% if row.item_code != row.item_name -%}\\n\\t\\t\\t\\t
Item Code: {{ row.item_code}}\\n\\t\\t\\t\\t{%- endif %}\\n\\t\\t\\t
{{ row.qty }} {{ row.uom or row.stock_uom }}{{ serial_nos|join(',') }}\\n\\t\\t\\t {% if batches %}\\n {% for batch_no, qty in batches.items() %}\\n

{{batch_no}} : {{qty}} {{ row.uom or row.stock_uom }}

\\n {% endfor %}\\n {% endif %}\\n\\t\\t\\t
\\n\"}]", "idx": 0, "line_breaks": 0, "margin_bottom": 15.0, "margin_left": 15.0, "margin_right": 15.0, "margin_top": 15.0, - "modified": "2023-06-02 00:09:37.315002", + "modified": "2023-06-26 14:51:20.609682", "modified_by": "Administrator", "module": "Stock", "name": "Purchase Receipt Serial and Batch Bundle Print", diff --git a/erpnext/stock/serial_batch_bundle.py b/erpnext/stock/serial_batch_bundle.py index 2c18f99acd9..d6c840f1339 100644 --- a/erpnext/stock/serial_batch_bundle.py +++ b/erpnext/stock/serial_batch_bundle.py @@ -312,7 +312,35 @@ def get_serial_nos_from_bundle(serial_and_batch_bundle, serial_nos=None): def get_serial_or_batch_nos(bundle): - return frappe.get_all("Serial and Batch Entry", fields=["*"], filters={"parent": bundle}) + # For print format + + bundle_data = frappe.get_cached_value( + "Serial and Batch Bundle", bundle, ["has_serial_no", "has_batch_no"], as_dict=True + ) + + fields = [] + if bundle_data.has_serial_no: + fields.append("serial_no") + + if bundle_data.has_batch_no: + fields.extend(["batch_no", "qty"]) + + data = frappe.get_all("Serial and Batch Entry", fields=fields, filters={"parent": bundle}) + + if bundle_data.has_serial_no and not bundle_data.has_batch_no: + return ", ".join([d.serial_no for d in data]) + + elif bundle_data.has_batch_no: + html = "" + for d in data: + if d.serial_no: + html += f"" + else: + html += f"" + + html += "
{d.batch_no}{d.serial_no}{abs(d.qty)}
{d.batch_no}{abs(d.qty)}
" + + return html class SerialNoValuation(DeprecatedSerialNoValuation): diff --git a/erpnext/templates/print_formats/includes/serial_and_batch_bundle.html b/erpnext/templates/print_formats/includes/serial_and_batch_bundle.html new file mode 100644 index 00000000000..8e625863fb8 --- /dev/null +++ b/erpnext/templates/print_formats/includes/serial_and_batch_bundle.html @@ -0,0 +1,4 @@ +{% if doc.get("serial_and_batch_bundle") %} + {% set bundle_print = get_serial_or_batch_nos(doc.serial_and_batch_bundle) %} + {{bundle_print}} +{%- endif %} From af75f6cea7aac9f765201b8c97b687dec5dc67de Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 26 Jun 2023 15:31:20 +0530 Subject: [PATCH 50/63] refactor: simplify exchange logic on cr/dr note reconciliation --- .../payment_reconciliation.py | 38 ++++++++++--------- 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py index a709740d2fe..216d4eccac7 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py +++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py @@ -336,7 +336,6 @@ class PaymentReconciliation(Document): entry_list = [] dr_or_cr_notes = [] - difference_entries = [] for row in self.get("allocation"): reconciled_entry = [] if row.invoice_number and row.allocated_amount: @@ -348,16 +347,17 @@ class PaymentReconciliation(Document): payment_details = self.get_payment_details(row, dr_or_cr) reconciled_entry.append(payment_details) - if payment_details.difference_amount: - difference_entries.append( - self.make_difference_entry(payment_details, do_not_save_and_submit=bool(dr_or_cr_notes)) - ) + if payment_details.difference_amount and row.reference_type not in [ + "Sales Invoice", + "Purchase Invoice", + ]: + self.make_difference_entry(payment_details) if entry_list: reconcile_against_document(entry_list, skip_ref_details_update_for_pe) if dr_or_cr_notes: - reconcile_dr_cr_note(dr_or_cr_notes, difference_entries, self.company) + reconcile_dr_cr_note(dr_or_cr_notes, self.company) @frappe.whitelist() def reconcile(self): @@ -385,7 +385,7 @@ class PaymentReconciliation(Document): self.get_unreconciled_entries() - def make_difference_entry(self, row, do_not_save_and_submit=False): + def make_difference_entry(self, row): journal_entry = frappe.new_doc("Journal Entry") journal_entry.voucher_type = "Exchange Gain Or Loss" journal_entry.company = self.company @@ -433,9 +433,8 @@ class PaymentReconciliation(Document): journal_entry.append("accounts", journal_account) - if not do_not_save_and_submit: - journal_entry.save() - journal_entry.submit() + journal_entry.save() + journal_entry.submit() return journal_entry @@ -603,13 +602,16 @@ class PaymentReconciliation(Document): return condition -def reconcile_dr_cr_note(dr_cr_notes, difference_entries, company): - def find_difference_entry(voucher_type, voucher_no): - for jv in difference_entries: - accounts = iter(jv.accounts) - for account in accounts: - if account.reference_type == voucher_type and account.reference_name == voucher_no: - return next(accounts) +def reconcile_dr_cr_note(dr_cr_notes, company): + def get_difference_row(inv): + if inv.difference_amount != 0 and inv.difference_account: + difference_row = { + "account": inv.difference_account, + inv.dr_or_cr: abs(inv.difference_amount) if inv.difference_amount > 0 else 0, + reconcile_dr_or_cr: abs(inv.difference_amount) if inv.difference_amount < 0 else 0, + "cost_center": erpnext.get_default_cost_center(company), + } + return difference_row for inv in dr_cr_notes: voucher_type = "Credit Note" if inv.voucher_type == "Sales Invoice" else "Debit Note" @@ -656,7 +658,7 @@ def reconcile_dr_cr_note(dr_cr_notes, difference_entries, company): } ) - if difference_entry := find_difference_entry(inv.against_voucher_type, inv.against_voucher): + if difference_entry := get_difference_row(inv): jv.append("accounts", difference_entry) jv.flags.ignore_mandatory = True From 1f9ef6c48faf330c47daafae485ac833489db08d Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 26 Jun 2023 22:01:12 +0530 Subject: [PATCH 51/63] fix: TDS amount calculation post LDC breach --- .../tax_withholding_category/tax_withholding_category.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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 c2b7ff0f352..58792d1d8ad 100644 --- a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py +++ b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py @@ -585,7 +585,9 @@ def get_tds_amount_from_ldc(ldc, parties, tax_details, posting_date, net_total): "supplier": ("in", parties), "apply_tds": 1, "docstatus": 1, + "tax_withholding_category": ldc.tax_withholding_category, "posting_date": ("between", (ldc.valid_from, ldc.valid_upto)), + "company": ldc.company, }, "sum(tax_withholding_net_total)", ) @@ -615,7 +617,7 @@ def is_valid_certificate( ): valid = False - available_amount = flt(certificate_limit) - flt(deducted_amount) - flt(current_amount) + available_amount = flt(certificate_limit) - flt(deducted_amount) if (getdate(valid_from) <= getdate(posting_date) <= getdate(valid_upto)) and available_amount > 0: valid = True From 742df8a25e6249e67eb755e449bdbe969e052b7e Mon Sep 17 00:00:00 2001 From: David Arnold Date: Tue, 27 Jun 2023 01:06:44 -0500 Subject: [PATCH 52/63] fix: delivery trip driver is only required on submit (#35876) This allows drafting trips and stops without yet deciding on the assignable driver which, in real life, may well be decided on after preparing and planning the trip. --- erpnext/stock/doctype/delivery_trip/delivery_trip.json | 7 ++++--- erpnext/stock/doctype/delivery_trip/delivery_trip.py | 3 +++ 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/erpnext/stock/doctype/delivery_trip/delivery_trip.json b/erpnext/stock/doctype/delivery_trip/delivery_trip.json index 11b71c20761..9d8fe46e8ca 100644 --- a/erpnext/stock/doctype/delivery_trip/delivery_trip.json +++ b/erpnext/stock/doctype/delivery_trip/delivery_trip.json @@ -66,8 +66,7 @@ "fieldname": "driver", "fieldtype": "Link", "label": "Driver", - "options": "Driver", - "reqd": 1 + "options": "Driver" }, { "fetch_from": "driver.full_name", @@ -189,10 +188,11 @@ ], "is_submittable": 1, "links": [], - "modified": "2021-04-30 21:21:36.610142", + "modified": "2023-06-27 11:22:27.927637", "modified_by": "Administrator", "module": "Stock", "name": "Delivery Trip", + "naming_rule": "By \"Naming Series\" field", "owner": "Administrator", "permissions": [ { @@ -228,5 +228,6 @@ ], "sort_field": "modified", "sort_order": "DESC", + "states": [], "title_field": "driver_name" } \ No newline at end of file diff --git a/erpnext/stock/doctype/delivery_trip/delivery_trip.py b/erpnext/stock/doctype/delivery_trip/delivery_trip.py index 1febbded52b..af2f4113e1e 100644 --- a/erpnext/stock/doctype/delivery_trip/delivery_trip.py +++ b/erpnext/stock/doctype/delivery_trip/delivery_trip.py @@ -24,6 +24,9 @@ class DeliveryTrip(Document): ) def validate(self): + if self._action == "submit" and not self.driver: + frappe.throw(_("A driver must be set to submit.")) + self.validate_stop_addresses() def on_submit(self): From 3a00bf83d68be5f083f9290f9cfcdf756e67570a Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Tue, 27 Jun 2023 11:48:29 +0530 Subject: [PATCH 53/63] fix(ux): PO Get Items From Open Material Requests --- erpnext/buying/doctype/purchase_order/purchase_order.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.js b/erpnext/buying/doctype/purchase_order/purchase_order.js index c6c9f1f98a3..8fa8f305549 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.js +++ b/erpnext/buying/doctype/purchase_order/purchase_order.js @@ -286,7 +286,7 @@ erpnext.buying.PurchaseOrderController = class PurchaseOrderController extends e source_name: this.frm.doc.supplier, target: this.frm, setters: { - company: me.frm.doc.company + company: this.frm.doc.company }, get_query_filters: { docstatus: ["!=", 2], From af418d2342281c5a1fe6291971ee94e2eca42b3b Mon Sep 17 00:00:00 2001 From: HLD Date: Tue, 27 Jun 2023 15:47:52 +0800 Subject: [PATCH 54/63] fix: filter parent warehouses not showing (#35897) --- erpnext/stock/doctype/warehouse/warehouse.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/warehouse/warehouse.js b/erpnext/stock/doctype/warehouse/warehouse.js index 87a23efc590..746a1cbaf17 100644 --- a/erpnext/stock/doctype/warehouse/warehouse.js +++ b/erpnext/stock/doctype/warehouse/warehouse.js @@ -13,7 +13,7 @@ frappe.ui.form.on("Warehouse", { }; }); - frm.set_query("parent_warehouse", function () { + frm.set_query("parent_warehouse", function (doc) { return { filters: { is_group: 1, From 5d726ef0377dc1fadc9857cb4b39828dc731a709 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Tue, 27 Jun 2023 16:49:28 +0530 Subject: [PATCH 55/63] feat: add voucher-wise balance report logic --- .../report/voucher_wise_balance/__init__.py | 0 .../voucher_wise_balance.js | 28 ++++++++ .../voucher_wise_balance.json | 33 ++++++++++ .../voucher_wise_balance.py | 66 +++++++++++++++++++ 4 files changed, 127 insertions(+) create mode 100644 erpnext/accounts/report/voucher_wise_balance/__init__.py create mode 100644 erpnext/accounts/report/voucher_wise_balance/voucher_wise_balance.js create mode 100644 erpnext/accounts/report/voucher_wise_balance/voucher_wise_balance.json create mode 100644 erpnext/accounts/report/voucher_wise_balance/voucher_wise_balance.py diff --git a/erpnext/accounts/report/voucher_wise_balance/__init__.py b/erpnext/accounts/report/voucher_wise_balance/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/accounts/report/voucher_wise_balance/voucher_wise_balance.js b/erpnext/accounts/report/voucher_wise_balance/voucher_wise_balance.js new file mode 100644 index 00000000000..0c148f85fb8 --- /dev/null +++ b/erpnext/accounts/report/voucher_wise_balance/voucher_wise_balance.js @@ -0,0 +1,28 @@ +// Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt +/* eslint-disable */ + +frappe.query_reports["Voucher-wise Balance"] = { + "filters": [ + { + "fieldname": "company", + "label": __("Company"), + "fieldtype": "Link", + "options": "Company" + }, + { + "fieldname":"from_date", + "label": __("From Date"), + "fieldtype": "Date", + "default": frappe.datetime.add_months(frappe.datetime.get_today(), -1), + "width": "60px" + }, + { + "fieldname":"to_date", + "label": __("To Date"), + "fieldtype": "Date", + "default": frappe.datetime.get_today(), + "width": "60px" + }, + ] +}; diff --git a/erpnext/accounts/report/voucher_wise_balance/voucher_wise_balance.json b/erpnext/accounts/report/voucher_wise_balance/voucher_wise_balance.json new file mode 100644 index 00000000000..434e5a3b257 --- /dev/null +++ b/erpnext/accounts/report/voucher_wise_balance/voucher_wise_balance.json @@ -0,0 +1,33 @@ +{ + "add_total_row": 0, + "columns": [], + "creation": "2023-06-27 16:40:15.109554", + "disabled": 0, + "docstatus": 0, + "doctype": "Report", + "filters": [], + "idx": 0, + "is_standard": "Yes", + "json": "{}", + "letter_head": "LetterHead", + "modified": "2023-06-27 16:40:32.493725", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Voucher-wise Balance", + "owner": "Administrator", + "prepared_report": 0, + "ref_doctype": "GL Entry", + "report_name": "Voucher-wise Balance", + "report_type": "Script Report", + "roles": [ + { + "role": "Accounts User" + }, + { + "role": "Accounts Manager" + }, + { + "role": "Auditor" + } + ] +} \ No newline at end of file diff --git a/erpnext/accounts/report/voucher_wise_balance/voucher_wise_balance.py b/erpnext/accounts/report/voucher_wise_balance/voucher_wise_balance.py new file mode 100644 index 00000000000..cbe25495854 --- /dev/null +++ b/erpnext/accounts/report/voucher_wise_balance/voucher_wise_balance.py @@ -0,0 +1,66 @@ +# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +import frappe +from frappe import _ +from frappe.query_builder.functions import Sum + + +def execute(filters=None): + columns = get_columns() + data = get_data(filters) + return columns, data + + +def get_columns(): + return [ + {"label": _("Voucher Type"), "fieldname": "voucher_type", "width": 300}, + { + "label": _("Voucher No"), + "fieldname": "voucher_no", + "fieldtype": "Dynamic Link", + "options": "voucher_type", + "width": 300, + }, + { + "fieldname": "debit", + "label": _("Debit"), + "fieldtype": "Currency", + "options": "currency", + "width": 300, + }, + { + "fieldname": "credit", + "label": _("Credit"), + "fieldtype": "Currency", + "options": "currency", + "width": 300, + }, + ] + + +def get_data(filters): + gle = frappe.qb.DocType("GL Entry") + query = ( + frappe.qb.from_(gle) + .select( + gle.voucher_type, gle.voucher_no, Sum(gle.debit).as_("debit"), Sum(gle.credit).as_("credit") + ) + .groupby(gle.voucher_no) + ) + query = apply_filters(query, filters, gle) + gl_entries = query.run(as_dict=True, debug=True) + unmatched = [entry for entry in gl_entries if entry.debit != entry.credit] + return unmatched + + +def apply_filters(query, filters, gle): + if filters.get("company"): + query = query.where(gle.company == filters.company) + if filters.get("voucher_type"): + query = query.where(gle.voucher_type == filters.voucher_type) + if filters.get("from_date"): + query = query.where(gle.posting_date >= filters.from_date) + if filters.get("to_date"): + query = query.where(gle.posting_date <= filters.to_date) + return query From 6b9f9f9b0e0a49ee15820c77ffe4de99605786ff Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Tue, 27 Jun 2023 21:59:35 +0530 Subject: [PATCH 56/63] fix: remove debug flag from sql --- .../report/voucher_wise_balance/voucher_wise_balance.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/report/voucher_wise_balance/voucher_wise_balance.py b/erpnext/accounts/report/voucher_wise_balance/voucher_wise_balance.py index cbe25495854..5ab3611b9af 100644 --- a/erpnext/accounts/report/voucher_wise_balance/voucher_wise_balance.py +++ b/erpnext/accounts/report/voucher_wise_balance/voucher_wise_balance.py @@ -49,7 +49,7 @@ def get_data(filters): .groupby(gle.voucher_no) ) query = apply_filters(query, filters, gle) - gl_entries = query.run(as_dict=True, debug=True) + gl_entries = query.run(as_dict=True) unmatched = [entry for entry in gl_entries if entry.debit != entry.credit] return unmatched From e832455790754385e9f64134889a5335883bc142 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 28 Jun 2023 10:25:03 +0530 Subject: [PATCH 57/63] perf: improve item wise register reports (backport #35908) (#35911) perf: improve item wise register reports (#35908) (cherry picked from commit 33ee01174bb909ee46bb3d714d6f94fa0aefe48f) Co-authored-by: Anand Baburajan --- .../item_wise_purchase_register.py | 31 ++++++++++++------- .../item_wise_sales_register.py | 30 ++++++++++++------ 2 files changed, 39 insertions(+), 22 deletions(-) diff --git a/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py b/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py index 924c14bdb94..6fdb2f337c0 100644 --- a/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py +++ b/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py @@ -15,7 +15,6 @@ from erpnext.accounts.report.item_wise_sales_register.item_wise_sales_register i get_group_by_conditions, get_tax_accounts, ) -from erpnext.selling.report.item_wise_sales_history.item_wise_sales_history import get_item_details def execute(filters=None): @@ -40,6 +39,16 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum tax_doctype="Purchase Taxes and Charges", ) + scrubbed_tax_fields = {} + + for tax in tax_columns: + scrubbed_tax_fields.update( + { + tax + " Rate": frappe.scrub(tax + " Rate"), + tax + " Amount": frappe.scrub(tax + " Amount"), + } + ) + po_pr_map = get_purchase_receipts_against_purchase_order(item_list) data = [] @@ -50,11 +59,7 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum if filters.get("group_by"): grand_total = get_grand_total(filters, "Purchase Invoice") - item_details = get_item_details() - for d in item_list: - item_record = item_details.get(d.item_code) - purchase_receipt = None if d.purchase_receipt: purchase_receipt = d.purchase_receipt @@ -67,8 +72,8 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum row = { "item_code": d.item_code, - "item_name": item_record.item_name if item_record else d.item_name, - "item_group": item_record.item_group if item_record else d.item_group, + "item_name": d.pi_item_name if d.pi_item_name else d.i_item_name, + "item_group": d.pi_item_group if d.pi_item_group else d.i_item_group, "description": d.description, "invoice": d.parent, "posting_date": d.posting_date, @@ -101,8 +106,8 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum item_tax = itemised_tax.get(d.name, {}).get(tax, {}) row.update( { - frappe.scrub(tax + " Rate"): item_tax.get("tax_rate", 0), - frappe.scrub(tax + " Amount"): item_tax.get("tax_amount", 0), + scrubbed_tax_fields[tax + " Rate"]: item_tax.get("tax_rate", 0), + scrubbed_tax_fields[tax + " Amount"]: item_tax.get("tax_amount", 0), } ) total_tax += flt(item_tax.get("tax_amount")) @@ -325,15 +330,17 @@ def get_items(filters, additional_query_columns): `tabPurchase Invoice`.supplier, `tabPurchase Invoice`.remarks, `tabPurchase Invoice`.base_net_total, `tabPurchase Invoice`.unrealized_profit_loss_account, `tabPurchase Invoice Item`.`item_code`, `tabPurchase Invoice Item`.description, - `tabPurchase Invoice Item`.`item_name`, `tabPurchase Invoice Item`.`item_group`, + `tabPurchase Invoice Item`.`item_name` as pi_item_name, `tabPurchase Invoice Item`.`item_group` as pi_item_group, + `tabItem`.`item_name` as i_item_name, `tabItem`.`item_group` as i_item_group, `tabPurchase Invoice Item`.`project`, `tabPurchase Invoice Item`.`purchase_order`, `tabPurchase Invoice Item`.`purchase_receipt`, `tabPurchase Invoice Item`.`po_detail`, `tabPurchase Invoice Item`.`expense_account`, `tabPurchase Invoice Item`.`stock_qty`, `tabPurchase Invoice Item`.`stock_uom`, `tabPurchase Invoice Item`.`base_net_amount`, `tabPurchase Invoice`.`supplier_name`, `tabPurchase Invoice`.`mode_of_payment` {0} - from `tabPurchase Invoice`, `tabPurchase Invoice Item` + from `tabPurchase Invoice`, `tabPurchase Invoice Item`, `tabItem` where `tabPurchase Invoice`.name = `tabPurchase Invoice Item`.`parent` and - `tabPurchase Invoice`.docstatus = 1 %s + `tabItem`.name = `tabPurchase Invoice Item`.`item_code` and + `tabPurchase Invoice`.docstatus = 1 %s """.format( additional_query_columns ) diff --git a/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py b/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py index 0ebe13f4f32..bd7d02e0430 100644 --- a/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py +++ b/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py @@ -11,7 +11,6 @@ from frappe.utils.xlsxutils import handle_html from erpnext.accounts.report.sales_register.sales_register import get_mode_of_payments from erpnext.selling.report.item_wise_sales_history.item_wise_sales_history import ( get_customer_details, - get_item_details, ) @@ -35,6 +34,16 @@ def _execute( if item_list: itemised_tax, tax_columns = get_tax_accounts(item_list, columns, company_currency) + scrubbed_tax_fields = {} + + for tax in tax_columns: + scrubbed_tax_fields.update( + { + tax + " Rate": frappe.scrub(tax + " Rate"), + tax + " Amount": frappe.scrub(tax + " Amount"), + } + ) + mode_of_payments = get_mode_of_payments(set(d.parent for d in item_list)) so_dn_map = get_delivery_notes_against_sales_order(item_list) @@ -47,11 +56,9 @@ def _execute( grand_total = get_grand_total(filters, "Sales Invoice") customer_details = get_customer_details() - item_details = get_item_details() for d in item_list: customer_record = customer_details.get(d.customer) - item_record = item_details.get(d.item_code) delivery_note = None if d.delivery_note: @@ -64,8 +71,8 @@ def _execute( row = { "item_code": d.item_code, - "item_name": item_record.item_name if item_record else d.item_name, - "item_group": item_record.item_group if item_record else d.item_group, + "item_name": d.si_item_name if d.si_item_name else d.i_item_name, + "item_group": d.si_item_group if d.si_item_group else d.i_item_group, "description": d.description, "invoice": d.parent, "posting_date": d.posting_date, @@ -107,8 +114,8 @@ def _execute( item_tax = itemised_tax.get(d.name, {}).get(tax, {}) row.update( { - frappe.scrub(tax + " Rate"): item_tax.get("tax_rate", 0), - frappe.scrub(tax + " Amount"): item_tax.get("tax_amount", 0), + scrubbed_tax_fields[tax + " Rate"]: item_tax.get("tax_rate", 0), + scrubbed_tax_fields[tax + " Amount"]: item_tax.get("tax_amount", 0), } ) if item_tax.get("is_other_charges"): @@ -404,15 +411,18 @@ def get_items(filters, additional_query_columns, additional_conditions=None): `tabSales Invoice Item`.project, `tabSales Invoice Item`.item_code, `tabSales Invoice Item`.description, `tabSales Invoice Item`.`item_name`, `tabSales Invoice Item`.`item_group`, + `tabSales Invoice Item`.`item_name` as si_item_name, `tabSales Invoice Item`.`item_group` as si_item_group, + `tabItem`.`item_name` as i_item_name, `tabItem`.`item_group` as i_item_group, `tabSales Invoice Item`.sales_order, `tabSales Invoice Item`.delivery_note, `tabSales Invoice Item`.income_account, `tabSales Invoice Item`.cost_center, `tabSales Invoice Item`.stock_qty, `tabSales Invoice Item`.stock_uom, `tabSales Invoice Item`.base_net_rate, `tabSales Invoice Item`.base_net_amount, `tabSales Invoice`.customer_name, `tabSales Invoice`.customer_group, `tabSales Invoice Item`.so_detail, `tabSales Invoice`.update_stock, `tabSales Invoice Item`.uom, `tabSales Invoice Item`.qty {0} - from `tabSales Invoice`, `tabSales Invoice Item` - where `tabSales Invoice`.name = `tabSales Invoice Item`.parent - and `tabSales Invoice`.docstatus = 1 {1} + from `tabSales Invoice`, `tabSales Invoice Item`, `tabItem` + where `tabSales Invoice`.name = `tabSales Invoice Item`.parent and + `tabItem`.name = `tabSales Invoice Item`.`item_code` and + `tabSales Invoice`.docstatus = 1 {1} """.format( additional_query_columns or "", conditions ), From 5113a417a1dc6e4efb75ba035c9b885d0d98d98d Mon Sep 17 00:00:00 2001 From: Gursheen Kaur Anand <40693548+GursheenK@users.noreply.github.com> Date: Wed, 28 Jun 2023 11:22:40 +0530 Subject: [PATCH 58/63] fix!: UX of supplier linking with supplier users on portal pages (#35836) * fix: create and add Portal Users child table in Supplier/Customer Issue #35772 * fix: modify the original permission check hook * fix: auto-add role for portal users * fix: added patch for auto-populating portal users * fix: modify patch to fetch users correctly * fix: remove unnecessary code for updating naming_series * fix(UX): show portal user in list view Also split columns to reduce whitespace. * refactor: simpler role checking * fix: consider parenttype while fetching portal user * refactor: simpler code, rename variable * test: supplier portal user can access their docs * refactor: only add role if not added * refactor: rename and move patch to supplier * refactor: dont add role if no perm or existing doc * fix: add role before save * refactor: run query directly * refactor: split patch and apply roles - if role isn't present dont add portal user - ignore failure as it's not critical * test: fix permission creation for webform test --------- Co-authored-by: Ankush Menat --- .../doctype/supplier/patches/__init__.py | 0 .../patches/migrate_supplier_portal_users.py | 56 +++++++++++++++++++ erpnext/buying/doctype/supplier/supplier.js | 8 +++ erpnext/buying/doctype/supplier/supplier.json | 23 +++++++- erpnext/buying/doctype/supplier/supplier.py | 31 +++++++++- .../buying/doctype/supplier/test_supplier.py | 26 +++++++++ .../controllers/website_list_for_contact.py | 54 ++++++++++++------ erpnext/patches.txt | 3 +- erpnext/selling/doctype/customer/customer.js | 8 +++ .../selling/doctype/customer/customer.json | 18 +++++- erpnext/selling/doctype/customer/customer.py | 6 ++ erpnext/tests/test_webform.py | 30 +++------- .../utilities/doctype/portal_user/__init__.py | 0 .../doctype/portal_user/portal_user.json | 34 +++++++++++ .../doctype/portal_user/portal_user.py | 9 +++ 15 files changed, 259 insertions(+), 47 deletions(-) create mode 100644 erpnext/buying/doctype/supplier/patches/__init__.py create mode 100644 erpnext/buying/doctype/supplier/patches/migrate_supplier_portal_users.py create mode 100644 erpnext/utilities/doctype/portal_user/__init__.py create mode 100644 erpnext/utilities/doctype/portal_user/portal_user.json create mode 100644 erpnext/utilities/doctype/portal_user/portal_user.py diff --git a/erpnext/buying/doctype/supplier/patches/__init__.py b/erpnext/buying/doctype/supplier/patches/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/buying/doctype/supplier/patches/migrate_supplier_portal_users.py b/erpnext/buying/doctype/supplier/patches/migrate_supplier_portal_users.py new file mode 100644 index 00000000000..5834952d5b6 --- /dev/null +++ b/erpnext/buying/doctype/supplier/patches/migrate_supplier_portal_users.py @@ -0,0 +1,56 @@ +import os + +import frappe + +in_ci = os.environ.get("CI") + + +def execute(): + try: + contacts = get_portal_user_contacts() + add_portal_users(contacts) + except Exception: + frappe.db.rollback() + frappe.log_error("Failed to migrate portal users") + + if in_ci: # TODO: better way to handle this. + raise + + +def get_portal_user_contacts(): + contact = frappe.qb.DocType("Contact") + dynamic_link = frappe.qb.DocType("Dynamic Link") + + return ( + frappe.qb.from_(contact) + .inner_join(dynamic_link) + .on(contact.name == dynamic_link.parent) + .select( + (dynamic_link.link_doctype).as_("doctype"), + (dynamic_link.link_name).as_("parent"), + (contact.email_id).as_("portal_user"), + ) + .where( + (dynamic_link.parenttype == "Contact") + & (dynamic_link.link_doctype.isin(["Supplier", "Customer"])) + ) + ).run(as_dict=True) + + +def add_portal_users(contacts): + for contact in contacts: + user = frappe.db.get_value("User", {"email": contact.portal_user}, "name") + if not user: + continue + + roles = frappe.get_roles(user) + required_role = contact.doctype + if required_role not in roles: + continue + + portal_user_doc = frappe.new_doc("Portal User") + portal_user_doc.parenttype = contact.doctype + portal_user_doc.parentfield = "portal_users" + portal_user_doc.parent = contact.parent + portal_user_doc.user = user + portal_user_doc.insert() diff --git a/erpnext/buying/doctype/supplier/supplier.js b/erpnext/buying/doctype/supplier/supplier.js index 1ae6f036474..a536578b2eb 100644 --- a/erpnext/buying/doctype/supplier/supplier.js +++ b/erpnext/buying/doctype/supplier/supplier.js @@ -42,6 +42,14 @@ frappe.ui.form.on("Supplier", { } }; }); + + frm.set_query("user", "portal_users", function(doc) { + return { + filters: { + "ignore_user_type": true, + } + }; + }); }, refresh: function (frm) { diff --git a/erpnext/buying/doctype/supplier/supplier.json b/erpnext/buying/doctype/supplier/supplier.json index f0097898f65..b3b6185e355 100644 --- a/erpnext/buying/doctype/supplier/supplier.json +++ b/erpnext/buying/doctype/supplier/supplier.json @@ -68,7 +68,10 @@ "on_hold", "hold_type", "column_break_59", - "release_date" + "release_date", + "portal_users_tab", + "portal_users", + "column_break_1mqv" ], "fields": [ { @@ -445,6 +448,21 @@ { "fieldname": "column_break_59", "fieldtype": "Column Break" + }, + { + "fieldname": "portal_users_tab", + "fieldtype": "Tab Break", + "label": "Portal Users" + }, + { + "fieldname": "portal_users", + "fieldtype": "Table", + "label": "Supplier Portal Users", + "options": "Portal User" + }, + { + "fieldname": "column_break_1mqv", + "fieldtype": "Column Break" } ], "icon": "fa fa-user", @@ -457,7 +475,7 @@ "link_fieldname": "party" } ], - "modified": "2023-05-09 15:34:13.408932", + "modified": "2023-06-26 14:20:00.961554", "modified_by": "Administrator", "module": "Buying", "name": "Supplier", @@ -489,7 +507,6 @@ "read": 1, "report": 1, "role": "Purchase Master Manager", - "set_user_permissions": 1, "share": 1, "write": 1 }, diff --git a/erpnext/buying/doctype/supplier/supplier.py b/erpnext/buying/doctype/supplier/supplier.py index 01b5c8f064f..31bf439dbb4 100644 --- a/erpnext/buying/doctype/supplier/supplier.py +++ b/erpnext/buying/doctype/supplier/supplier.py @@ -16,6 +16,7 @@ from erpnext.accounts.party import ( # noqa get_timeline_data, validate_party_accounts, ) +from erpnext.controllers.website_list_for_contact import add_role_for_portal_user from erpnext.utilities.transaction_base import TransactionBase @@ -46,12 +47,35 @@ class Supplier(TransactionBase): self.name = set_name_from_naming_options(frappe.get_meta(self.doctype).autoname, self) def on_update(self): - if not self.naming_series: - self.naming_series = "" - self.create_primary_contact() self.create_primary_address() + def add_role_for_user(self): + for portal_user in self.portal_users: + add_role_for_portal_user(portal_user, "Supplier") + + def _add_supplier_role(self, portal_user): + if not portal_user.is_new(): + return + + user_doc = frappe.get_doc("User", portal_user.user) + roles = {r.role for r in user_doc.roles} + + if "Supplier" in roles: + return + + if "System Manager" not in frappe.get_roles(): + frappe.msgprint( + _("Please add 'Supplier' role to user {0}.").format(portal_user.user), + alert=True, + ) + return + + user_doc.add_roles("Supplier") + frappe.msgprint( + _("Added Supplier Role to User {0}.").format(frappe.bold(user_doc.name)), alert=True + ) + def validate(self): self.flags.is_new_doc = self.is_new() @@ -62,6 +86,7 @@ class Supplier(TransactionBase): validate_party_accounts(self) self.validate_internal_supplier() + self.add_role_for_user() @frappe.whitelist() def get_supplier_group_details(self): diff --git a/erpnext/buying/doctype/supplier/test_supplier.py b/erpnext/buying/doctype/supplier/test_supplier.py index 7a205ac20ce..7be1d834a65 100644 --- a/erpnext/buying/doctype/supplier/test_supplier.py +++ b/erpnext/buying/doctype/supplier/test_supplier.py @@ -7,6 +7,7 @@ from frappe.custom.doctype.property_setter.property_setter import make_property_ from frappe.test_runner import make_test_records from erpnext.accounts.party import get_due_date +from erpnext.controllers.website_list_for_contact import get_customers_suppliers from erpnext.exceptions import PartyDisabled test_dependencies = ["Payment Term", "Payment Terms Template"] @@ -195,6 +196,9 @@ class TestSupplier(FrappeTestCase): def create_supplier(**args): args = frappe._dict(args) + if not args.supplier_name: + args.supplier_name = frappe.generate_hash() + if frappe.db.exists("Supplier", args.supplier_name): return frappe.get_doc("Supplier", args.supplier_name) @@ -209,3 +213,25 @@ def create_supplier(**args): ).insert() return doc + + +class TestSupplierPortal(FrappeTestCase): + def test_portal_user_can_access_supplier_data(self): + + supplier = create_supplier() + + user = frappe.generate_hash() + "@example.com" + frappe.new_doc( + "User", + first_name="Supplier Portal User", + email=user, + send_welcome_email=False, + ).insert() + + supplier.append("portal_users", {"user": user}) + supplier.save() + + frappe.set_user(user) + _, suppliers = get_customers_suppliers("Purchase Order", user) + + self.assertIn(supplier.name, suppliers) diff --git a/erpnext/controllers/website_list_for_contact.py b/erpnext/controllers/website_list_for_contact.py index 7c3c38706dc..642722ae6bf 100644 --- a/erpnext/controllers/website_list_for_contact.py +++ b/erpnext/controllers/website_list_for_contact.py @@ -232,22 +232,8 @@ def get_customers_suppliers(doctype, user): has_supplier_field = meta.has_field("supplier") if has_common(["Supplier", "Customer"], frappe.get_roles(user)): - contacts = frappe.db.sql( - """ - select - `tabContact`.email_id, - `tabDynamic Link`.link_doctype, - `tabDynamic Link`.link_name - from - `tabContact`, `tabDynamic Link` - where - `tabContact`.name=`tabDynamic Link`.parent and `tabContact`.email_id =%s - """, - user, - as_dict=1, - ) - customers = [c.link_name for c in contacts if c.link_doctype == "Customer"] - suppliers = [c.link_name for c in contacts if c.link_doctype == "Supplier"] + suppliers = get_parents_for_user("Supplier") + customers = get_parents_for_user("Customer") elif frappe.has_permission(doctype, "read", user=user): customer_list = frappe.get_list("Customer") customers = suppliers = [customer.name for customer in customer_list] @@ -255,6 +241,17 @@ def get_customers_suppliers(doctype, user): return customers if has_customer_field else None, suppliers if has_supplier_field else None +def get_parents_for_user(parenttype: str) -> list[str]: + portal_user = frappe.qb.DocType("Portal User") + + return ( + frappe.qb.from_(portal_user) + .select(portal_user.parent) + .where(portal_user.user == frappe.session.user) + .where(portal_user.parenttype == parenttype) + ).run(pluck="name") + + def has_website_permission(doc, ptype, user, verbose=False): doctype = doc.doctype customers, suppliers = get_customers_suppliers(doctype, user) @@ -282,3 +279,28 @@ def get_customer_field_name(doctype): return "party_name" else: return "customer" + + +def add_role_for_portal_user(portal_user, role): + """When a new portal user is added, give appropriate roles to user if + posssible, else warn user to add roles.""" + if not portal_user.is_new(): + return + + user_doc = frappe.get_doc("User", portal_user.user) + roles = {r.role for r in user_doc.roles} + + if role in roles: + return + + if "System Manager" not in frappe.get_roles(): + frappe.msgprint( + _("Please add {1} role to user {0}.").format(portal_user.user, role), + alert=True, + ) + return + + user_doc.add_roles(role) + frappe.msgprint( + _("Added {1} Role to User {0}.").format(frappe.bold(user_doc.name), role), alert=True + ) diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 8c0fa6bbcda..fe6346e81e8 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -339,4 +339,5 @@ execute:frappe.delete_doc('DocType', 'Cash Flow Mapper', ignore_missing=True) execute:frappe.delete_doc('DocType', 'Cash Flow Mapping Template', ignore_missing=True) execute:frappe.delete_doc('DocType', 'Cash Flow Mapping Accounts', ignore_missing=True) erpnext.patches.v14_0.cleanup_workspaces -erpnext.patches.v14_0.set_report_in_process_SOA +erpnext.patches.v14_0.set_report_in_process_SOA +erpnext.buying.doctype.supplier.patches.migrate_supplier_portal_users diff --git a/erpnext/selling/doctype/customer/customer.js b/erpnext/selling/doctype/customer/customer.js index b53f339229b..3a446e171a5 100644 --- a/erpnext/selling/doctype/customer/customer.js +++ b/erpnext/selling/doctype/customer/customer.js @@ -63,6 +63,14 @@ frappe.ui.form.on("Customer", { } } }); + + frm.set_query("user", "portal_users", function() { + return { + filters: { + "ignore_user_type": true, + } + }; + }); }, customer_primary_address: function(frm){ if(frm.doc.customer_primary_address){ diff --git a/erpnext/selling/doctype/customer/customer.json b/erpnext/selling/doctype/customer/customer.json index 72a1594b2d2..edfe0050dee 100644 --- a/erpnext/selling/doctype/customer/customer.json +++ b/erpnext/selling/doctype/customer/customer.json @@ -81,7 +81,9 @@ "dn_required", "column_break_53", "is_frozen", - "disabled" + "disabled", + "portal_users_tab", + "portal_users" ], "fields": [ { @@ -555,6 +557,17 @@ { "fieldname": "column_break_54", "fieldtype": "Column Break" + }, + { + "fieldname": "portal_users_tab", + "fieldtype": "Tab Break", + "label": "Portal Users" + }, + { + "fieldname": "portal_users", + "fieldtype": "Table", + "label": "Customer Portal Users", + "options": "Portal User" } ], "icon": "fa fa-user", @@ -568,7 +581,7 @@ "link_fieldname": "party" } ], - "modified": "2023-05-09 15:38:40.255193", + "modified": "2023-06-22 13:21:10.678382", "modified_by": "Administrator", "module": "Selling", "name": "Customer", @@ -607,7 +620,6 @@ "read": 1, "report": 1, "role": "Sales Master Manager", - "set_user_permissions": 1, "share": 1, "write": 1 }, diff --git a/erpnext/selling/doctype/customer/customer.py b/erpnext/selling/doctype/customer/customer.py index 6367e3cb6a5..555db59b082 100644 --- a/erpnext/selling/doctype/customer/customer.py +++ b/erpnext/selling/doctype/customer/customer.py @@ -22,6 +22,7 @@ from erpnext.accounts.party import ( # noqa get_timeline_data, validate_party_accounts, ) +from erpnext.controllers.website_list_for_contact import add_role_for_portal_user from erpnext.utilities.transaction_base import TransactionBase @@ -82,6 +83,7 @@ class Customer(TransactionBase): self.check_customer_group_change() self.validate_default_bank_account() self.validate_internal_customer() + self.add_role_for_user() # set loyalty program tier if frappe.db.exists("Customer", self.name): @@ -170,6 +172,10 @@ class Customer(TransactionBase): self.update_customer_groups() + def add_role_for_user(self): + for portal_user in self.portal_users: + add_role_for_portal_user(portal_user, "Customer") + def update_customer_groups(self): ignore_doctypes = ["Lead", "Opportunity", "POS Profile", "Tax Rule", "Pricing Rule"] if frappe.flags.customer_group_changed: diff --git a/erpnext/tests/test_webform.py b/erpnext/tests/test_webform.py index 202467b5450..af50a057b8a 100644 --- a/erpnext/tests/test_webform.py +++ b/erpnext/tests/test_webform.py @@ -3,18 +3,21 @@ import unittest import frappe from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order +from erpnext.buying.doctype.supplier.test_supplier import create_supplier class TestWebsite(unittest.TestCase): def test_permission_for_custom_doctype(self): create_user("Supplier 1", "supplier1@gmail.com") create_user("Supplier 2", "supplier2@gmail.com") - create_supplier_with_contact( - "Supplier1", "All Supplier Groups", "Supplier 1", "supplier1@gmail.com" - ) - create_supplier_with_contact( - "Supplier2", "All Supplier Groups", "Supplier 2", "supplier2@gmail.com" - ) + + supplier1 = create_supplier(supplier_name="Supplier1") + supplier2 = create_supplier(supplier_name="Supplier2") + supplier1.append("portal_users", {"user": "supplier1@gmail.com"}) + supplier1.save() + supplier2.append("portal_users", {"user": "supplier2@gmail.com"}) + supplier2.save() + po1 = create_purchase_order(supplier="Supplier1") po2 = create_purchase_order(supplier="Supplier2") @@ -61,21 +64,6 @@ def create_user(name, email): ).insert(ignore_if_duplicate=True) -def create_supplier_with_contact(name, group, contact_name, contact_email): - supplier = frappe.get_doc( - {"doctype": "Supplier", "supplier_name": name, "supplier_group": group} - ).insert(ignore_if_duplicate=True) - - if not frappe.db.exists("Contact", contact_name + "-1-" + name): - new_contact = frappe.new_doc("Contact") - new_contact.first_name = contact_name - new_contact.is_primary_contact = (True,) - new_contact.append("links", {"link_doctype": "Supplier", "link_name": supplier.name}) - new_contact.append("email_ids", {"email_id": contact_email, "is_primary": 1}) - - new_contact.insert(ignore_mandatory=True) - - def create_custom_doctype(): frappe.get_doc( { diff --git a/erpnext/utilities/doctype/portal_user/__init__.py b/erpnext/utilities/doctype/portal_user/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/utilities/doctype/portal_user/portal_user.json b/erpnext/utilities/doctype/portal_user/portal_user.json new file mode 100644 index 00000000000..361166cb653 --- /dev/null +++ b/erpnext/utilities/doctype/portal_user/portal_user.json @@ -0,0 +1,34 @@ +{ + "actions": [], + "allow_rename": 1, + "creation": "2023-06-20 14:01:35.362233", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "user" + ], + "fields": [ + { + "fieldname": "user", + "fieldtype": "Link", + "in_list_view": 1, + "label": "User", + "options": "User", + "reqd": 1, + "search_index": 1 + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2023-06-26 14:15:34.695605", + "modified_by": "Administrator", + "module": "Utilities", + "name": "Portal User", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "states": [] +} \ No newline at end of file diff --git a/erpnext/utilities/doctype/portal_user/portal_user.py b/erpnext/utilities/doctype/portal_user/portal_user.py new file mode 100644 index 00000000000..2e0064d1981 --- /dev/null +++ b/erpnext/utilities/doctype/portal_user/portal_user.py @@ -0,0 +1,9 @@ +# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + + +class PortalUser(Document): + pass From b9e9204e522ea46dfcefc16deed474dd4e7d78d9 Mon Sep 17 00:00:00 2001 From: Gursheen Kaur Anand <40693548+GursheenK@users.noreply.github.com> Date: Wed, 28 Jun 2023 15:39:00 +0530 Subject: [PATCH 59/63] fix: create multiple variants button count and status (#35915) fix: change class for fetching columns in multiple variants --- erpnext/stock/doctype/item/item.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/item/item.js b/erpnext/stock/doctype/item/item.js index 6f1f981e2b9..31a3ecbc47e 100644 --- a/erpnext/stock/doctype/item/item.js +++ b/erpnext/stock/doctype/item/item.js @@ -590,7 +590,7 @@ $.extend(erpnext.item, { let selected_attributes = {}; me.multiple_variant_dialog.$wrapper.find('.form-column').each((i, col) => { if(i===0) return; - let attribute_name = $(col).find('.control-label').html().trim(); + let attribute_name = $(col).find('.column-label').html().trim(); selected_attributes[attribute_name] = []; let checked_opts = $(col).find('.checkbox input'); checked_opts.each((i, opt) => { From 9e1736e027cb679a639571f38daca55b10811035 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Wed, 28 Jun 2023 19:19:14 +0530 Subject: [PATCH 60/63] fix: index collect_progress and project date (#35920) There's background job to send progress update, this ends up scanning entire table. --- erpnext/projects/doctype/project/project.json | 5 +- .../project_update/project_update.json | 427 ++++-------------- 2 files changed, 92 insertions(+), 340 deletions(-) diff --git a/erpnext/projects/doctype/project/project.json b/erpnext/projects/doctype/project/project.json index 2b3392a67bc..f007430ab37 100644 --- a/erpnext/projects/doctype/project/project.json +++ b/erpnext/projects/doctype/project/project.json @@ -364,7 +364,8 @@ "default": "0", "fieldname": "collect_progress", "fieldtype": "Check", - "label": "Collect Progress" + "label": "Collect Progress", + "search_index": 1 }, { "depends_on": "collect_progress", @@ -451,7 +452,7 @@ "index_web_pages_for_search": 1, "links": [], "max_attachments": 4, - "modified": "2023-04-17 21:11:11.346986", + "modified": "2023-06-28 18:57:11.603497", "modified_by": "Administrator", "module": "Projects", "name": "Project", diff --git a/erpnext/projects/doctype/project_update/project_update.json b/erpnext/projects/doctype/project_update/project_update.json index 497b2b73285..c548111f2fb 100644 --- a/erpnext/projects/doctype/project_update/project_update.json +++ b/erpnext/projects/doctype/project_update/project_update.json @@ -1,355 +1,106 @@ { - "allow_copy": 0, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "autoname": "naming_series:", - "beta": 0, - "creation": "2018-01-18 09:44:47.565494", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, - "engine": "InnoDB", + "actions": [], + "autoname": "naming_series:", + "creation": "2018-01-18 09:44:47.565494", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "naming_series", + "project", + "sent", + "column_break_2", + "date", + "time", + "section_break_5", + "users", + "amended_from" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "", - "fieldname": "naming_series", - "fieldtype": "Data", - "hidden": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Series", - "length": 0, - "no_copy": 0, - "options": "PROJ-UPD-.YYYY.-", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "naming_series", + "fieldtype": "Data", + "hidden": 1, + "label": "Series", + "options": "PROJ-UPD-.YYYY.-" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "project", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Project", - "length": 0, - "no_copy": 0, - "options": "Project", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "project", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Project", + "options": "Project", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "0", - "fieldname": "sent", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Sent", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "default": "0", + "fieldname": "sent", + "fieldtype": "Check", + "label": "Sent", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_2", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "column_break_2", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "date", - "fieldtype": "Date", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Date", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "date", + "fieldtype": "Date", + "label": "Date", + "read_only": 1, + "search_index": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "time", - "fieldtype": "Time", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Time", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "time", + "fieldtype": "Time", + "label": "Time", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "section_break_5", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "section_break_5", + "fieldtype": "Section Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "users", - "fieldtype": "Table", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Users", - "length": 0, - "no_copy": 0, - "options": "Project User", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "users", + "fieldtype": "Table", + "label": "Users", + "options": "Project User" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "amended_from", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Amended From", - "length": 0, - "no_copy": 1, - "options": "Project Update", - "permlevel": 0, - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "fieldname": "amended_from", + "fieldtype": "Link", + "label": "Amended From", + "no_copy": 1, + "options": "Project Update", + "print_hide": 1, + "read_only": 1 } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 1, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2019-01-16 19:31:05.210656", - "modified_by": "Administrator", - "module": "Projects", - "name": "Project Update", - "name_case": "", - "owner": "Administrator", + ], + "is_submittable": 1, + "links": [], + "modified": "2023-06-28 18:59:50.678917", + "modified_by": "Administrator", + "module": "Projects", + "name": "Project Update", + "naming_rule": "By \"Naming Series\" field", + "owner": "Administrator", "permissions": [ { - "amend": 0, - "cancel": 0, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Projects User", - "set_user_permissions": 0, - "share": 1, - "submit": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Projects User", + "share": 1, + "submit": 1, "write": 1 } - ], - "quick_entry": 0, - "read_only": 0, - "read_only_onload": 0, - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1, - "track_seen": 0, - "track_views": 0 + ], + "sort_field": "modified", + "sort_order": "DESC", + "states": [], + "track_changes": 1 } \ No newline at end of file From 86f453593a6aeb208741514b0cf9a66ab8f18a42 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Wed, 28 Jun 2023 20:15:29 +0530 Subject: [PATCH 61/63] perf: avoid perm checks from background jobs --- erpnext/crm/doctype/social_media_post/social_media_post.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/crm/doctype/social_media_post/social_media_post.py b/erpnext/crm/doctype/social_media_post/social_media_post.py index 55db29a627a..3654d29bdc0 100644 --- a/erpnext/crm/doctype/social_media_post/social_media_post.py +++ b/erpnext/crm/doctype/social_media_post/social_media_post.py @@ -74,7 +74,7 @@ class SocialMediaPost(Document): def process_scheduled_social_media_posts(): - posts = frappe.get_list( + posts = frappe.get_all( "Social Media Post", filters={"post_status": "Scheduled", "docstatus": 1}, fields=["name", "scheduled_time"], From a4d6f2eba6d3f17a4ea4a098710ca3cd6688084d Mon Sep 17 00:00:00 2001 From: Anand Baburajan Date: Wed, 28 Jun 2023 20:15:40 +0530 Subject: [PATCH 62/63] fix: asset movement (#35918) fix: asset movement fixes (cherry picked from commit e16c14863b52aaa7856c799ad64fe977d4a4fbbe) --- .../doctype/asset_movement/asset_movement.js | 18 +++++++++------- .../asset_movement/asset_movement.json | 7 +++++-- .../doctype/asset_movement/asset_movement.py | 19 +++++++---------- .../asset_movement/test_asset_movement.py | 21 ++++++++++++++----- 4 files changed, 38 insertions(+), 27 deletions(-) diff --git a/erpnext/assets/doctype/asset_movement/asset_movement.js b/erpnext/assets/doctype/asset_movement/asset_movement.js index 2df7db97446..f9c600731b3 100644 --- a/erpnext/assets/doctype/asset_movement/asset_movement.js +++ b/erpnext/assets/doctype/asset_movement/asset_movement.js @@ -70,19 +70,21 @@ frappe.ui.form.on('Asset Movement', { else if (frm.doc.purpose === 'Issue') { fieldnames_to_be_altered = { target_location: { read_only: 1, reqd: 0 }, - source_location: { read_only: 1, reqd: 1 }, + source_location: { read_only: 1, reqd: 0 }, from_employee: { read_only: 1, reqd: 0 }, to_employee: { read_only: 0, reqd: 1 } }; } - Object.keys(fieldnames_to_be_altered).forEach(fieldname => { - let property_to_be_altered = fieldnames_to_be_altered[fieldname]; - Object.keys(property_to_be_altered).forEach(property => { - let value = property_to_be_altered[property]; - frm.set_df_property(fieldname, property, value, cdn, 'assets'); + if (fieldnames_to_be_altered) { + Object.keys(fieldnames_to_be_altered).forEach(fieldname => { + let property_to_be_altered = fieldnames_to_be_altered[fieldname]; + Object.keys(property_to_be_altered).forEach(property => { + let value = property_to_be_altered[property]; + frm.fields_dict['assets'].grid.update_docfield_property(fieldname, property, value); + }); }); - }); - frm.refresh_field('assets'); + frm.refresh_field('assets'); + } } }); diff --git a/erpnext/assets/doctype/asset_movement/asset_movement.json b/erpnext/assets/doctype/asset_movement/asset_movement.json index bdce639b039..5382f9e75f2 100644 --- a/erpnext/assets/doctype/asset_movement/asset_movement.json +++ b/erpnext/assets/doctype/asset_movement/asset_movement.json @@ -37,6 +37,7 @@ "reqd": 1 }, { + "default": "Now", "fieldname": "transaction_date", "fieldtype": "Datetime", "in_list_view": 1, @@ -95,10 +96,11 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2021-01-22 12:30:55.295670", + "modified": "2023-06-28 16:54:26.571083", "modified_by": "Administrator", "module": "Assets", "name": "Asset Movement", + "naming_rule": "Expression", "owner": "Administrator", "permissions": [ { @@ -148,5 +150,6 @@ } ], "sort_field": "modified", - "sort_order": "DESC" + "sort_order": "DESC", + "states": [] } \ No newline at end of file diff --git a/erpnext/assets/doctype/asset_movement/asset_movement.py b/erpnext/assets/doctype/asset_movement/asset_movement.py index 143f215db2e..b58ca10482b 100644 --- a/erpnext/assets/doctype/asset_movement/asset_movement.py +++ b/erpnext/assets/doctype/asset_movement/asset_movement.py @@ -28,25 +28,20 @@ class AssetMovement(Document): def validate_location(self): for d in self.assets: if self.purpose in ["Transfer", "Issue"]: - if not d.source_location: - d.source_location = frappe.db.get_value("Asset", d.asset, "location") - - if not d.source_location: - frappe.throw(_("Source Location is required for the Asset {0}").format(d.asset)) - + current_location = frappe.db.get_value("Asset", d.asset, "location") if d.source_location: - current_location = frappe.db.get_value("Asset", d.asset, "location") - if current_location != d.source_location: frappe.throw( _("Asset {0} does not belongs to the location {1}").format(d.asset, d.source_location) ) + else: + d.source_location = current_location if self.purpose == "Issue": if d.target_location: frappe.throw( _( - "Issuing cannot be done to a location. Please enter employee who has issued Asset {0}" + "Issuing cannot be done to a location. Please enter employee to issue the Asset {0} to" ).format(d.asset), title=_("Incorrect Movement Purpose"), ) @@ -107,12 +102,12 @@ class AssetMovement(Document): ) def on_submit(self): - self.set_latest_location_in_asset() + self.set_latest_location_and_custodian_in_asset() def on_cancel(self): - self.set_latest_location_in_asset() + self.set_latest_location_and_custodian_in_asset() - def set_latest_location_in_asset(self): + def set_latest_location_and_custodian_in_asset(self): current_location, current_employee = "", "" cond = "1=1" diff --git a/erpnext/assets/doctype/asset_movement/test_asset_movement.py b/erpnext/assets/doctype/asset_movement/test_asset_movement.py index 72c05752c57..27e7e557f19 100644 --- a/erpnext/assets/doctype/asset_movement/test_asset_movement.py +++ b/erpnext/assets/doctype/asset_movement/test_asset_movement.py @@ -47,7 +47,7 @@ class TestAssetMovement(unittest.TestCase): if not frappe.db.exists("Location", "Test Location 2"): frappe.get_doc({"doctype": "Location", "location_name": "Test Location 2"}).insert() - movement1 = create_asset_movement( + create_asset_movement( purpose="Transfer", company=asset.company, assets=[ @@ -58,7 +58,7 @@ class TestAssetMovement(unittest.TestCase): ) self.assertEqual(frappe.db.get_value("Asset", asset.name, "location"), "Test Location 2") - create_asset_movement( + movement1 = create_asset_movement( purpose="Transfer", company=asset.company, assets=[ @@ -70,21 +70,32 @@ class TestAssetMovement(unittest.TestCase): self.assertEqual(frappe.db.get_value("Asset", asset.name, "location"), "Test Location") movement1.cancel() - self.assertEqual(frappe.db.get_value("Asset", asset.name, "location"), "Test Location") + self.assertEqual(frappe.db.get_value("Asset", asset.name, "location"), "Test Location 2") employee = make_employee("testassetmovemp@example.com", company="_Test Company") create_asset_movement( purpose="Issue", company=asset.company, - assets=[{"asset": asset.name, "source_location": "Test Location", "to_employee": employee}], + assets=[{"asset": asset.name, "source_location": "Test Location 2", "to_employee": employee}], reference_doctype="Purchase Receipt", reference_name=pr.name, ) - # after issuing asset should belong to an employee not at a location + # after issuing, asset should belong to an employee not at a location self.assertEqual(frappe.db.get_value("Asset", asset.name, "location"), None) self.assertEqual(frappe.db.get_value("Asset", asset.name, "custodian"), employee) + create_asset_movement( + purpose="Receipt", + company=asset.company, + assets=[{"asset": asset.name, "from_employee": employee, "target_location": "Test Location"}], + reference_doctype="Purchase Receipt", + reference_name=pr.name, + ) + + # after receiving, asset should belong to a location not at an employee + self.assertEqual(frappe.db.get_value("Asset", asset.name, "location"), "Test Location") + def test_last_movement_cancellation(self): pr = make_purchase_receipt( item_code="Macbook Pro", qty=1, rate=100000.0, location="Test Location" From 1d1103f39c173509f0bd3ba04c1c142db27b4e80 Mon Sep 17 00:00:00 2001 From: RJPvT <48353029+RJPvT@users.noreply.github.com> Date: Thu, 29 Jun 2023 07:32:08 +0200 Subject: [PATCH 63/63] chore: update translations chore: update translations --- erpnext/translations/nl.csv | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/translations/nl.csv b/erpnext/translations/nl.csv index f2158b55871..a13382cf10e 100644 --- a/erpnext/translations/nl.csv +++ b/erpnext/translations/nl.csv @@ -875,7 +875,7 @@ Donor,schenker, Donor Type information.,Donor Type informatie., Donor information.,Donorinformatie., Download JSON,JSON downloaden, -Draft,Droogte, +Draft,Concept, Drop Ship,Drop Ship, Drug,drug, Due / Reference Date cannot be after {0},Verval- / Referentiedatum kan niet na {0} zijn, @@ -4279,7 +4279,7 @@ Failed to setup defaults for country {0}. Please contact support@erpnext.com,Kan Row #{0}: Item {1} is not a Serialized/Batched Item. It cannot have a Serial No/Batch No against it.,Rij # {0}: artikel {1} is geen geserialiseerd / batch artikel. Het kan geen serienummer / batchnummer hebben., Please set {0},Stel {0} in, Please set {0},Stel {0} in,supplier -Draft,Droogte,"docstatus,=,0" +Draft,Concept,"docstatus,=,0" Cancelled,Geannuleerd,"docstatus,=,2" Please setup Instructor Naming System in Education > Education Settings,Stel het instructeursysteem in onder onderwijs> onderwijsinstellingen, Please set Naming Series for {0} via Setup > Settings > Naming Series,Stel Naming Series in op {0} via Instellingen> Instellingen> Naming Series, @@ -8189,7 +8189,7 @@ Actual Batch Quantity,Werkelijke batchhoeveelheid, Prevdoc DocType,Prevdoc DocType, Parent Detail docname,Bovenliggende Detail docname, "Generate packing slips for packages to be delivered. Used to notify package number, package contents and its weight.","Genereren van pakbonnen voor pakketten te leveren. Gebruikt voor pakket nummer, inhoud van de verpakking en het gewicht te melden.", -Indicates that the package is a part of this delivery (Only Draft),Geeft aan dat het pakket een onderdeel is van deze levering (alleen ontwerp), +Indicates that the package is a part of this delivery (Only Draft),Geeft aan dat het pakket een onderdeel is van deze levering (alleen concept), MAT-PAC-.YYYY.-,MAT-PAC-.YYYY.-, From Package No.,Van Pakket No, Identification of the package for the delivery (for print),Identificatie van het pakket voor de levering (voor afdrukken),