diff --git a/.mergify.yml b/.mergify.yml index c5f3d83ce63..804b27d4357 100644 --- a/.mergify.yml +++ b/.mergify.yml @@ -15,6 +15,8 @@ pull_request_rules: - or: - base=version-13 - base=version-12 + - base=version-14 + - base=version-15 actions: close: comment: diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.json b/erpnext/accounts/doctype/journal_entry/journal_entry.json index 80e72226d3d..80df0ff0dff 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.json +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.json @@ -9,6 +9,7 @@ "engine": "InnoDB", "field_order": [ "entry_type_and_date", + "is_system_generated", "title", "voucher_type", "naming_series", @@ -88,7 +89,7 @@ "label": "Entry Type", "oldfieldname": "voucher_type", "oldfieldtype": "Select", - "options": "Journal Entry\nInter Company Journal Entry\nBank Entry\nCash Entry\nCredit Card Entry\nDebit Note\nCredit Note\nContra Entry\nExcise Entry\nWrite Off Entry\nOpening Entry\nDepreciation Entry\nExchange Rate Revaluation\nExchange Gain Or Loss\nDeferred Revenue\nDeferred Expense", + "options": "Journal Entry\nInter Company Journal Entry\nBank Entry\nCash Entry\nCredit Card Entry\nDebit Note\nCredit Note\nContra Entry\nExcise Entry\nWrite Off Entry\nOpening Entry\nDepreciation Entry\nExchange Rate Revaluation\nExchange Gain Or Loss\nDeferred Revenue\nDeferred Expense\nReversal Of ITC", "reqd": 1, "search_index": 1 }, @@ -533,57 +534,28 @@ "label": "Process Deferred Accounting", "options": "Process Deferred Accounting", "read_only": 1 + }, + { + "default": "0", + "depends_on": "eval:doc.is_system_generated == 1;", + "fieldname": "is_system_generated", + "fieldtype": "Check", + "label": "Is System Generated", + "no_copy": 1, + "read_only": 1 } ], "icon": "fa fa-file-text", "idx": 176, "is_submittable": 1, "links": [], - "modified": "2023-03-01 14:58:59.286591", + "modified": "2023-08-10 14:32:22.366895", "modified_by": "Administrator", "module": "Accounts", "name": "Journal Entry", "naming_rule": "By \"Naming Series\" field", "owner": "Administrator", - "permissions": [ - { - "amend": 1, - "cancel": 1, - "create": 1, - "delete": 1, - "email": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "Accounts User", - "share": 1, - "submit": 1, - "write": 1 - }, - { - "amend": 1, - "cancel": 1, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "import": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "Accounts Manager", - "share": 1, - "submit": 1, - "write": 1 - }, - { - "email": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "Auditor" - } - ], + "permissions": [], "search_fields": "voucher_type,posting_date, due_date, cheque_no", "sort_field": "modified", "sort_order": "DESC", diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py index 22e092c0d04..85ef6f76d28 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py @@ -797,6 +797,9 @@ class JournalEntry(AccountsController): def create_remarks(self): r = [] + if self.flags.skip_remarks_creation: + return + if self.user_remark: r.append(_("Note: {0}").format(self.user_remark)) diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py index ea06e0ec9ae..3a9e80a9d94 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py +++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py @@ -6,7 +6,7 @@ import frappe from frappe import _, msgprint, qb from frappe.model.document import Document from frappe.query_builder.custom import ConstantColumn -from frappe.utils import flt, get_link_to_form, getdate, nowdate, today +from frappe.utils import flt, fmt_money, get_link_to_form, getdate, nowdate, today import erpnext from erpnext.accounts.doctype.process_payment_reconciliation.process_payment_reconciliation import ( @@ -657,6 +657,7 @@ def reconcile_dr_cr_note(dr_cr_notes, company): "reference_name": inv.against_voucher, "cost_center": erpnext.get_default_cost_center(company), "exchange_rate": inv.exchange_rate, + "user_remark": f"{fmt_money(flt(inv.allocated_amount), currency=company_currency)} against {inv.against_voucher}", }, { "account": inv.account, @@ -671,6 +672,7 @@ def reconcile_dr_cr_note(dr_cr_notes, company): "reference_name": inv.voucher_no, "cost_center": erpnext.get_default_cost_center(company), "exchange_rate": inv.exchange_rate, + "user_remark": f"{fmt_money(flt(inv.allocated_amount), currency=company_currency)} from {inv.voucher_no}", }, ], } @@ -678,6 +680,9 @@ def reconcile_dr_cr_note(dr_cr_notes, company): jv.flags.ignore_mandatory = True jv.flags.ignore_exchange_rate = True + jv.remark = None + jv.flags.skip_remarks_creation = True + jv.is_system_generated = True jv.submit() if inv.difference_amount != 0: diff --git a/erpnext/accounts/doctype/process_payment_reconciliation/process_payment_reconciliation.json b/erpnext/accounts/doctype/process_payment_reconciliation/process_payment_reconciliation.json index 8bb7092dc50..1a1ab4d800e 100644 --- a/erpnext/accounts/doctype/process_payment_reconciliation/process_payment_reconciliation.json +++ b/erpnext/accounts/doctype/process_payment_reconciliation/process_payment_reconciliation.json @@ -146,7 +146,7 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2023-04-21 17:19:30.912953", + "modified": "2023-08-11 10:56:51.699137", "modified_by": "Administrator", "module": "Accounts", "name": "Process Payment Reconciliation", @@ -154,15 +154,25 @@ "owner": "Administrator", "permissions": [ { + "amend": 1, + "cancel": 1, "create": 1, "delete": 1, - "email": 1, - "export": 1, - "print": 1, "read": 1, - "report": 1, - "role": "System Manager", + "role": "Accounts Manager", "share": 1, + "submit": 1, + "write": 1 + }, + { + "amend": 1, + "cancel": 1, + "create": 1, + "delete": 1, + "read": 1, + "role": "Accounts User", + "share": 1, + "submit": 1, "write": 1 } ], diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index db120740dcc..0bc5aa2ed2d 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -1654,15 +1654,13 @@ class SalesInvoice(SellingController): frappe.db.set_value("Customer", self.customer, "loyalty_program_tier", lp_details.tier_name) def get_returned_amount(self): - from frappe.query_builder.functions import Coalesce, Sum + from frappe.query_builder.functions import Sum doc = frappe.qb.DocType(self.doctype) returned_amount = ( frappe.qb.from_(doc) .select(Sum(doc.grand_total)) - .where( - (doc.docstatus == 1) & (doc.is_return == 1) & (Coalesce(doc.return_against, "") == self.name) - ) + .where((doc.docstatus == 1) & (doc.is_return == 1) & (doc.return_against == self.name)) ).run() return abs(returned_amount[0][0]) if returned_amount[0][0] else 0 diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py index 895c314510a..0d67752ba74 100644 --- a/erpnext/accounts/party.py +++ b/erpnext/accounts/party.py @@ -707,6 +707,7 @@ def get_payment_terms_template(party_name, party_type, company=None): if party_type not in ("Customer", "Supplier"): return template = None + if party_type == "Customer": customer = frappe.get_cached_value( "Customer", party_name, fieldname=["payment_terms", "customer_group"], as_dict=1 diff --git a/erpnext/accounts/report/financial_statements.py b/erpnext/accounts/report/financial_statements.py index a76dea6a523..6b2341cef13 100644 --- a/erpnext/accounts/report/financial_statements.py +++ b/erpnext/accounts/report/financial_statements.py @@ -335,12 +335,10 @@ def add_total_row(out, root_type, balance_must_be, period_list, company_currency for period in period_list: total_row.setdefault(period.key, 0.0) total_row[period.key] += row.get(period.key, 0.0) - row[period.key] = row.get(period.key, 0.0) total_row.setdefault("total", 0.0) total_row["total"] += flt(row["total"]) total_row["opening_balance"] += row["opening_balance"] - row["total"] = "" if "total" in total_row: out.append(total_row) diff --git a/erpnext/accounts/workspace/accounting/accounting.json b/erpnext/accounts/workspace/accounting/accounting.json index c27ede29d1b..dfdae1dec69 100644 --- a/erpnext/accounts/workspace/accounting/accounting.json +++ b/erpnext/accounts/workspace/accounting/accounting.json @@ -5,7 +5,7 @@ "label": "Profit and Loss" } ], - "content": "[{\"id\":\"MmUf9abwxg\",\"type\":\"onboarding\",\"data\":{\"onboarding_name\":\"Accounts\",\"col\":12}},{\"id\":\"i0EtSjDAXq\",\"type\":\"chart\",\"data\":{\"chart_name\":\"Profit and Loss\",\"col\":12}},{\"id\":\"X78jcbq1u3\",\"type\":\"spacer\",\"data\":{\"col\":12}},{\"id\":\"vikWSkNm6_\",\"type\":\"header\",\"data\":{\"text\":\"Your Shortcuts\",\"col\":12}},{\"id\":\"pMywM0nhlj\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Chart of Accounts\",\"col\":3}},{\"id\":\"_pRdD6kqUG\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Sales Invoice\",\"col\":3}},{\"id\":\"G984SgVRJN\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Purchase Invoice\",\"col\":3}},{\"id\":\"1ArNvt9qhz\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Journal Entry\",\"col\":3}},{\"id\":\"F9f4I1viNr\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Payment Entry\",\"col\":3}},{\"id\":\"4IBBOIxfqW\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Accounts Receivable\",\"col\":3}},{\"id\":\"El2anpPaFY\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"General Ledger\",\"col\":3}},{\"id\":\"1nwcM9upJo\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Trial Balance\",\"col\":3}},{\"id\":\"OF9WOi1Ppc\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Dashboard\",\"col\":3}},{\"id\":\"iAwpe-Chra\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Learn Accounting\",\"col\":3}},{\"id\":\"B7-uxs8tkU\",\"type\":\"spacer\",\"data\":{\"col\":12}},{\"id\":\"tHb3yxthkR\",\"type\":\"header\",\"data\":{\"text\":\"Reports & Masters\",\"col\":12}},{\"id\":\"DnNtsmxpty\",\"type\":\"card\",\"data\":{\"card_name\":\"Accounting Masters\",\"col\":4}},{\"id\":\"nKKr6fjgjb\",\"type\":\"card\",\"data\":{\"card_name\":\"General Ledger\",\"col\":4}},{\"id\":\"xOHTyD8b5l\",\"type\":\"card\",\"data\":{\"card_name\":\"Accounts Receivable\",\"col\":4}},{\"id\":\"_Cb7C8XdJJ\",\"type\":\"card\",\"data\":{\"card_name\":\"Accounts Payable\",\"col\":4}},{\"id\":\"p7NY6MHe2Y\",\"type\":\"card\",\"data\":{\"card_name\":\"Financial Statements\",\"col\":4}},{\"id\":\"KlqilF5R_V\",\"type\":\"card\",\"data\":{\"card_name\":\"Taxes\",\"col\":4}},{\"id\":\"jTUy8LB0uw\",\"type\":\"card\",\"data\":{\"card_name\":\"Cost Center and Budgeting\",\"col\":4}},{\"id\":\"Wn2lhs7WLn\",\"type\":\"card\",\"data\":{\"card_name\":\"Multi Currency\",\"col\":4}},{\"id\":\"PAQMqqNkBM\",\"type\":\"card\",\"data\":{\"card_name\":\"Bank Statement\",\"col\":4}},{\"id\":\"Q_hBCnSeJY\",\"type\":\"card\",\"data\":{\"card_name\":\"Reports\",\"col\":4}},{\"id\":\"3AK1Zf0oew\",\"type\":\"card\",\"data\":{\"card_name\":\"Profitability\",\"col\":4}},{\"id\":\"kxhoaiqdLq\",\"type\":\"card\",\"data\":{\"card_name\":\"Opening and Closing\",\"col\":4}},{\"id\":\"q0MAlU2j_Z\",\"type\":\"card\",\"data\":{\"card_name\":\"Subscription Management\",\"col\":4}},{\"id\":\"ptm7T6Hwu-\",\"type\":\"card\",\"data\":{\"card_name\":\"Share Management\",\"col\":4}},{\"id\":\"OX7lZHbiTr\",\"type\":\"card\",\"data\":{\"card_name\":\"Settings\",\"col\":4}}]", + "content": "[{\"id\":\"MmUf9abwxg\",\"type\":\"onboarding\",\"data\":{\"onboarding_name\":\"Accounts\",\"col\":12}},{\"id\":\"VVvJ1lUcfc\",\"type\":\"number_card\",\"data\":{\"number_card_name\":\"Total Outgoing Bills\",\"col\":3}},{\"id\":\"Vlj2FZtlHV\",\"type\":\"number_card\",\"data\":{\"number_card_name\":\"Total Incoming Bills\",\"col\":3}},{\"id\":\"VVVjQVAhPf\",\"type\":\"number_card\",\"data\":{\"number_card_name\":\"Total Incoming Payment\",\"col\":3}},{\"id\":\"DySNdlysIW\",\"type\":\"number_card\",\"data\":{\"number_card_name\":\"Total Outgoing Payment\",\"col\":3}},{\"id\":\"i0EtSjDAXq\",\"type\":\"chart\",\"data\":{\"chart_name\":\"Profit and Loss\",\"col\":12}},{\"id\":\"X78jcbq1u3\",\"type\":\"spacer\",\"data\":{\"col\":12}},{\"id\":\"vikWSkNm6_\",\"type\":\"header\",\"data\":{\"text\":\"Your Shortcuts\",\"col\":12}},{\"id\":\"pMywM0nhlj\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Chart of Accounts\",\"col\":3}},{\"id\":\"_pRdD6kqUG\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Sales Invoice\",\"col\":3}},{\"id\":\"G984SgVRJN\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Purchase Invoice\",\"col\":3}},{\"id\":\"1ArNvt9qhz\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Journal Entry\",\"col\":3}},{\"id\":\"F9f4I1viNr\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Payment Entry\",\"col\":3}},{\"id\":\"4IBBOIxfqW\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Accounts Receivable\",\"col\":3}},{\"id\":\"El2anpPaFY\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"General Ledger\",\"col\":3}},{\"id\":\"1nwcM9upJo\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Trial Balance\",\"col\":3}},{\"id\":\"OF9WOi1Ppc\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Dashboard\",\"col\":3}},{\"id\":\"iAwpe-Chra\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Learn Accounting\",\"col\":3}},{\"id\":\"B7-uxs8tkU\",\"type\":\"spacer\",\"data\":{\"col\":12}},{\"id\":\"tHb3yxthkR\",\"type\":\"header\",\"data\":{\"text\":\"Reports & Masters\",\"col\":12}},{\"id\":\"DnNtsmxpty\",\"type\":\"card\",\"data\":{\"card_name\":\"Accounting Masters\",\"col\":4}},{\"id\":\"nKKr6fjgjb\",\"type\":\"card\",\"data\":{\"card_name\":\"General Ledger\",\"col\":4}},{\"id\":\"xOHTyD8b5l\",\"type\":\"card\",\"data\":{\"card_name\":\"Accounts Receivable\",\"col\":4}},{\"id\":\"_Cb7C8XdJJ\",\"type\":\"card\",\"data\":{\"card_name\":\"Accounts Payable\",\"col\":4}},{\"id\":\"p7NY6MHe2Y\",\"type\":\"card\",\"data\":{\"card_name\":\"Financial Statements\",\"col\":4}},{\"id\":\"KlqilF5R_V\",\"type\":\"card\",\"data\":{\"card_name\":\"Taxes\",\"col\":4}},{\"id\":\"jTUy8LB0uw\",\"type\":\"card\",\"data\":{\"card_name\":\"Cost Center and Budgeting\",\"col\":4}},{\"id\":\"Wn2lhs7WLn\",\"type\":\"card\",\"data\":{\"card_name\":\"Multi Currency\",\"col\":4}},{\"id\":\"PAQMqqNkBM\",\"type\":\"card\",\"data\":{\"card_name\":\"Bank Statement\",\"col\":4}},{\"id\":\"Q_hBCnSeJY\",\"type\":\"card\",\"data\":{\"card_name\":\"Reports\",\"col\":4}},{\"id\":\"3AK1Zf0oew\",\"type\":\"card\",\"data\":{\"card_name\":\"Profitability\",\"col\":4}},{\"id\":\"kxhoaiqdLq\",\"type\":\"card\",\"data\":{\"card_name\":\"Opening and Closing\",\"col\":4}},{\"id\":\"q0MAlU2j_Z\",\"type\":\"card\",\"data\":{\"card_name\":\"Subscription Management\",\"col\":4}},{\"id\":\"ptm7T6Hwu-\",\"type\":\"card\",\"data\":{\"card_name\":\"Share Management\",\"col\":4}},{\"id\":\"OX7lZHbiTr\",\"type\":\"card\",\"data\":{\"card_name\":\"Settings\",\"col\":4}}]", "creation": "2020-03-02 15:41:59.515192", "custom_blocks": [], "docstatus": 0, @@ -1061,11 +1061,28 @@ "type": "Link" } ], - "modified": "2023-07-04 14:32:15.842044", + "modified": "2023-08-10 17:41:14.059005", "modified_by": "Administrator", "module": "Accounts", "name": "Accounting", - "number_cards": [], + "number_cards": [ + { + "label": "Total Outgoing Bills", + "number_card_name": "Total Outgoing Bills" + }, + { + "label": "Total Incoming Bills", + "number_card_name": "Total Incoming Bills" + }, + { + "label": "Total Incoming Payment", + "number_card_name": "Total Incoming Payment" + }, + { + "label": "Total Outgoing Payment", + "number_card_name": "Total Outgoing Payment" + } + ], "owner": "Administrator", "parent_page": "", "public": 1, diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index 04ec7be3cd8..2060c6ca835 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -478,7 +478,9 @@ class Asset(AccountsController): @frappe.whitelist() def get_manual_depreciation_entries(self): - (_, _, depreciation_expense_account) = get_depreciation_accounts(self) + (_, _, depreciation_expense_account) = get_depreciation_accounts( + self.asset_category, self.company + ) gle = frappe.qb.DocType("GL Entry") @@ -821,10 +823,10 @@ def get_asset_account(account_name, asset=None, asset_category=None, company=Non def make_journal_entry(asset_name): asset = frappe.get_doc("Asset", asset_name) ( - fixed_asset_account, + _, accumulated_depreciation_account, depreciation_expense_account, - ) = get_depreciation_accounts(asset) + ) = get_depreciation_accounts(asset.asset_category, asset.company) depreciation_cost_center, depreciation_series = frappe.get_cached_value( "Company", asset.company, ["depreciation_cost_center", "series_for_depreciation_entry"] diff --git a/erpnext/assets/doctype/asset/depreciation.py b/erpnext/assets/doctype/asset/depreciation.py index 0588065d396..e2a4b2909a5 100644 --- a/erpnext/assets/doctype/asset/depreciation.py +++ b/erpnext/assets/doctype/asset/depreciation.py @@ -4,6 +4,8 @@ import frappe from frappe import _ +from frappe.query_builder import Order +from frappe.query_builder.functions import Max, Min from frappe.utils import ( add_months, cint, @@ -43,11 +45,48 @@ def post_depreciation_entries(date=None): failed_asset_names = [] error_log_names = [] - for asset_name in get_depreciable_assets(date): - asset_doc = frappe.get_doc("Asset", asset_name) + depreciable_asset_depr_schedules_data = get_depreciable_asset_depr_schedules_data(date) + + credit_and_debit_accounts_for_asset_category_and_company = {} + depreciation_cost_center_and_depreciation_series_for_company = ( + get_depreciation_cost_center_and_depreciation_series_for_company() + ) + + accounting_dimensions = get_checks_for_pl_and_bs_accounts() + + for asset_depr_schedule_data in depreciable_asset_depr_schedules_data: + ( + asset_depr_schedule_name, + asset_name, + asset_category, + asset_company, + sch_start_idx, + sch_end_idx, + ) = asset_depr_schedule_data + + if ( + asset_category, + asset_company, + ) not in credit_and_debit_accounts_for_asset_category_and_company: + credit_and_debit_accounts_for_asset_category_and_company.update( + { + (asset_category, asset_company): get_credit_and_debit_accounts_for_asset_category_and_company( + asset_category, asset_company + ), + } + ) try: - make_depreciation_entry_for_all_asset_depr_schedules(asset_doc, date) + make_depreciation_entry( + asset_depr_schedule_name, + date, + sch_start_idx, + sch_end_idx, + credit_and_debit_accounts_for_asset_category_and_company[(asset_category, asset_company)], + depreciation_cost_center_and_depreciation_series_for_company[asset_company], + accounting_dimensions, + ) + frappe.db.commit() except Exception as e: frappe.db.rollback() @@ -62,18 +101,36 @@ def post_depreciation_entries(date=None): frappe.db.commit() -def get_depreciable_assets(date): - return frappe.db.sql_list( - """select distinct a.name - from tabAsset a, `tabAsset Depreciation Schedule` ads, `tabDepreciation Schedule` ds - where a.name = ads.asset and ads.name = ds.parent and a.docstatus=1 and ads.docstatus=1 - and a.status in ('Submitted', 'Partially Depreciated') - and a.calculate_depreciation = 1 - and ds.schedule_date<=%s - and ifnull(ds.journal_entry, '')=''""", - date, +def get_depreciable_asset_depr_schedules_data(date): + a = frappe.qb.DocType("Asset") + ads = frappe.qb.DocType("Asset Depreciation Schedule") + ds = frappe.qb.DocType("Depreciation Schedule") + + res = ( + frappe.qb.from_(ads) + .join(a) + .on(ads.asset == a.name) + .join(ds) + .on(ads.name == ds.parent) + .select(ads.name, a.name, a.asset_category, a.company, Min(ds.idx) - 1, Max(ds.idx)) + .where(a.calculate_depreciation == 1) + .where(a.docstatus == 1) + .where(ads.docstatus == 1) + .where(a.status.isin(["Submitted", "Partially Depreciated"])) + .where(ds.journal_entry.isnull()) + .where(ds.schedule_date <= date) + .groupby(ads.name) + .orderby(a.creation, order=Order.desc) ) + acc_frozen_upto = get_acc_frozen_upto() + if acc_frozen_upto: + res = res.where(ds.schedule_date > acc_frozen_upto) + + res = res.run() + + return res + def make_depreciation_entry_for_all_asset_depr_schedules(asset_doc, date=None): for row in asset_doc.get("finance_books"): @@ -83,8 +140,60 @@ def make_depreciation_entry_for_all_asset_depr_schedules(asset_doc, date=None): make_depreciation_entry(asset_depr_schedule_name, date) +def get_acc_frozen_upto(): + acc_frozen_upto = frappe.db.get_single_value("Accounts Settings", "acc_frozen_upto") + + if not acc_frozen_upto: + return + + frozen_accounts_modifier = frappe.db.get_single_value( + "Accounts Settings", "frozen_accounts_modifier" + ) + + if frozen_accounts_modifier not in frappe.get_roles() or frappe.session.user == "Administrator": + return getdate(acc_frozen_upto) + + return + + +def get_credit_and_debit_accounts_for_asset_category_and_company(asset_category, company): + ( + _, + accumulated_depreciation_account, + depreciation_expense_account, + ) = get_depreciation_accounts(asset_category, company) + + credit_account, debit_account = get_credit_and_debit_accounts( + accumulated_depreciation_account, depreciation_expense_account + ) + + return (credit_account, debit_account) + + +def get_depreciation_cost_center_and_depreciation_series_for_company(): + company_names = frappe.db.get_all("Company", pluck="name") + + res = {} + + for company_name in company_names: + depreciation_cost_center, depreciation_series = frappe.get_cached_value( + "Company", company_name, ["depreciation_cost_center", "series_for_depreciation_entry"] + ) + res.update({company_name: (depreciation_cost_center, depreciation_series)}) + + return res + + @frappe.whitelist() -def make_depreciation_entry(asset_depr_schedule_name, date=None): +def make_depreciation_entry( + asset_depr_schedule_name, + date=None, + sch_start_idx=None, + sch_end_idx=None, + credit_and_debit_accounts=None, + depreciation_cost_center_and_depreciation_series=None, + accounting_dimensions=None, +): frappe.has_permission("Journal Entry", throw=True) if not date: @@ -92,100 +201,144 @@ def make_depreciation_entry(asset_depr_schedule_name, date=None): asset_depr_schedule_doc = frappe.get_doc("Asset Depreciation Schedule", asset_depr_schedule_name) - asset_name = asset_depr_schedule_doc.asset + asset = frappe.get_doc("Asset", asset_depr_schedule_doc.asset) - asset = frappe.get_doc("Asset", asset_name) - ( - fixed_asset_account, - accumulated_depreciation_account, - depreciation_expense_account, - ) = get_depreciation_accounts(asset) + if credit_and_debit_accounts: + credit_account, debit_account = credit_and_debit_accounts + else: + credit_account, debit_account = get_credit_and_debit_accounts_for_asset_category_and_company( + asset.asset_category, asset.company + ) - depreciation_cost_center, depreciation_series = frappe.get_cached_value( - "Company", asset.company, ["depreciation_cost_center", "series_for_depreciation_entry"] - ) + if depreciation_cost_center_and_depreciation_series: + depreciation_cost_center, depreciation_series = depreciation_cost_center_and_depreciation_series + else: + depreciation_cost_center, depreciation_series = frappe.get_cached_value( + "Company", asset.company, ["depreciation_cost_center", "series_for_depreciation_entry"] + ) depreciation_cost_center = asset.cost_center or depreciation_cost_center - accounting_dimensions = get_checks_for_pl_and_bs_accounts() + if not accounting_dimensions: + accounting_dimensions = get_checks_for_pl_and_bs_accounts() - for d in asset_depr_schedule_doc.get("depreciation_schedule"): - if not d.journal_entry and getdate(d.schedule_date) <= getdate(date): - je = frappe.new_doc("Journal Entry") - je.voucher_type = "Depreciation Entry" - je.naming_series = depreciation_series - je.posting_date = d.schedule_date - je.company = asset.company - je.finance_book = asset_depr_schedule_doc.finance_book - je.remark = "Depreciation Entry against {0} worth {1}".format(asset_name, d.depreciation_amount) + depreciation_posting_error = None - credit_account, debit_account = get_credit_and_debit_accounts( - accumulated_depreciation_account, depreciation_expense_account + for d in asset_depr_schedule_doc.get("depreciation_schedule")[ + sch_start_idx or 0 : sch_end_idx or len(asset_depr_schedule_doc.get("depreciation_schedule")) + ]: + try: + _make_journal_entry_for_depreciation( + asset_depr_schedule_doc, + asset, + date, + d, + sch_start_idx, + sch_end_idx, + depreciation_cost_center, + depreciation_series, + credit_account, + debit_account, + accounting_dimensions, ) - - credit_entry = { - "account": credit_account, - "credit_in_account_currency": d.depreciation_amount, - "reference_type": "Asset", - "reference_name": asset.name, - "cost_center": depreciation_cost_center, - } - - debit_entry = { - "account": debit_account, - "debit_in_account_currency": d.depreciation_amount, - "reference_type": "Asset", - "reference_name": asset.name, - "cost_center": depreciation_cost_center, - } - - for dimension in accounting_dimensions: - if asset.get(dimension["fieldname"]) or dimension.get("mandatory_for_bs"): - credit_entry.update( - { - dimension["fieldname"]: asset.get(dimension["fieldname"]) - or dimension.get("default_dimension") - } - ) - - if asset.get(dimension["fieldname"]) or dimension.get("mandatory_for_pl"): - debit_entry.update( - { - dimension["fieldname"]: asset.get(dimension["fieldname"]) - or dimension.get("default_dimension") - } - ) - - je.append("accounts", credit_entry) - - je.append("accounts", debit_entry) - - je.flags.ignore_permissions = True - je.flags.planned_depr_entry = True - je.save() - - d.db_set("journal_entry", je.name) - - 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") + frappe.db.commit() + except Exception as e: + frappe.db.rollback() + depreciation_posting_error = e asset.set_status() - return asset_depr_schedule_doc + if not depreciation_posting_error: + asset.db_set("depr_entry_posting_status", "Successful") + return asset_depr_schedule_doc + + raise depreciation_posting_error -def get_depreciation_accounts(asset): +def _make_journal_entry_for_depreciation( + asset_depr_schedule_doc, + asset, + date, + depr_schedule, + sch_start_idx, + sch_end_idx, + depreciation_cost_center, + depreciation_series, + credit_account, + debit_account, + accounting_dimensions, +): + if not (sch_start_idx and sch_end_idx) and not ( + not depr_schedule.journal_entry and getdate(depr_schedule.schedule_date) <= getdate(date) + ): + return + + je = frappe.new_doc("Journal Entry") + je.voucher_type = "Depreciation Entry" + je.naming_series = depreciation_series + je.posting_date = depr_schedule.schedule_date + je.company = asset.company + je.finance_book = asset_depr_schedule_doc.finance_book + je.remark = "Depreciation Entry against {0} worth {1}".format( + asset.name, depr_schedule.depreciation_amount + ) + + credit_entry = { + "account": credit_account, + "credit_in_account_currency": depr_schedule.depreciation_amount, + "reference_type": "Asset", + "reference_name": asset.name, + "cost_center": depreciation_cost_center, + } + + debit_entry = { + "account": debit_account, + "debit_in_account_currency": depr_schedule.depreciation_amount, + "reference_type": "Asset", + "reference_name": asset.name, + "cost_center": depreciation_cost_center, + } + + for dimension in accounting_dimensions: + if asset.get(dimension["fieldname"]) or dimension.get("mandatory_for_bs"): + credit_entry.update( + { + dimension["fieldname"]: asset.get(dimension["fieldname"]) + or dimension.get("default_dimension") + } + ) + + if asset.get(dimension["fieldname"]) or dimension.get("mandatory_for_pl"): + debit_entry.update( + { + dimension["fieldname"]: asset.get(dimension["fieldname"]) + or dimension.get("default_dimension") + } + ) + + je.append("accounts", credit_entry) + je.append("accounts", debit_entry) + + je.flags.ignore_permissions = True + je.flags.planned_depr_entry = True + je.save() + + depr_schedule.db_set("journal_entry", je.name) + + 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 -= depr_schedule.depreciation_amount + row.db_update() + + +def get_depreciation_accounts(asset_category, company): fixed_asset_account = accumulated_depreciation_account = depreciation_expense_account = None accounts = frappe.db.get_value( "Asset Category Account", - filters={"parent": asset.asset_category, "company_name": asset.company}, + filters={"parent": asset_category, "company_name": company}, fieldname=[ "fixed_asset_account", "accumulated_depreciation_account", @@ -201,7 +354,7 @@ def get_depreciation_accounts(asset): if not accumulated_depreciation_account or not depreciation_expense_account: accounts = frappe.get_cached_value( - "Company", asset.company, ["accumulated_depreciation_account", "depreciation_expense_account"] + "Company", company, ["accumulated_depreciation_account", "depreciation_expense_account"] ) if not accumulated_depreciation_account: @@ -216,7 +369,7 @@ def get_depreciation_accounts(asset): ): frappe.throw( _("Please set Depreciation related Accounts in Asset Category {0} or Company {1}").format( - asset.asset_category, asset.company + asset_category, company ) ) @@ -565,8 +718,8 @@ def get_gl_entries_on_asset_disposal( def get_asset_details(asset, finance_book=None): - fixed_asset_account, accumulated_depr_account, depr_expense_account = get_depreciation_accounts( - asset + fixed_asset_account, accumulated_depr_account, _ = get_depreciation_accounts( + asset.asset_category, asset.company ) disposal_account, depreciation_cost_center = get_disposal_account_and_cost_center(asset.company) depreciation_cost_center = asset.cost_center or depreciation_cost_center diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py index 2a74f20e1bf..cd66f1d136a 100644 --- a/erpnext/assets/doctype/asset/test_asset.py +++ b/erpnext/assets/doctype/asset/test_asset.py @@ -754,6 +754,40 @@ class TestDepreciationMethods(AssetSetup): self.assertEqual(schedules, expected_schedules) + def test_schedule_for_straight_line_method_with_daily_depreciation(self): + asset = create_asset( + calculate_depreciation=1, + available_for_use_date="2023-01-01", + purchase_date="2023-01-01", + gross_purchase_amount=12000, + depreciation_start_date="2023-01-31", + total_number_of_depreciations=12, + frequency_of_depreciation=1, + daily_depreciation=1, + ) + + expected_schedules = [ + ["2023-01-31", 1019.18, 1019.18], + ["2023-02-28", 920.55, 1939.73], + ["2023-03-31", 1019.18, 2958.91], + ["2023-04-30", 986.3, 3945.21], + ["2023-05-31", 1019.18, 4964.39], + ["2023-06-30", 986.3, 5950.69], + ["2023-07-31", 1019.18, 6969.87], + ["2023-08-31", 1019.18, 7989.05], + ["2023-09-30", 986.3, 8975.35], + ["2023-10-31", 1019.18, 9994.53], + ["2023-11-30", 986.3, 10980.83], + ["2023-12-31", 1019.17, 12000.0], + ] + + schedules = [ + [cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount] + for d in get_depr_schedule(asset.name, "Draft") + ] + + self.assertEqual(schedules, expected_schedules) + def test_schedule_for_straight_line_method_for_existing_asset(self): asset = create_asset( calculate_depreciation=1, @@ -1724,6 +1758,7 @@ def create_asset(**args): "total_number_of_depreciations": args.total_number_of_depreciations or 5, "expected_value_after_useful_life": args.expected_value_after_useful_life or 0, "depreciation_start_date": args.depreciation_start_date, + "daily_depreciation": args.daily_depreciation or 0, }, ) diff --git a/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.json b/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.json index d38508d0c42..3772ef4d683 100644 --- a/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.json +++ b/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.json @@ -19,6 +19,7 @@ "depreciation_method", "total_number_of_depreciations", "rate_of_depreciation", + "daily_depreciation", "column_break_8", "frequency_of_depreciation", "expected_value_after_useful_life", @@ -174,12 +175,21 @@ "label": "Number of Depreciations Booked", "print_hide": 1, "read_only": 1 + }, + { + "default": "0", + "depends_on": "eval:doc.depreciation_method == \"Straight Line\" || doc.depreciation_method == \"Manual\"", + "fieldname": "daily_depreciation", + "fieldtype": "Check", + "label": "Daily Depreciation", + "print_hide": 1, + "read_only": 1 } ], "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2023-02-26 16:37:23.734806", + "modified": "2023-08-10 22:22:09.722968", "modified_by": "Administrator", "module": "Assets", "name": "Asset Depreciation Schedule", diff --git a/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py b/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py index e616665ad1f..39ebd4ec0ec 100644 --- a/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py +++ b/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py @@ -153,6 +153,7 @@ class AssetDepreciationSchedule(Document): self.frequency_of_depreciation = row.frequency_of_depreciation self.rate_of_depreciation = row.rate_of_depreciation self.expected_value_after_useful_life = row.expected_value_after_useful_life + self.daily_depreciation = row.daily_depreciation self.status = "Draft" def make_depr_schedule( @@ -499,29 +500,36 @@ def get_total_days(date, frequency): return date_diff(date, period_start_date) -@erpnext.allow_regional def get_depreciation_amount( asset, depreciable_value, - row, + fb_row, schedule_idx=0, prev_depreciation_amount=0, has_wdv_or_dd_non_yearly_pro_rata=False, ): - if row.depreciation_method in ("Straight Line", "Manual"): - return get_straight_line_or_manual_depr_amount(asset, row) + if fb_row.depreciation_method in ("Straight Line", "Manual"): + return get_straight_line_or_manual_depr_amount(asset, fb_row, schedule_idx) else: + rate_of_depreciation = get_updated_rate_of_depreciation_for_wdv_and_dd( + asset, depreciable_value, fb_row + ) return get_wdv_or_dd_depr_amount( depreciable_value, - row.rate_of_depreciation, - row.frequency_of_depreciation, + rate_of_depreciation, + fb_row.frequency_of_depreciation, schedule_idx, prev_depreciation_amount, has_wdv_or_dd_non_yearly_pro_rata, ) -def get_straight_line_or_manual_depr_amount(asset, row): +@erpnext.allow_regional +def get_updated_rate_of_depreciation_for_wdv_and_dd(asset, depreciable_value, fb_row): + return fb_row.rate_of_depreciation + + +def get_straight_line_or_manual_depr_amount(asset, row, schedule_idx): # if the Depreciation Schedule is being modified after Asset Repair due to increase in asset life and value if asset.flags.increase_in_asset_life: return (flt(row.value_after_depreciation) - flt(row.expected_value_after_useful_life)) / ( @@ -534,11 +542,30 @@ def get_straight_line_or_manual_depr_amount(asset, row): ) # if the Depreciation Schedule is being prepared for the first time else: - return ( - flt(asset.gross_purchase_amount) - - flt(asset.opening_accumulated_depreciation) - - flt(row.expected_value_after_useful_life) - ) / flt(row.total_number_of_depreciations - asset.number_of_depreciations_booked) + if row.daily_depreciation: + daily_depr_amount = ( + flt(asset.gross_purchase_amount) + - flt(asset.opening_accumulated_depreciation) + - flt(row.expected_value_after_useful_life) + ) / date_diff( + add_months( + row.depreciation_start_date, + flt(row.total_number_of_depreciations - asset.number_of_depreciations_booked) + * row.frequency_of_depreciation, + ), + row.depreciation_start_date, + ) + to_date = add_months(row.depreciation_start_date, schedule_idx * row.frequency_of_depreciation) + from_date = add_months( + row.depreciation_start_date, (schedule_idx - 1) * row.frequency_of_depreciation + ) + return daily_depr_amount * date_diff(to_date, from_date) + else: + return ( + flt(asset.gross_purchase_amount) + - flt(asset.opening_accumulated_depreciation) + - flt(row.expected_value_after_useful_life) + ) / flt(row.total_number_of_depreciations - asset.number_of_depreciations_booked) def get_wdv_or_dd_depr_amount( diff --git a/erpnext/assets/doctype/asset_finance_book/asset_finance_book.json b/erpnext/assets/doctype/asset_finance_book/asset_finance_book.json index e5a5f194c1b..4121302c1e9 100644 --- a/erpnext/assets/doctype/asset_finance_book/asset_finance_book.json +++ b/erpnext/assets/doctype/asset_finance_book/asset_finance_book.json @@ -8,6 +8,7 @@ "finance_book", "depreciation_method", "total_number_of_depreciations", + "daily_depreciation", "column_break_5", "frequency_of_depreciation", "depreciation_start_date", @@ -17,6 +18,7 @@ ], "fields": [ { + "columns": 2, "fieldname": "finance_book", "fieldtype": "Link", "in_list_view": 1, @@ -32,6 +34,7 @@ "reqd": 1 }, { + "columns": 2, "fieldname": "total_number_of_depreciations", "fieldtype": "Int", "in_list_view": 1, @@ -43,6 +46,7 @@ "fieldtype": "Column Break" }, { + "columns": 2, "fieldname": "frequency_of_depreciation", "fieldtype": "Int", "in_list_view": 1, @@ -57,6 +61,7 @@ "mandatory_depends_on": "eval:parent.doctype == 'Asset'" }, { + "columns": 1, "default": "0", "depends_on": "eval:parent.doctype == 'Asset'", "fieldname": "expected_value_after_useful_life", @@ -79,12 +84,19 @@ "fieldname": "rate_of_depreciation", "fieldtype": "Percent", "label": "Rate of Depreciation" + }, + { + "default": "0", + "depends_on": "eval:doc.depreciation_method == \"Straight Line\" || doc.depreciation_method == \"Manual\"", + "fieldname": "daily_depreciation", + "fieldtype": "Check", + "label": "Daily Depreciation" } ], "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2021-06-17 12:59:05.743683", + "modified": "2023-08-10 22:10:36.576199", "modified_by": "Administrator", "module": "Assets", "name": "Asset Finance Book", @@ -93,5 +105,6 @@ "quick_entry": 1, "sort_field": "modified", "sort_order": "DESC", + "states": [], "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.py b/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.py index a1f047352c4..823b6e9e21d 100644 --- a/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.py +++ b/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.py @@ -64,10 +64,10 @@ class AssetValueAdjustment(Document): def make_depreciation_entry(self): asset = frappe.get_doc("Asset", self.asset) ( - fixed_asset_account, + _, accumulated_depreciation_account, depreciation_expense_account, - ) = get_depreciation_accounts(asset) + ) = get_depreciation_accounts(asset.asset_category, asset.company) depreciation_cost_center, depreciation_series = frappe.get_cached_value( "Company", asset.company, ["depreciation_cost_center", "series_for_depreciation_entry"] @@ -78,9 +78,7 @@ class AssetValueAdjustment(Document): je.naming_series = depreciation_series je.posting_date = self.date je.company = self.company - je.remark = _("Depreciation Entry against {0} worth {1}").format( - self.asset, self.difference_amount - ) + je.remark = "Depreciation Entry against {0} worth {1}".format(self.asset, self.difference_amount) je.finance_book = self.finance_book credit_entry = { 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 94c77ea517c..bf62a8fb39c 100644 --- a/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py +++ b/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py @@ -7,13 +7,14 @@ from itertools import chain import frappe from frappe import _ from frappe.query_builder.functions import IfNull, Sum -from frappe.utils import cstr, flt, formatdate, getdate +from frappe.utils import add_months, cstr, flt, formatdate, getdate, nowdate, today from erpnext.accounts.report.financial_statements import ( get_fiscal_year_data, get_period_list, validate_fiscal_year, ) +from erpnext.accounts.utils import get_fiscal_year from erpnext.assets.doctype.asset.asset import get_asset_value_after_depreciation @@ -37,15 +38,26 @@ def get_conditions(filters): if filters.get("company"): conditions["company"] = filters.company + if filters.filter_based_on == "Date Range": + if not filters.from_date and not filters.to_date: + filters.from_date = add_months(nowdate(), -12) + filters.to_date = nowdate() + conditions[date_field] = ["between", [filters.from_date, filters.to_date]] - if filters.filter_based_on == "Fiscal Year": + elif filters.filter_based_on == "Fiscal Year": + if not filters.from_fiscal_year and not filters.to_fiscal_year: + default_fiscal_year = get_fiscal_year(today())[0] + filters.from_fiscal_year = default_fiscal_year + filters.to_fiscal_year = default_fiscal_year + fiscal_year = get_fiscal_year_data(filters.from_fiscal_year, filters.to_fiscal_year) validate_fiscal_year(fiscal_year, filters.from_fiscal_year, filters.to_fiscal_year) filters.year_start_date = getdate(fiscal_year.year_start_date) filters.year_end_date = getdate(fiscal_year.year_end_date) conditions[date_field] = ["between", [filters.year_start_date, filters.year_end_date]] + if filters.get("only_existing_assets"): conditions["is_existing_asset"] = filters.get("only_existing_assets") if filters.get("asset_category"): diff --git a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.json b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.json index 158334f9632..fbfc1ac1693 100644 --- a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.json +++ b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.json @@ -273,7 +273,9 @@ "fieldname": "html_llwp", "fieldtype": "HTML", "options": "
In your Email Template, you can use the following special variables:\n
\n{{ update_password_link }}: A link where your supplier can set a new password to log into your portal.\n {{ portal_link }}: A link to this RFQ in your supplier portal.\n {{ supplier_name }}: The company name of your supplier.\n {{ contact.salutation }} {{ contact.last_name }}: The contact person of your supplier.\n {{ user_fullname }}: Your full name.\n Apart from these, you can access all values in this RFQ, like {{ message_for_supplier }} or {{ terms }}.
+ ${message_string} +
+ + + + + +