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 8cc3bc3bf47..6017c5a491d 100644 --- a/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.py +++ b/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.py @@ -423,9 +423,7 @@ def reconcile_vouchers(bank_transaction_name, vouchers): vouchers = json.loads(vouchers) transaction = frappe.get_doc("Bank Transaction", bank_transaction_name) transaction.add_payment_entries(vouchers) - transaction.save() - - return transaction + return frappe.get_doc("Bank Transaction", bank_transaction_name) @frappe.whitelist() diff --git a/erpnext/accounts/doctype/bank_transaction/bank_transaction.json b/erpnext/accounts/doctype/bank_transaction/bank_transaction.json index 8c5a5eaef5d..bb7a4771b2d 100644 --- a/erpnext/accounts/doctype/bank_transaction/bank_transaction.json +++ b/erpnext/accounts/doctype/bank_transaction/bank_transaction.json @@ -13,7 +13,6 @@ "status", "bank_account", "company", - "amended_from", "section_break_4", "deposit", "withdrawal", @@ -26,10 +25,10 @@ "transaction_id", "transaction_type", "section_break_14", - "column_break_oufv", "payment_entries", "section_break_18", "allocated_amount", + "amended_from", "column_break_17", "unallocated_amount", "party_section", @@ -139,12 +138,10 @@ "fieldtype": "Section Break" }, { - "allow_on_submit": 1, "fieldname": "allocated_amount", "fieldtype": "Currency", "label": "Allocated Amount", - "options": "currency", - "read_only": 1 + "options": "currency" }, { "fieldname": "amended_from", @@ -160,12 +157,10 @@ "fieldtype": "Column Break" }, { - "allow_on_submit": 1, "fieldname": "unallocated_amount", "fieldtype": "Currency", "label": "Unallocated Amount", - "options": "currency", - "read_only": 1 + "options": "currency" }, { "fieldname": "party_section", @@ -230,15 +225,11 @@ "fieldname": "bank_party_account_number", "fieldtype": "Data", "label": "Party Account No. (Bank Statement)" - }, - { - "fieldname": "column_break_oufv", - "fieldtype": "Column Break" } ], "is_submittable": 1, "links": [], - "modified": "2023-11-18 18:32:47.203694", + "modified": "2023-06-06 13:58:12.821411", "modified_by": "Administrator", "module": "Accounts", "name": "Bank Transaction", diff --git a/erpnext/accounts/doctype/bank_transaction/bank_transaction.py b/erpnext/accounts/doctype/bank_transaction/bank_transaction.py index a9a8b159950..256bde5c719 100644 --- a/erpnext/accounts/doctype/bank_transaction/bank_transaction.py +++ b/erpnext/accounts/doctype/bank_transaction/bank_transaction.py @@ -2,73 +2,78 @@ # For license information, please see license.txt import frappe -from frappe import _ from frappe.utils import flt from erpnext.controllers.status_updater import StatusUpdater class BankTransaction(StatusUpdater): - def before_validate(self): - self.update_allocated_amount() + def after_insert(self): + self.unallocated_amount = abs(flt(self.withdrawal) - flt(self.deposit)) - def validate(self): - self.validate_duplicate_references() - - def validate_duplicate_references(self): - """Make sure the same voucher is not allocated twice within the same Bank Transaction""" - if not self.payment_entries: - return - - pe = [] - for row in self.payment_entries: - reference = (row.payment_document, row.payment_entry) - if reference in pe: - frappe.throw( - _("{0} {1} is allocated twice in this Bank Transaction").format( - row.payment_document, row.payment_entry - ) - ) - pe.append(reference) - - def update_allocated_amount(self): - self.allocated_amount = ( - sum(p.allocated_amount for p in self.payment_entries) if self.payment_entries else 0.0 - ) - self.unallocated_amount = abs(flt(self.withdrawal) - flt(self.deposit)) - self.allocated_amount - - def before_submit(self): - self.allocate_payment_entries() + def on_submit(self): + self.clear_linked_payment_entries() self.set_status() if frappe.db.get_single_value("Accounts Settings", "enable_party_matching"): self.auto_set_party() - def before_update_after_submit(self): - self.validate_duplicate_references() - self.allocate_payment_entries() - self.update_allocated_amount() + _saving_flag = False + + # nosemgrep: frappe-semgrep-rules.rules.frappe-modifying-but-not-comitting + def on_update_after_submit(self): + "Run on save(). Avoid recursion caused by multiple saves" + if not self._saving_flag: + self._saving_flag = True + self.clear_linked_payment_entries() + self.update_allocations() + self._saving_flag = False def on_cancel(self): - for payment_entry in self.payment_entries: - self.clear_linked_payment_entry(payment_entry, for_cancel=True) + self.clear_linked_payment_entries(for_cancel=True) + self.set_status(update=True) + def update_allocations(self): + "The doctype does not allow modifications after submission, so write to the db direct" + if self.payment_entries: + allocated_amount = sum(p.allocated_amount for p in self.payment_entries) + else: + allocated_amount = 0.0 + + amount = abs(flt(self.withdrawal) - flt(self.deposit)) + self.db_set("allocated_amount", flt(allocated_amount)) + self.db_set("unallocated_amount", amount - flt(allocated_amount)) + self.reload() self.set_status(update=True) def add_payment_entries(self, vouchers): "Add the vouchers with zero allocation. Save() will perform the allocations and clearance" if 0.0 >= self.unallocated_amount: - frappe.throw(_("Bank Transaction {0} is already fully reconciled").format(self.name)) + frappe.throw(frappe._("Bank Transaction {0} is already fully reconciled").format(self.name)) + added = False for voucher in vouchers: - self.append( - "payment_entries", - { + # Can't add same voucher twice + found = False + for pe in self.payment_entries: + if ( + pe.payment_document == voucher["payment_doctype"] + and pe.payment_entry == voucher["payment_name"] + ): + found = True + + if not found: + pe = { "payment_document": voucher["payment_doctype"], "payment_entry": voucher["payment_name"], "allocated_amount": 0.0, # Temporary - }, - ) + } + child = self.append("payment_entries", pe) + added = True + + # runs on_update_after_submit + if added: + self.save() def allocate_payment_entries(self): """Refactored from bank reconciliation tool. @@ -85,7 +90,6 @@ class BankTransaction(StatusUpdater): - clear means: set the latest transaction date as clearance date """ remaining_amount = self.unallocated_amount - to_remove = [] for payment_entry in self.payment_entries: if payment_entry.allocated_amount == 0.0: unallocated_amount, should_clear, latest_transaction = get_clearance_details( @@ -95,39 +99,49 @@ class BankTransaction(StatusUpdater): if 0.0 == unallocated_amount: if should_clear: latest_transaction.clear_linked_payment_entry(payment_entry) - to_remove.append(payment_entry) + self.db_delete_payment_entry(payment_entry) elif remaining_amount <= 0.0: - to_remove.append(payment_entry) + self.db_delete_payment_entry(payment_entry) - elif 0.0 < unallocated_amount <= remaining_amount: - payment_entry.allocated_amount = unallocated_amount + elif 0.0 < unallocated_amount and unallocated_amount <= remaining_amount: + payment_entry.db_set("allocated_amount", unallocated_amount) remaining_amount -= unallocated_amount if should_clear: latest_transaction.clear_linked_payment_entry(payment_entry) - elif 0.0 < unallocated_amount: - payment_entry.allocated_amount = remaining_amount + elif 0.0 < unallocated_amount and unallocated_amount > remaining_amount: + payment_entry.db_set("allocated_amount", remaining_amount) remaining_amount = 0.0 elif 0.0 > unallocated_amount: - frappe.throw(_("Voucher {0} is over-allocated by {1}").format(unallocated_amount)) + self.db_delete_payment_entry(payment_entry) + frappe.throw(frappe._("Voucher {0} is over-allocated by {1}").format(unallocated_amount)) - for payment_entry in to_remove: - self.remove(to_remove) + self.reload() + + def db_delete_payment_entry(self, payment_entry): + frappe.db.delete("Bank Transaction Payments", {"name": payment_entry.name}) @frappe.whitelist() def remove_payment_entries(self): for payment_entry in self.payment_entries: self.remove_payment_entry(payment_entry) - - self.save() # runs before_update_after_submit + # runs on_update_after_submit + self.save() def remove_payment_entry(self, payment_entry): "Clear payment entry and clearance" self.clear_linked_payment_entry(payment_entry, for_cancel=True) self.remove(payment_entry) + def clear_linked_payment_entries(self, for_cancel=False): + if for_cancel: + for payment_entry in self.payment_entries: + self.clear_linked_payment_entry(payment_entry, for_cancel) + else: + self.allocate_payment_entries() + def clear_linked_payment_entry(self, payment_entry, for_cancel=False): clearance_date = None if for_cancel else self.date set_voucher_clearance( @@ -148,10 +162,11 @@ class BankTransaction(StatusUpdater): deposit=self.deposit, ).match() - if not result: - return - - self.party_type, self.party = result + if result: + party_type, party = result + frappe.db.set_value( + "Bank Transaction", self.name, field={"party_type": party_type, "party": party} + ) @frappe.whitelist() @@ -183,7 +198,9 @@ def get_clearance_details(transaction, payment_entry): if gle["gl_account"] == gl_bank_account: if gle["amount"] <= 0.0: frappe.throw( - _("Voucher {0} value is broken: {1}").format(payment_entry.payment_entry, gle["amount"]) + frappe._("Voucher {0} value is broken: {1}").format( + payment_entry.payment_entry, gle["amount"] + ) ) unmatched_gles -= 1 @@ -204,7 +221,7 @@ def get_clearance_details(transaction, payment_entry): def get_related_bank_gl_entries(doctype, docname): # nosemgrep: frappe-semgrep-rules.rules.frappe-using-db-sql - return frappe.db.sql( + result = frappe.db.sql( """ SELECT ABS(gle.credit_in_account_currency - gle.debit_in_account_currency) AS amount, @@ -222,6 +239,7 @@ def get_related_bank_gl_entries(doctype, docname): dict(doctype=doctype, docname=docname), as_dict=True, ) + return result def get_total_allocated_amount(doctype, docname): @@ -354,7 +372,6 @@ def set_voucher_clearance(doctype, docname, clearance_date, self): if clearance_date: vouchers = [{"payment_doctype": "Bank Transaction", "payment_name": self.name}] bt.add_payment_entries(vouchers) - bt.save() else: for pe in bt.payment_entries: if pe.payment_document == self.doctype and pe.payment_entry == self.name: diff --git a/erpnext/accounts/doctype/payment_reconciliation_allocation/payment_reconciliation_allocation.py b/erpnext/accounts/doctype/payment_reconciliation_allocation/payment_reconciliation_allocation.py index 9db8e62af08..d7b457a83a7 100644 --- a/erpnext/accounts/doctype/payment_reconciliation_allocation/payment_reconciliation_allocation.py +++ b/erpnext/accounts/doctype/payment_reconciliation_allocation/payment_reconciliation_allocation.py @@ -6,4 +6,6 @@ from frappe.model.document import Document class PaymentReconciliationAllocation(Document): - pass + @staticmethod + def get_list(args): + pass diff --git a/erpnext/accounts/doctype/payment_reconciliation_invoice/payment_reconciliation_invoice.py b/erpnext/accounts/doctype/payment_reconciliation_invoice/payment_reconciliation_invoice.py index 7665b75ff0f..eb7e4a5ad17 100644 --- a/erpnext/accounts/doctype/payment_reconciliation_invoice/payment_reconciliation_invoice.py +++ b/erpnext/accounts/doctype/payment_reconciliation_invoice/payment_reconciliation_invoice.py @@ -6,4 +6,6 @@ from frappe.model.document import Document class PaymentReconciliationInvoice(Document): - pass + @staticmethod + def get_list(args): + pass diff --git a/erpnext/accounts/doctype/payment_reconciliation_payment/payment_reconciliation_payment.py b/erpnext/accounts/doctype/payment_reconciliation_payment/payment_reconciliation_payment.py index c0e3fd641ad..1c66b9b4cb5 100644 --- a/erpnext/accounts/doctype/payment_reconciliation_payment/payment_reconciliation_payment.py +++ b/erpnext/accounts/doctype/payment_reconciliation_payment/payment_reconciliation_payment.py @@ -6,4 +6,6 @@ from frappe.model.document import Document class PaymentReconciliationPayment(Document): - pass + @staticmethod + def get_list(args): + pass diff --git a/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.py b/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.py index 1d72a46c12f..9211b286c7d 100644 --- a/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.py +++ b/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.py @@ -108,7 +108,7 @@ class RepostAccountingLedger(Document): return rendered_page def on_submit(self): - if len(self.vouchers) > 1: + if len(self.vouchers) > 5: job_name = "repost_accounting_ledger_" + self.name frappe.enqueue( method="erpnext.accounts.doctype.repost_accounting_ledger.repost_accounting_ledger.start_repost", @@ -152,8 +152,6 @@ def start_repost(account_repost_doc=str) -> None: doc.make_gl_entries(1) doc.make_gl_entries() - frappe.db.commit() - def get_allowed_types_from_settings(): return [ diff --git a/erpnext/accounts/doctype/repost_accounting_ledger/test_repost_accounting_ledger.py b/erpnext/accounts/doctype/repost_accounting_ledger/test_repost_accounting_ledger.py index dda0ec778f6..d6f7096132f 100644 --- a/erpnext/accounts/doctype/repost_accounting_ledger/test_repost_accounting_ledger.py +++ b/erpnext/accounts/doctype/repost_accounting_ledger/test_repost_accounting_ledger.py @@ -20,18 +20,11 @@ class TestRepostAccountingLedger(AccountsTestMixin, FrappeTestCase): self.create_company() self.create_customer() self.create_item() - self.update_repost_settings() + update_repost_settings() - def teadDown(self): + def tearDown(self): frappe.db.rollback() - def update_repost_settings(self): - allowed_types = ["Sales Invoice", "Purchase Invoice", "Payment Entry", "Journal Entry"] - repost_settings = frappe.get_doc("Repost Accounting Ledger Settings") - for x in allowed_types: - repost_settings.append("allowed_types", {"document_type": x, "allowed": True}) - repost_settings.save() - def test_01_basic_functions(self): si = create_sales_invoice( item=self.item, @@ -90,9 +83,6 @@ class TestRepostAccountingLedger(AccountsTestMixin, FrappeTestCase): # Submit repost document ral.save().submit() - # background jobs don't run on test cases. Manually triggering repost function. - start_repost(ral.name) - res = ( qb.from_(gl) .select(gl.voucher_no, Sum(gl.debit).as_("debit"), Sum(gl.credit).as_("credit")) @@ -177,26 +167,6 @@ class TestRepostAccountingLedger(AccountsTestMixin, FrappeTestCase): pe = get_payment_entry(si.doctype, si.name) pe.save().submit() - # without deletion flag set - ral = frappe.new_doc("Repost Accounting Ledger") - ral.company = self.company - ral.delete_cancelled_entries = False - ral.append("vouchers", {"voucher_type": si.doctype, "voucher_no": si.name}) - ral.append("vouchers", {"voucher_type": pe.doctype, "voucher_no": pe.name}) - ral.save() - - # assert preview data is generated - preview = ral.generate_preview() - self.assertIsNotNone(preview) - - ral.save().submit() - - # background jobs don't run on test cases. Manually triggering repost function. - start_repost(ral.name) - - self.assertIsNotNone(frappe.db.exists("GL Entry", {"voucher_no": si.name, "is_cancelled": 1})) - self.assertIsNotNone(frappe.db.exists("GL Entry", {"voucher_no": pe.name, "is_cancelled": 1})) - # with deletion flag set ral = frappe.new_doc("Repost Accounting Ledger") ral.company = self.company @@ -205,6 +175,38 @@ class TestRepostAccountingLedger(AccountsTestMixin, FrappeTestCase): ral.append("vouchers", {"voucher_type": pe.doctype, "voucher_no": pe.name}) ral.save().submit() - start_repost(ral.name) self.assertIsNone(frappe.db.exists("GL Entry", {"voucher_no": si.name, "is_cancelled": 1})) self.assertIsNone(frappe.db.exists("GL Entry", {"voucher_no": pe.name, "is_cancelled": 1})) + + def test_05_without_deletion_flag(self): + si = create_sales_invoice( + item=self.item, + company=self.company, + customer=self.customer, + debit_to=self.debit_to, + parent_cost_center=self.cost_center, + cost_center=self.cost_center, + rate=100, + ) + + pe = get_payment_entry(si.doctype, si.name) + pe.save().submit() + + # without deletion flag set + ral = frappe.new_doc("Repost Accounting Ledger") + ral.company = self.company + ral.delete_cancelled_entries = False + ral.append("vouchers", {"voucher_type": si.doctype, "voucher_no": si.name}) + ral.append("vouchers", {"voucher_type": pe.doctype, "voucher_no": pe.name}) + ral.save().submit() + + self.assertIsNotNone(frappe.db.exists("GL Entry", {"voucher_no": si.name, "is_cancelled": 1})) + self.assertIsNotNone(frappe.db.exists("GL Entry", {"voucher_no": pe.name, "is_cancelled": 1})) + + +def update_repost_settings(): + allowed_types = ["Sales Invoice", "Purchase Invoice", "Payment Entry", "Journal Entry"] + repost_settings = frappe.get_doc("Repost Accounting Ledger Settings") + for x in allowed_types: + repost_settings.append("allowed_types", {"document_type": x, "allowed": True}) + repost_settings.save() diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 584f297c904..d050299912d 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -2791,6 +2791,12 @@ class TestSalesInvoice(FrappeTestCase): @change_settings("Selling Settings", {"enable_discount_accounting": 1}) def test_additional_discount_for_sales_invoice_with_discount_accounting_enabled(self): + from erpnext.accounts.doctype.repost_accounting_ledger.test_repost_accounting_ledger import ( + update_repost_settings, + ) + + update_repost_settings() + additional_discount_account = create_account( account_name="Discount Account", parent_account="Indirect Expenses - _TC", diff --git a/erpnext/accounts/report/financial_statements.py b/erpnext/accounts/report/financial_statements.py index 096bb107069..7355c4b8a16 100644 --- a/erpnext/accounts/report/financial_statements.py +++ b/erpnext/accounts/report/financial_statements.py @@ -8,7 +8,17 @@ import re import frappe from frappe import _ -from frappe.utils import add_days, add_months, cint, cstr, flt, formatdate, get_first_day, getdate +from frappe.utils import ( + add_days, + add_months, + cint, + cstr, + flt, + formatdate, + get_first_day, + getdate, + today, +) from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import ( get_accounting_dimensions, @@ -43,6 +53,8 @@ def get_period_list( year_start_date = getdate(period_start_date) year_end_date = getdate(period_end_date) + year_end_date = getdate(today()) if year_end_date > getdate(today()) else year_end_date + months_to_add = {"Yearly": 12, "Half-Yearly": 6, "Quarterly": 3, "Monthly": 1}[periodicity] period_list = [] diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index e9c1e14b94c..92a349abb3e 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -427,6 +427,10 @@ class Asset(AccountsController): n == 0 and (has_pro_rata or has_wdv_or_dd_non_yearly_pro_rata) and not self.opening_accumulated_depreciation + and get_updated_rate_of_depreciation_for_wdv_and_dd( + self, value_after_depreciation, finance_book, False + ) + == finance_book.rate_of_depreciation ): from_date = add_days( self.available_for_use_date, -1 @@ -1387,7 +1391,9 @@ def get_depreciation_amount( @erpnext.allow_regional -def get_updated_rate_of_depreciation_for_wdv_and_dd(asset, depreciable_value, fb_row): +def get_updated_rate_of_depreciation_for_wdv_and_dd( + asset, depreciable_value, fb_row, show_msg=True +): return fb_row.rate_of_depreciation diff --git a/erpnext/hooks.py b/erpnext/hooks.py index dad12c4d591..054f9dedabe 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -638,4 +638,5 @@ additional_timeline_content = { extend_bootinfo = [ "erpnext.support.doctype.service_level_agreement.service_level_agreement.add_sla_doctypes", + "erpnext.startup.boot.bootinfo", ] diff --git a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py index 0d16c7f0c50..0b057f85aa3 100644 --- a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py +++ b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py @@ -599,6 +599,8 @@ def regenerate_repayment_schedule(loan, cancel=0): last_repayment_amount = None last_balance_amount = None + original_repayment_schedule_len = len(loan_doc.get("repayment_schedule")) + for term in reversed(loan_doc.get("repayment_schedule")): if not term.is_accrued: next_accrual_date = term.payment_date @@ -616,7 +618,7 @@ def regenerate_repayment_schedule(loan, cancel=0): if loan_doc.repayment_method == "Repay Fixed Amount per Period": monthly_repayment_amount = flt( - balance_amount / len(loan_doc.get("repayment_schedule")) - accrued_entries + balance_amount / (original_repayment_schedule_len - accrued_entries) ) else: repayment_period = loan_doc.repayment_periods - accrued_entries diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 050b9dcd3db..ff00a3ade16 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -441,7 +441,6 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe item.pricing_rules = '' return this.frm.call({ method: "erpnext.stock.get_item_details.get_item_details", - child: item, args: { doc: me.frm.doc, args: { @@ -490,6 +489,19 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe callback: function(r) { if(!r.exc) { frappe.run_serially([ + () => { + var child = locals[cdt][cdn]; + var std_field_list = ["doctype"] + .concat(frappe.model.std_fields_list) + .concat(frappe.model.child_table_field_list); + + for (var key in r.message) { + if (std_field_list.indexOf(key) === -1) { + if (key === "qty" && child[key]) continue; + child[key] = r.message[key]; + } + } + }, () => { var d = locals[cdt][cdn]; me.add_taxes_from_item_tax_template(d.item_tax_rate); diff --git a/erpnext/public/js/financial_statements.js b/erpnext/public/js/financial_statements.js index 907a775bfa5..5e1974299ee 100644 --- a/erpnext/public/js/financial_statements.js +++ b/erpnext/public/js/financial_statements.js @@ -2,10 +2,16 @@ frappe.provide("erpnext.financial_statements"); erpnext.financial_statements = { "filters": get_filters(), - "formatter": function(value, row, column, data, default_formatter) { + "formatter": function(value, row, column, data, default_formatter, filter) { if (data && column.fieldname=="account") { value = data.account_name || value; + if (filter && filter?.text && filter?.type == "contains") { + if (!value.toLowerCase().includes(filter.text)) { + return value; + } + } + if (data.account) { column.link_onclick = "erpnext.financial_statements.open_general_ledger(" + JSON.stringify(data) + ")"; diff --git a/erpnext/startup/boot.py b/erpnext/startup/boot.py index db1cc494e0b..bc4b6141bf0 100644 --- a/erpnext/startup/boot.py +++ b/erpnext/startup/boot.py @@ -73,3 +73,11 @@ def update_page_info(bootinfo): "Sales Person Tree": {"title": "Sales Person Tree", "route": "Tree/Sales Person"}, } ) + + +def bootinfo(bootinfo): + if bootinfo.get("user") and bootinfo["user"].get("name"): + bootinfo["user"]["employee"] = "" + employee = frappe.db.get_value("Employee", {"user_id": bootinfo["user"]["name"]}, "name") + if employee: + bootinfo["user"]["employee"] = employee