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\n

\n

Apart from these, you can access all values in this RFQ, like {{ message_for_supplier }} or {{ terms }}.

", - "read_only": 1 + "print_hide": 1, + "read_only": 1, + "report_hide": 1 }, { "default": "1", @@ -287,7 +289,7 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2023-07-27 16:41:48.468873", + "modified": "2023-08-08 16:30:10.870429", "modified_by": "Administrator", "module": "Buying", "name": "Request for Quotation", diff --git a/erpnext/controllers/print_settings.py b/erpnext/controllers/print_settings.py index d86607d8dbc..59f13c6dfaf 100644 --- a/erpnext/controllers/print_settings.py +++ b/erpnext/controllers/print_settings.py @@ -14,9 +14,6 @@ def set_print_templates_for_item_table(doc, settings): } } - if doc.meta.get_field("items"): - doc.meta.get_field("items").hide_in_print_layout = ["uom", "stock_uom"] - doc.flags.compact_item_fields = ["description", "qty", "rate", "amount"] if settings.compact_item_print: diff --git a/erpnext/controllers/status_updater.py b/erpnext/controllers/status_updater.py index a4bc4a9c69e..f3663cc5271 100644 --- a/erpnext/controllers/status_updater.py +++ b/erpnext/controllers/status_updater.py @@ -233,7 +233,7 @@ class StatusUpdater(Document): if hasattr(d, "qty") and d.qty > 0 and self.get("is_return"): frappe.throw(_("For an item {0}, quantity must be negative number").format(d.item_code)) - if hasattr(d, "item_code") and hasattr(d, "rate") and d.rate < 0: + if hasattr(d, "item_code") and hasattr(d, "rate") and flt(d.rate) < 0: frappe.throw(_("For an item {0}, rate must be a positive number").format(d.item_code)) if d.doctype == args["source_dt"] and d.get(args["join_field"]): diff --git a/erpnext/hooks.py b/erpnext/hooks.py index d5eb4644064..7eaa146db65 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -70,6 +70,19 @@ treeviews = [ "Department", ] +demo_master_doctypes = [ + "item_group", + "item", + "customer_group", + "supplier_group", + "customer", + "supplier", +] +demo_transaction_doctypes = [ + "purchase_order", + "sales_order", +] + jinja = { "methods": [ "erpnext.stock.serial_batch_bundle.get_serial_or_batch_nos", @@ -429,7 +442,6 @@ scheduler_events = { "erpnext.controllers.accounts_controller.update_invoice_status", "erpnext.accounts.doctype.fiscal_year.fiscal_year.auto_create_fiscal_year", "erpnext.projects.doctype.task.task.set_tasks_as_overdue", - "erpnext.assets.doctype.asset.depreciation.post_depreciation_entries", "erpnext.stock.doctype.serial_no.serial_no.update_maintenance_status", "erpnext.buying.doctype.supplier_scorecard.supplier_scorecard.refresh_scorecards", "erpnext.setup.doctype.company.company.cache_companies_monthly_sales_history", @@ -454,6 +466,7 @@ scheduler_events = { "erpnext.setup.doctype.email_digest.email_digest.send", "erpnext.manufacturing.doctype.bom_update_tool.bom_update_tool.auto_update_latest_price_in_all_boms", "erpnext.crm.utils.open_leads_opportunities_based_on_todays_event", + "erpnext.assets.doctype.asset.depreciation.post_depreciation_entries", ], "monthly_long": [ "erpnext.accounts.deferred_revenue.process_deferred_accounting", diff --git a/erpnext/patches.txt b/erpnext/patches.txt index d53bacea64d..d035ad6fe7c 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -262,7 +262,6 @@ erpnext.patches.v14_0.update_reference_due_date_in_journal_entry erpnext.patches.v15_0.saudi_depreciation_warning erpnext.patches.v15_0.delete_saudi_doctypes erpnext.patches.v14_0.show_loan_management_deprecation_warning -erpnext.patches.v14_0.update_subscription_details execute:frappe.rename_doc("Report", "TDS Payable Monthly", "Tax Withholding Details", force=True) [post_model_sync] @@ -322,6 +321,7 @@ erpnext.patches.v14_0.create_accounting_dimensions_for_closing_balance erpnext.patches.v14_0.update_closing_balances #14-07-2023 execute:frappe.db.set_single_value("Accounts Settings", "merge_similar_account_heads", 0) erpnext.patches.v14_0.update_reference_type_in_journal_entry_accounts +erpnext.patches.v14_0.update_subscription_details # below migration patches should always run last erpnext.patches.v14_0.migrate_gl_to_payment_ledger execute:frappe.delete_doc_if_exists("Report", "Tax Detail") diff --git a/erpnext/patches/v14_0/update_subscription_details.py b/erpnext/patches/v14_0/update_subscription_details.py index 729ac1895a1..58ab16d39e3 100644 --- a/erpnext/patches/v14_0/update_subscription_details.py +++ b/erpnext/patches/v14_0/update_subscription_details.py @@ -10,7 +10,7 @@ def execute(): frappe.db.set_value( subscription_invoice.document_type, subscription_invoice.invoice, - "Subscription", + "subscription", subscription_invoice.parent, ) diff --git a/erpnext/public/build.json b/erpnext/public/build.json deleted file mode 100644 index b9b48aba453..00000000000 --- a/erpnext/public/build.json +++ /dev/null @@ -1,68 +0,0 @@ -{ - "css/erpnext.css": [ - "public/less/erpnext.less", - "public/scss/call_popup.scss", - "public/scss/point-of-sale.scss" - ], - "js/erpnext-web.min.js": [ - "public/js/website_utils.js", - "public/js/shopping_cart.js", - "public/js/wishlist.js" - ], - "css/erpnext-web.css": [ - "public/scss/website.scss", - "public/scss/shopping_cart.scss" - ], - "js/erpnext.min.js": [ - "public/js/conf.js", - "public/js/utils.js", - "public/js/queries.js", - "public/js/sms_manager.js", - "public/js/utils/party.js", - "public/js/controllers/stock_controller.js", - "public/js/payment/payments.js", - "public/js/controllers/taxes_and_totals.js", - "public/js/controllers/transaction.js", - "public/js/templates/item_selector.html", - "public/js/utils/item_selector.js", - "public/js/help_links.js", - "public/js/templates/item_quick_entry.html", - "public/js/utils/customer_quick_entry.js", - "public/js/utils/supplier_quick_entry.js", - "public/js/education/student_button.html", - "public/js/education/assessment_result_tool.html", - "public/js/call_popup/call_popup.js", - "public/js/utils/dimension_tree_filter.js", - "public/js/telephony.js", - "public/js/templates/call_link.html", - "public/js/bulk_transaction_processing.js" - ], - "js/item-dashboard.min.js": [ - "stock/dashboard/item_dashboard.html", - "stock/dashboard/item_dashboard_list.html", - "stock/dashboard/item_dashboard.js", - "stock/page/warehouse_capacity_summary/warehouse_capacity_summary.html", - "stock/page/warehouse_capacity_summary/warehouse_capacity_summary_header.html" - ], - "js/point-of-sale.min.js": [ - "selling/page/point_of_sale/pos_item_selector.js", - "selling/page/point_of_sale/pos_item_cart.js", - "selling/page/point_of_sale/pos_item_details.js", - "selling/page/point_of_sale/pos_number_pad.js", - "selling/page/point_of_sale/pos_payment.js", - "selling/page/point_of_sale/pos_past_order_list.js", - "selling/page/point_of_sale/pos_past_order_summary.js", - "selling/page/point_of_sale/pos_controller.js" - ], - "js/bank-reconciliation-tool.min.js": [ - "public/js/bank_reconciliation_tool/data_table_manager.js", - "public/js/bank_reconciliation_tool/number_card.js", - "public/js/bank_reconciliation_tool/dialog_manager.js" - ], - "js/e-commerce.min.js": [ - "e_commerce/product_ui/views.js", - "e_commerce/product_ui/grid.js", - "e_commerce/product_ui/list.js", - "e_commerce/product_ui/search.js" - ] -} diff --git a/erpnext/public/js/erpnext.bundle.js b/erpnext/public/js/erpnext.bundle.js index d7bea7b8341..966a9e1f9b3 100644 --- a/erpnext/public/js/erpnext.bundle.js +++ b/erpnext/public/js/erpnext.bundle.js @@ -28,5 +28,6 @@ import "./controllers/accounts.js" import "./utils/landed_taxes_and_charges_common.js"; import "./utils/sales_common.js"; import "./controllers/buying.js"; +import "./utils/demo.js"; // import { sum } from 'frappe/public/utils/util.js' diff --git a/erpnext/public/js/setup_wizard.js b/erpnext/public/js/setup_wizard.js index 934fd1f88ae..ba200ef1681 100644 --- a/erpnext/public/js/setup_wizard.js +++ b/erpnext/public/js/setup_wizard.js @@ -40,6 +40,12 @@ erpnext.setup.slides_settings = [ { fieldname: 'fy_start_date', label: __('Financial Year Begins On'), fieldtype: 'Date', reqd: 1 }, // end date should be hidden (auto calculated) { fieldname: 'fy_end_date', label: __('End Date'), fieldtype: 'Date', reqd: 1, hidden: 1 }, + { fieldtype: "Section Break" }, + { + fieldname: 'setup_demo', + label: __('Generate Demo Data for Exploration'), + fieldtype: 'Check', + description: 'If checked, we will create demo data for you to explore the system. This demo data can be erased later.'}, ], onload: function (slide) { diff --git a/erpnext/public/js/utils/demo.js b/erpnext/public/js/utils/demo.js new file mode 100644 index 00000000000..b59c4762e00 --- /dev/null +++ b/erpnext/public/js/utils/demo.js @@ -0,0 +1,91 @@ +$(document).on("toolbar_setup", function () { + if (frappe.boot.sysdefaults.demo_company) { + render_clear_demo_button(); + } + + // for first load after setup. + frappe.realtime.on("demo_data_complete", () => { + render_clear_demo_button(); + }); +}); + +function render_clear_demo_button() { + let wait_for_onboaring_tours = setInterval(() => { + if ($("#driver-page-overlay").length) { + return; + } + setup_clear_demo_button(); + clearInterval(wait_for_onboaring_tours); + }, 2000); +} + +function setup_clear_demo_button() { + let message_string = __( + "Demo data is present on the system, erase data before starting real usage." + ); + let $floatingBar = $(` +
+
+

+ ${message_string} +

+ + + + + + + +
+
+ `); + + $("footer").append($floatingBar); + + $("#clear-demo").on("click", function () { + frappe.confirm( + __("Are you sure you want to clear all demo data?"), + () => { + frappe.call({ + method: "erpnext.setup.demo.clear_demo_data", + freeze: true, + freeze_message: __("Clearing Demo Data..."), + callback: function (r) { + frappe.ui.toolbar.clear_cache(); + frappe.show_alert({ + message: __("Demo data cleared"), + indicator: "green", + }); + $("footer").remove($floatingBar); + }, + }); + } + ); + }); + + $("#dismiss-demo-banner").on("click", function () { + $floatingBar.remove(); + }); +} diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py index ca669f630a4..cc141ffa111 100755 --- a/erpnext/selling/doctype/sales_order/sales_order.py +++ b/erpnext/selling/doctype/sales_order/sales_order.py @@ -108,18 +108,26 @@ class SalesOrder(SellingController): and customer = %s", (self.po_no, self.name, self.customer), ) - if ( - so - and so[0][0] - and not cint( + if so and so[0][0]: + if cint( frappe.db.get_single_value("Selling Settings", "allow_against_multiple_purchase_orders") - ) - ): - frappe.msgprint( - _("Warning: Sales Order {0} already exists against Customer's Purchase Order {1}").format( - so[0][0], self.po_no + ): + frappe.msgprint( + _("Warning: Sales Order {0} already exists against Customer's Purchase Order {1}").format( + frappe.bold(so[0][0]), frappe.bold(self.po_no) + ) + ) + else: + frappe.throw( + _( + "Sales Order {0} already exists against Customer's Purchase Order {1}. To allow multiple Sales Orders, Enable {2} in {3}" + ).format( + frappe.bold(so[0][0]), + frappe.bold(self.po_no), + frappe.bold(_("'Allow Multiple Sales Orders Against a Customer's Purchase Order'")), + get_link_to_form("Selling Settings", "Selling Settings"), + ) ) - ) def validate_for_items(self): for d in self.get("items"): diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py index c85a4fb2f0d..954393f5730 100644 --- a/erpnext/selling/doctype/sales_order/test_sales_order.py +++ b/erpnext/selling/doctype/sales_order/test_sales_order.py @@ -2055,7 +2055,7 @@ def make_sales_order(**args): so.company = args.company or "_Test Company" so.customer = args.customer or "_Test Customer" so.currency = args.currency or "INR" - so.po_no = args.po_no or "12345" + so.po_no = args.po_no or "" if args.selling_price_list: so.selling_price_list = args.selling_price_list diff --git a/erpnext/selling/doctype/selling_settings/selling_settings.json b/erpnext/selling/doctype/selling_settings/selling_settings.json index 045227f0aa4..f3b9f6f3b06 100644 --- a/erpnext/selling/doctype/selling_settings/selling_settings.json +++ b/erpnext/selling/doctype/selling_settings/selling_settings.json @@ -85,7 +85,7 @@ "fieldname": "sales_update_frequency", "fieldtype": "Select", "label": "Sales Update Frequency in Company and Project", - "options": "Each Transaction\nDaily\nMonthly", + "options": "Monthly\nEach Transaction\nDaily", "reqd": 1 }, { @@ -200,7 +200,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2023-02-04 12:37:53.380857", + "modified": "2023-08-09 15:35:42.914354", "modified_by": "Administrator", "module": "Selling", "name": "Selling Settings", @@ -229,4 +229,4 @@ "sort_order": "DESC", "states": [], "track_changes": 1 -} +} \ No newline at end of file diff --git a/erpnext/setup/demo.py b/erpnext/setup/demo.py new file mode 100644 index 00000000000..1c19974fce6 --- /dev/null +++ b/erpnext/setup/demo.py @@ -0,0 +1,202 @@ +# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors +# License: GNU General Public License v3. See license.txt + +import json +import os +from random import randint + +import frappe +from frappe import _ +from frappe.utils import add_days, getdate + +from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry +from erpnext.accounts.utils import get_fiscal_year +from erpnext.buying.doctype.purchase_order.purchase_order import make_purchase_invoice +from erpnext.selling.doctype.sales_order.sales_order import make_sales_invoice +from erpnext.setup.setup_wizard.operations.install_fixtures import create_bank_account + + +def setup_demo_data(): + from frappe.utils.telemetry import capture + + capture("demo_data_creation_started", "erpnext") + try: + company = create_demo_company() + process_masters() + make_transactions(company) + frappe.cache.delete_keys("bootinfo") + frappe.publish_realtime("demo_data_complete") + except Exception: + frappe.log_error("Failed to create demo data") + capture("demo_data_creation_failed", "erpnext", properties={"exception": frappe.get_traceback()}) + raise + capture("demo_data_creation_completed", "erpnext") + + +@frappe.whitelist() +def clear_demo_data(): + from frappe.utils.telemetry import capture + + frappe.only_for("System Manager") + + capture("demo_data_erased", "erpnext") + try: + company = frappe.db.get_single_value("Global Defaults", "demo_company") + create_transaction_deletion_record(company) + clear_masters() + delete_company(company) + default_company = frappe.db.get_single_value("Global Defaults", "default_company") + frappe.db.set_default("company", default_company) + except Exception: + frappe.db.rollback() + frappe.log_error("Failed to erase demo data") + frappe.throw( + _("Failed to erase demo data, please delete the demo company manually."), + title=_("Could Not Delete Demo Data"), + ) + + +def create_demo_company(): + company = frappe.db.get_all("Company")[0].name + company_doc = frappe.get_doc("Company", company) + + # Make a dummy company + new_company = frappe.new_doc("Company") + new_company.company_name = company_doc.company_name + " (Demo)" + new_company.abbr = company_doc.abbr + "D" + new_company.enable_perpetual_inventory = 1 + new_company.default_currency = company_doc.default_currency + new_company.country = company_doc.country + new_company.chart_of_accounts_based_on = "Standard Template" + new_company.chart_of_accounts = company_doc.chart_of_accounts + new_company.insert() + + # Set Demo Company as default to + frappe.db.set_single_value("Global Defaults", "demo_company", new_company.name) + frappe.db.set_default("company", new_company.name) + + bank_account = create_bank_account({"company_name": new_company.name}) + frappe.db.set_value("Company", new_company.name, "default_bank_account", bank_account.name) + + return new_company.name + + +def process_masters(): + for doctype in frappe.get_hooks("demo_master_doctypes"): + data = read_data_file_using_hooks(doctype) + if data: + for item in json.loads(data): + create_demo_record(item) + + +def create_demo_record(doctype): + frappe.get_doc(doctype).insert(ignore_permissions=True) + + +def make_transactions(company): + frappe.db.set_single_value("Stock Settings", "allow_negative_stock", 1) + start_date = get_fiscal_year(date=getdate())[1] + + for doctype in frappe.get_hooks("demo_transaction_doctypes"): + data = read_data_file_using_hooks(doctype) + if data: + for item in json.loads(data): + create_transaction(item, company, start_date) + + convert_order_to_invoices() + frappe.db.set_single_value("Stock Settings", "allow_negative_stock", 0) + + +def create_transaction(doctype, company, start_date): + document_type = doctype.get("doctype") + warehouse = get_warehouse(company) + + if document_type == "Purchase Order": + posting_date = get_random_date(start_date, 1, 30) + else: + posting_date = get_random_date(start_date, 31, 365) + + doctype.update( + { + "company": company, + "set_posting_time": 1, + "transaction_date": posting_date, + "schedule_date": posting_date, + "delivery_date": posting_date, + "set_warehouse": warehouse, + } + ) + + doc = frappe.get_doc(doctype) + doc.save(ignore_permissions=True) + doc.submit() + + +def convert_order_to_invoices(): + for document in ["Purchase Order", "Sales Order"]: + # Keep some orders intentionally unbilled/unpaid + for i, order in enumerate( + frappe.db.get_all( + document, filters={"docstatus": 1}, fields=["name", "transaction_date"], limit=6 + ) + ): + + if document == "Purchase Order": + invoice = make_purchase_invoice(order.name) + elif document == "Sales Order": + invoice = make_sales_invoice(order.name) + + invoice.set_posting_time = 1 + invoice.posting_date = order.transaction_date + invoice.due_date = order.transaction_date + invoice.update_stock = 1 + invoice.submit() + + if i % 2 != 0: + payment = get_payment_entry(invoice.doctype, invoice.name) + payment.reference_no = invoice.name + payment.submit() + + +def get_random_date(start_date, start_range, end_range): + return add_days(start_date, randint(start_range, end_range)) + + +def create_transaction_deletion_record(company): + transaction_deletion_record = frappe.new_doc("Transaction Deletion Record") + transaction_deletion_record.company = company + transaction_deletion_record.save(ignore_permissions=True) + transaction_deletion_record.submit() + + +def clear_masters(): + for doctype in frappe.get_hooks("demo_master_doctypes")[::-1]: + data = read_data_file_using_hooks(doctype) + if data: + for item in json.loads(data): + clear_demo_record(item) + + +def clear_demo_record(document): + document_type = document.get("doctype") + del document["doctype"] + doc = frappe.get_doc(document_type, document) + frappe.delete_doc(doc.doctype, doc.name, ignore_permissions=True) + + +def delete_company(company): + frappe.db.set_single_value("Global Defaults", "demo_company", "") + frappe.delete_doc("Company", company, ignore_permissions=True) + + +def read_data_file_using_hooks(doctype): + path = os.path.join(os.path.dirname(__file__), "demo_data") + with open(os.path.join(path, doctype + ".json"), "r") as f: + data = f.read() + + return data + + +def get_warehouse(company): + warehouses = frappe.db.get_all("Warehouse", {"company": company, "is_group": 0}) + return warehouses[randint(0, 3)].name diff --git a/erpnext/setup/demo_data/customer.json b/erpnext/setup/demo_data/customer.json new file mode 100644 index 00000000000..1b47906eb6c --- /dev/null +++ b/erpnext/setup/demo_data/customer.json @@ -0,0 +1,20 @@ +[ + { + "doctype": "Customer", + "customer_group": "Demo Customer Group", + "territory": "All Territories", + "customer_name": "Grant Plastics Ltd." + }, + { + "doctype": "Customer", + "customer_group": "Demo Customer Group", + "territory": "All Territories", + "customer_name": "West View Software Ltd." + }, + { + "doctype": "Customer", + "customer_group": "Demo Customer Group", + "territory": "All Territories", + "customer_name": "Palmer Productions Ltd." + } +] \ No newline at end of file diff --git a/erpnext/setup/demo_data/customer_group.json b/erpnext/setup/demo_data/customer_group.json new file mode 100644 index 00000000000..754333548a8 --- /dev/null +++ b/erpnext/setup/demo_data/customer_group.json @@ -0,0 +1,6 @@ +[ + { + "doctype": "Customer Group", + "customer_group_name": "Demo Customer Group" + } +] \ No newline at end of file diff --git a/erpnext/setup/demo_data/item.json b/erpnext/setup/demo_data/item.json new file mode 100644 index 00000000000..ffe41528af5 --- /dev/null +++ b/erpnext/setup/demo_data/item.json @@ -0,0 +1,72 @@ +[ + { + "doctype": "Item", + "item_group": "Demo Item Group", + "item_code": "SKU001", + "item_name": "T-shirt", + "image": "https://images.pexels.com/photos/1484808/pexels-photo-1484808.jpeg" + }, + { + "doctype": "Item", + "item_group": "Demo Item Group", + "item_code": "SKU002", + "item_name": "Laptop", + "image": "https://images.pexels.com/photos/3999538/pexels-photo-3999538.jpeg" + }, + { + "doctype": "Item", + "item_group": "Demo Item Group", + "item_code": "SKU003", + "item_name": "Book", + "image": "https://images.pexels.com/photos/2422178/pexels-photo-2422178.jpeg" + }, + { + "doctype": "Item", + "item_group": "Demo Item Group", + "item_code": "SKU004", + "item_name": "Smartphone", + "image": "https://images.pexels.com/photos/1647976/pexels-photo-1647976.jpeg" + }, + { + "doctype": "Item", + "item_group": "Demo Item Group", + "item_code": "SKU005", + "item_name": "Sneakers", + "image": "https://images.pexels.com/photos/1598505/pexels-photo-1598505.jpeg" + }, + { + "doctype": "Item", + "item_group": "Demo Item Group", + "item_code": "SKU006", + "item_name": "Coffee Mug", + "image": "https://images.pexels.com/photos/585753/pexels-photo-585753.jpeg" + }, + { + "doctype": "Item", + "item_group": "Demo Item Group", + "item_code": "SKU007", + "item_name": "Television", + "image": "https://images.pexels.com/photos/8059376/pexels-photo-8059376.jpeg" + }, + { + "doctype": "Item", + "item_group": "Demo Item Group", + "item_code": "SKU008", + "item_name": "Backpack", + "image": "https://images.pexels.com/photos/3731256/pexels-photo-3731256.jpeg" + }, + { + "doctype": "Item", + "item_group": "Demo Item Group", + "item_code": "SKU009", + "item_name": "Headphones", + "image": "https://images.pexels.com/photos/3587478/pexels-photo-3587478.jpeg" + }, + { + "doctype": "Item", + "item_group": "Demo Item Group", + "item_code": "SKU010", + "item_name": "Camera", + "image": "https://images.pexels.com/photos/51383/photo-camera-subject-photographer-51383.jpeg" + } +] diff --git a/erpnext/setup/demo_data/item_group.json b/erpnext/setup/demo_data/item_group.json new file mode 100644 index 00000000000..acb261f4ed5 --- /dev/null +++ b/erpnext/setup/demo_data/item_group.json @@ -0,0 +1,7 @@ +[ + { + "doctype": "Item Group", + "item_group_name": "Demo Item Group", + "parent_item_group": "All Item Groups" + } +] \ No newline at end of file diff --git a/erpnext/setup/demo_data/journal_entry.json b/erpnext/setup/demo_data/journal_entry.json new file mode 100644 index 00000000000..b751c7cf244 --- /dev/null +++ b/erpnext/setup/demo_data/journal_entry.json @@ -0,0 +1,25 @@ +[ + { + "cheque_date": "2023-03-14", + "cheque_no": "33", + "doctype": "Journal Entry", + "accounts": [ + { + "party_type": "Customer", + "party": "ABC Enterprises", + "credit_in_account_currency": 40000.0, + "debit_in_account_currency": 0.0, + "doctype": "Journal Entry Account", + "parentfield": "accounts", + }, + { + "credit_in_account_currency": 0.0, + "debit_in_account_currency": 40000.0, + "doctype": "Journal Entry Account", + "parentfield": "accounts", + } + ], + "user_remark": "test", + "voucher_type": "Bank Entry" + } +] \ No newline at end of file diff --git a/erpnext/setup/demo_data/payment_entry.json b/erpnext/setup/demo_data/payment_entry.json new file mode 100644 index 00000000000..c0767c3114d --- /dev/null +++ b/erpnext/setup/demo_data/payment_entry.json @@ -0,0 +1,57 @@ +[ + { + "doctype": "Payment Entry", + "payment_type": "Receive", + "party_type": "Customer", + "party": "ABC Enterprises", + "paid_amount": 67000, + "received_amount": 67000, + "reference_no": "#ref0001", + "source_exchange_rate": 1, + "target_exchange_rate": 1 + }, + { + "doctype": "Payment Entry", + "payment_type": "Receive", + "party_type": "Customer", + "party": "XYZ Corporation", + "paid_amount": 500000, + "received_amount": 500000, + "reference_no": "#ref0001", + "source_exchange_rate": 1, + "target_exchange_rate": 1 + }, + { + "doctype": "Payment Entry", + "payment_type": "Receive", + "party_type": "Customer", + "party": "KJPR Pvt. Ltd.", + "paid_amount": 300000, + "received_amount": 30000, + "reference_no": "#ref0001", + "source_exchange_rate": 1, + "target_exchange_rate": 1 + }, + { + "doctype": "Payment Entry", + "payment_type": "Pay", + "party_type": "Supplier", + "party": "DQ Industries", + "paid_amount": 85000, + "received_amount": 85000, + "reference_no": "#ref0005", + "source_exchange_rate": 1, + "target_exchange_rate": 1 + }, + { + "doctype": "Payment Entry", + "payment_type": "Pay", + "party_type": "Supplier", + "party": "KC Corp.", + "paid_amount": 100000, + "received_amount": 100000, + "reference_no": "#ref0006", + "source_exchange_rate": 1, + "target_exchange_rate": 1 + } +] \ No newline at end of file diff --git a/erpnext/setup/demo_data/purchase_order.json b/erpnext/setup/demo_data/purchase_order.json new file mode 100644 index 00000000000..42ffa8862a9 --- /dev/null +++ b/erpnext/setup/demo_data/purchase_order.json @@ -0,0 +1,162 @@ +[ + { + "conversion_rate": 1.0, + "supplier": "Zuckerman Security Ltd.", + "doctype": "Purchase Order", + "update_stock": 1, + "items": [ + { + "doctype": "Purchase Order Item", + "item_code": "SKU001", + "parentfield": "items", + "qty": 100.0, + "rate": 400.0, + "conversion_factor": 1 + } + ] + }, + { + "conversion_rate": 1.0, + "supplier": "MA Inc.", + "doctype": "Purchase Order", + "update_stock": 1, + "items": [ + { + "doctype": "Purchase Order Item", + "item_code": "SKU002", + "parentfield": "items", + "qty": 50.0, + "rate": 300.0, + "conversion_factor": 1 + } + ] + }, + { + "conversion_rate": 1.0, + "supplier": "Summit Traders Ltd.", + "doctype": "Purchase Order", + "update_stock": 1, + "items": [ + { + "doctype": "Purchase Order Item", + "item_code": "SKU003", + "parentfield": "items", + "qty": 200.0, + "rate": 523.0, + "conversion_factor": 1 + } + ] + }, + { + "conversion_rate": 1.0, + "supplier": "Zuckerman Security Ltd.", + "doctype": "Purchase Order", + "update_stock": 1, + "items": [ + { + "doctype": "Purchase Order Item", + "item_code": "SKU004", + "parentfield": "items", + "qty": 60.0, + "rate": 725.0, + "conversion_factor": 1 + } + ] + }, + { + "conversion_rate": 1.0, + "supplier": "MA Inc.", + "doctype": "Purchase Order", + "update_stock": 1, + "items": [ + { + "doctype": "Purchase Order Item", + "item_code": "SKU005", + "parentfield": "items", + "qty": 182.0, + "rate": 222.0, + "conversion_factor": 1 + } + ] + }, + { + "conversion_rate": 1.0, + "supplier": "Summit Traders Ltd.", + "doctype": "Purchase Order", + "update_stock": 1, + "items": [ + { + "doctype": "Purchase Order Item", + "item_code": "SKU006", + "parentfield": "items", + "qty": 250.0, + "rate": 420.0, + "conversion_factor": 1 + } + ] + }, + { + "conversion_rate": 1.0, + "supplier": "Zuckerman Security Ltd.", + "doctype": "Purchase Order", + "update_stock": 1, + "items": [ + { + "doctype": "Purchase Order Item", + "item_code": "SKU007", + "parentfield": "items", + "qty": 190.0, + "rate": 375.0, + "conversion_factor": 1 + } + ] + }, + { + "conversion_rate": 1.0, + "supplier": "MA Inc.", + "doctype": "Purchase Order", + "update_stock": 1, + "items": [ + { + "doctype": "Purchase Order Item", + "item_code": "SKU008", + "parentfield": "items", + "qty": 121.0, + "rate": 333.0, + "conversion_factor": 1 + } + ] + }, + { + "conversion_rate": 1.0, + "supplier": "Summit Traders Ltd.", + "doctype": "Purchase Order", + "update_stock": 1, + "items": [ + { + "doctype": "Purchase Order Item", + "item_code": "SKU009", + "parentfield": "items", + "qty": 76.0, + "rate": 700.0, + "conversion_factor": 1 + } + ] + }, + { + "conversion_rate": 1.0, + "supplier": "Zuckerman Security Ltd.", + "doctype": "Purchase Order", + "update_stock": 1, + "items": [ + { + "doctype": "Purchase Order Item", + "item_code": "SKU010", + "parentfield": "items", + "qty": 78.0, + "rate": 500.0, + "conversion_factor": 1 + } + ] + } +] \ No newline at end of file diff --git a/erpnext/setup/demo_data/sales_order.json b/erpnext/setup/demo_data/sales_order.json new file mode 100644 index 00000000000..d39063723ee --- /dev/null +++ b/erpnext/setup/demo_data/sales_order.json @@ -0,0 +1,122 @@ +[ + { + "conversion_rate": 1.0, + "customer": "Grant Plastics Ltd.", + "doctype": "Sales Order", + "update_stock": 1, + "items": [ + { + "doctype": "Sales Order Item", + "item_code": "SKU004", + "parentfield": "items", + "qty": 20.0, + "rate": 1000.0, + "conversion_factor": 1 + } + ] + }, + { + "conversion_rate": 1.0, + "customer": "West View Software Ltd.", + "doctype": "Sales Order", + "update_stock": 1, + "items": [ + { + "doctype": "Sales Order Item", + "item_code": "SKU001", + "parentfield": "items", + "qty": 25.0, + "rate": 800.0, + "conversion_factor": 1 + }, + { + "doctype": "Sales Order Item", + "item_code": "SKU002", + "parentfield": "items", + "qty": 15.0, + "rate": 800.0, + "conversion_factor": 1 + } + ] + }, + { + "conversion_rate": 1.0, + "customer": "West View Software Ltd.", + "doctype": "Sales Order", + "update_stock": 1, + "items": [ + { + "doctype": "Sales Order Item", + "item_code": "SKU003", + "parentfield": "items", + "qty": 100, + "rate": 500.0, + "conversion_factor": 1 + }, + { + "doctype": "Sales Order Item", + "item_code": "SKU006", + "parentfield": "items", + "qty": 100, + "rate": 890.0, + "conversion_factor": 1 + }, + { + "doctype": "Sales Order Item", + "item_code": "SKU007", + "parentfield": "items", + "qty": 100, + "rate": 900.0, + "conversion_factor": 1 + } + ] + }, + { + "conversion_rate": 1.0, + "customer": "Palmer Productions Ltd.", + "doctype": "Sales Order", + "update_stock": 1, + "items": [ + { + "doctype": "Sales Order Item", + "item_code": "SKU005", + "parentfield": "items", + "qty": 150.0, + "rate": 100.0, + "conversion_factor": 1 + } + ] + }, + { + "conversion_rate": 1.0, + "customer": "Grant Plastics Ltd.", + "doctype": "Sales Order", + "update_stock": 1, + "items": [ + { + "doctype": "Sales Order Item", + "item_code": "SKU008", + "parentfield": "items", + "qty": 20.0, + "rate": 500.0, + "conversion_factor": 1 + }, + { + "doctype": "Sales Order Item", + "item_code": "SKU009", + "parentfield": "items", + "qty": 40.0, + "rate": 300.0, + "conversion_factor": 1 + }, + { + "doctype": "Sales Order Item", + "item_code": "SKU010", + "parentfield": "items", + "qty": 50.0, + "rate": 900.0, + "conversion_factor": 1 + } + ] + } +] \ No newline at end of file diff --git a/erpnext/setup/demo_data/supplier.json b/erpnext/setup/demo_data/supplier.json new file mode 100644 index 00000000000..01a4e8969a4 --- /dev/null +++ b/erpnext/setup/demo_data/supplier.json @@ -0,0 +1,17 @@ +[ + { + "doctype": "Supplier", + "supplier_group": "Demo Supplier Group", + "supplier_name": "Zuckerman Security Ltd." + }, + { + "doctype": "Supplier", + "supplier_group": "Demo Supplier Group", + "supplier_name": "MA Inc." + }, + { + "doctype": "Supplier", + "supplier_group": "Demo Supplier Group", + "supplier_name": "Summit Traders Ltd." + } +] \ No newline at end of file diff --git a/erpnext/setup/demo_data/supplier_group.json b/erpnext/setup/demo_data/supplier_group.json new file mode 100644 index 00000000000..17070bf9b60 --- /dev/null +++ b/erpnext/setup/demo_data/supplier_group.json @@ -0,0 +1,6 @@ +[ + { + "doctype": "Supplier Group", + "supplier_group_name": "Demo Supplier Group" + } +] \ No newline at end of file diff --git a/erpnext/setup/doctype/company/company.js b/erpnext/setup/doctype/company/company.js index f4682c1b806..fa207ecdd1c 100644 --- a/erpnext/setup/doctype/company/company.js +++ b/erpnext/setup/doctype/company/company.js @@ -18,6 +18,7 @@ frappe.ui.form.on("Company", { }); }, setup: function(frm) { + frm.__rename_queue = "long"; erpnext.company.setup_queries(frm); frm.set_query("parent_company", function() { diff --git a/erpnext/setup/doctype/company/test_company.py b/erpnext/setup/doctype/company/test_company.py index fd2fe300fac..babd7dd4e84 100644 --- a/erpnext/setup/doctype/company/test_company.py +++ b/erpnext/setup/doctype/company/test_company.py @@ -195,6 +195,22 @@ class TestCompany(unittest.TestCase): child_company.save() self.test_basic_tree() + def test_demo_data(self): + from erpnext.setup.demo import clear_demo_data, setup_demo_data + + setup_demo_data() + company_name = frappe.db.get_value("Company", {"name": ("like", "%(Demo)")}) + self.assertTrue(company_name) + + for transaction in frappe.get_hooks("demo_transaction_doctypes"): + self.assertTrue(frappe.db.exists(frappe.unscrub(transaction), {"company": company_name})) + + clear_demo_data() + company_name = frappe.db.get_value("Company", {"name": ("like", "%(Demo)")}) + self.assertFalse(company_name) + for transaction in frappe.get_hooks("demo_transaction_doctypes"): + self.assertFalse(frappe.db.exists(frappe.unscrub(transaction), {"company": company_name})) + def create_company_communication(doctype, docname): comm = frappe.get_doc( diff --git a/erpnext/setup/doctype/global_defaults/global_defaults.json b/erpnext/setup/doctype/global_defaults/global_defaults.json index 823d2ba7d7b..bd80e1d8584 100644 --- a/erpnext/setup/doctype/global_defaults/global_defaults.json +++ b/erpnext/setup/doctype/global_defaults/global_defaults.json @@ -12,7 +12,8 @@ "default_currency", "hide_currency_symbol", "disable_rounded_total", - "disable_in_words" + "disable_in_words", + "demo_company" ], "fields": [ { @@ -71,6 +72,14 @@ "fieldtype": "Check", "in_list_view": 1, "label": "Disable In Words" + }, + { + "fieldname": "demo_company", + "fieldtype": "Link", + "hidden": 1, + "label": "Demo Company", + "options": "Company", + "read_only": 1 } ], "icon": "fa fa-cog", diff --git a/erpnext/setup/setup_wizard/operations/install_fixtures.py b/erpnext/setup/setup_wizard/operations/install_fixtures.py index 535c87d652c..ae6881b99e0 100644 --- a/erpnext/setup/setup_wizard/operations/install_fixtures.py +++ b/erpnext/setup/setup_wizard/operations/install_fixtures.py @@ -490,7 +490,7 @@ def update_stock_settings(): def create_bank_account(args): if not args.get("bank_account"): - return + args["bank_account"] = _("Bank Account") company_name = args.get("company_name") bank_account_group = frappe.db.get_value( diff --git a/erpnext/setup/setup_wizard/setup_wizard.py b/erpnext/setup/setup_wizard/setup_wizard.py index 65b268e385c..2da107e4e94 100644 --- a/erpnext/setup/setup_wizard/setup_wizard.py +++ b/erpnext/setup/setup_wizard/setup_wizard.py @@ -5,7 +5,8 @@ import frappe from frappe import _ -from .operations import install_fixtures as fixtures +from erpnext.setup.demo import setup_demo_data +from erpnext.setup.setup_wizard.operations import install_fixtures as fixtures def get_setup_stages(args=None): @@ -36,6 +37,11 @@ def get_setup_stages(args=None): {"fn": setup_defaults, "args": args, "fail_msg": _("Failed to setup defaults")}, ], }, + { + "status": _("Setting up demo data"), + "fail_msg": _("Failed to setup demo data"), + "tasks": [{"fn": setup_demo, "args": args, "fail_msg": _("Failed to setup demo data")}], + }, { "status": _("Wrapping up"), "fail_msg": _("Failed to login"), @@ -63,6 +69,11 @@ def fin(args): login_as_first_user(args) +def setup_demo(args): + if args.get("setup_demo"): + frappe.enqueue(setup_demo_data, enqueue_after_commit=True, at_front=True) + + def login_as_first_user(args): if args.get("email") and hasattr(frappe.local, "login_manager"): frappe.local.login_manager.login_as(args.get("email")) diff --git a/erpnext/startup/boot.py b/erpnext/startup/boot.py index db1cc494e0b..bdbf8b4fac5 100644 --- a/erpnext/startup/boot.py +++ b/erpnext/startup/boot.py @@ -61,6 +61,8 @@ def boot_session(bootinfo): ) bootinfo.party_account_types = frappe._dict(party_account_types) + bootinfo.sysdefaults.demo_company = frappe.db.get_single_value("Global Defaults", "demo_company") + def update_page_info(bootinfo): bootinfo.page_info.update( diff --git a/erpnext/stock/doctype/delivery_note/test_delivery_note.py b/erpnext/stock/doctype/delivery_note/test_delivery_note.py index 0ef3027bce3..48b8ab75042 100644 --- a/erpnext/stock/doctype/delivery_note/test_delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/test_delivery_note.py @@ -703,7 +703,7 @@ class TestDeliveryNote(FrappeTestCase): def test_dn_billing_status_case1(self): # SO -> DN -> SI - so = make_sales_order() + so = make_sales_order(po_no="12345") dn = create_dn_against_so(so.name, delivered_qty=2) self.assertEqual(dn.status, "To Bill") @@ -730,7 +730,7 @@ class TestDeliveryNote(FrappeTestCase): make_sales_invoice, ) - so = make_sales_order() + so = make_sales_order(po_no="12345") si = make_sales_invoice(so.name) si.get("items")[0].qty = 5 @@ -774,7 +774,7 @@ class TestDeliveryNote(FrappeTestCase): frappe.db.set_single_value("Stock Settings", "allow_negative_stock", 1) - so = make_sales_order() + so = make_sales_order(po_no="12345") dn1 = make_delivery_note(so.name) dn1.get("items")[0].qty = 2 @@ -820,7 +820,7 @@ class TestDeliveryNote(FrappeTestCase): from erpnext.accounts.doctype.sales_invoice.sales_invoice import make_delivery_note from erpnext.selling.doctype.sales_order.sales_order import make_sales_invoice - so = make_sales_order() + so = make_sales_order(po_no="12345") si = make_sales_invoice(so.name) si.submit() @@ -1227,6 +1227,7 @@ class TestDeliveryNote(FrappeTestCase): self.assertEqual(get_reserved_qty(item, warehouse), 0 if dont_reserve_qty else qty_to_reserve) def tearDown(self): + frappe.db.rollback() frappe.db.set_single_value("Selling Settings", "dont_reserve_sales_order_qty_on_sales_return", 0) diff --git a/erpnext/stock/report/item_shortage_report/item_shortage_report.py b/erpnext/stock/report/item_shortage_report/item_shortage_report.py index 9fafe91c3f9..4bd9a107e2c 100644 --- a/erpnext/stock/report/item_shortage_report/item_shortage_report.py +++ b/erpnext/stock/report/item_shortage_report/item_shortage_report.py @@ -40,7 +40,12 @@ def get_data(filters): item.item_name, item.description, ) - .where((bin.projected_qty < 0) & (wh.name == bin.warehouse) & (bin.item_code == item.name)) + .where( + (item.disabled == 0) + & (bin.projected_qty < 0) + & (wh.name == bin.warehouse) + & (bin.item_code == item.name) + ) .orderby(bin.projected_qty) )