diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.js b/erpnext/accounts/doctype/journal_entry/journal_entry.js index 8d8cbefa71..35a378856b 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.js +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.js @@ -8,7 +8,7 @@ frappe.provide("erpnext.journal_entry"); frappe.ui.form.on("Journal Entry", { setup: function(frm) { frm.add_fetch("bank_account", "account", "account"); - frm.ignore_doctypes_on_cancel_all = ['Sales Invoice', 'Purchase Invoice', 'Journal Entry', "Repost Payment Ledger", 'Asset', 'Asset Movement', 'Asset Depreciation Schedule']; + frm.ignore_doctypes_on_cancel_all = ['Sales Invoice', 'Purchase Invoice', 'Journal Entry', "Repost Payment Ledger", 'Asset', 'Asset Movement', 'Asset Depreciation Schedule', "Repost Accounting Ledger"]; }, refresh: function(frm) { diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py index 1e1b3ba642..22e092c0d0 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py @@ -96,6 +96,8 @@ class JournalEntry(AccountsController): "Payment Ledger Entry", "Repost Payment Ledger", "Repost Payment Ledger Items", + "Repost Accounting Ledger", + "Repost Accounting Ledger Items", ) self.make_gl_entries(1) self.update_advance_paid() diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js index 33f263433b..f131be2dfe 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.js +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js @@ -9,7 +9,7 @@ erpnext.accounts.taxes.setup_tax_filters("Advance Taxes and Charges"); frappe.ui.form.on('Payment Entry', { onload: function(frm) { - frm.ignore_doctypes_on_cancel_all = ['Sales Invoice', 'Purchase Invoice', "Journal Entry", "Repost Payment Ledger"]; + frm.ignore_doctypes_on_cancel_all = ['Sales Invoice', 'Purchase Invoice', 'Journal Entry', 'Repost Payment Ledger','Repost Accounting Ledger']; if(frm.doc.__islocal) { if (!frm.doc.paid_from) frm.set_value("paid_from_account_currency", null); diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index cc42f9faec..64b4d16708 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -147,6 +147,8 @@ class PaymentEntry(AccountsController): "Payment Ledger Entry", "Repost Payment Ledger", "Repost Payment Ledger Items", + "Repost Accounting Ledger", + "Repost Accounting Ledger Items", ) super(PaymentEntry, self).on_cancel() self.make_gl_entries(cancel=1) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js index 89d62078cc..66438a7efa 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js @@ -35,7 +35,7 @@ erpnext.accounts.PurchaseInvoice = class PurchaseInvoice extends erpnext.buying. super.onload(); // Ignore linked advances - this.frm.ignore_doctypes_on_cancel_all = ['Journal Entry', 'Payment Entry', 'Purchase Invoice', "Repost Payment Ledger"]; + this.frm.ignore_doctypes_on_cancel_all = ['Journal Entry', 'Payment Entry', 'Purchase Invoice', "Repost Payment Ledger", "Repost Accounting Ledger"]; if(!this.frm.doc.__islocal) { // show credit_to in print format diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 7329cecf8e..f33439989a 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -1415,6 +1415,8 @@ class PurchaseInvoice(BuyingController): "Repost Item Valuation", "Repost Payment Ledger", "Repost Payment Ledger Items", + "Repost Accounting Ledger", + "Repost Accounting Ledger Items", "Payment Ledger Entry", "Tax Withheld Vouchers", "Serial and Batch Bundle", diff --git a/erpnext/accounts/doctype/repost_accounting_ledger/__init__.py b/erpnext/accounts/doctype/repost_accounting_ledger/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.html b/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.html new file mode 100644 index 0000000000..2dec8f753f --- /dev/null +++ b/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.html @@ -0,0 +1,44 @@ + + + + + + {% for col in gl_columns%} + + {% endfor %} + + + + {% for col in gl_columns%} + + {% endfor %} + + +{% for gl in gl_data%} +{% if gl["old"]%} + +{% else %} + +{% endif %} + {% for col in gl_columns %} + + {% endfor %} + +{% endfor %} +
{{ col.label }}
+ {{ gl[col.fieldname] }} +
diff --git a/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.js b/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.js new file mode 100644 index 0000000000..3a87a380d1 --- /dev/null +++ b/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.js @@ -0,0 +1,50 @@ +// Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on("Repost Accounting Ledger", { + setup: function(frm) { + frm.fields_dict['vouchers'].grid.get_field('voucher_type').get_query = function(doc) { + return { + filters: { + name: ['in', ['Purchase Invoice', 'Sales Invoice', 'Payment Entry', 'Journal Entry']], + } + } + } + + frm.fields_dict['vouchers'].grid.get_field('voucher_no').get_query = function(doc) { + if (doc.company) { + return { + filters: { + company: doc.company, + docstatus: 1 + } + } + } + } + }, + + refresh: function(frm) { + frm.add_custom_button(__('Show Preview'), () => { + frm.call({ + method: 'generate_preview', + doc: frm.doc, + freeze: true, + freeze_message: __('Generating Preview'), + callback: function(r) { + if (r && r.message) { + let content = r.message; + let opts = { + title: "Preview", + subtitle: "preview", + content: content, + print_settings: {orientation: "landscape"}, + columns: [], + data: [], + } + frappe.render_grid(opts); + } + } + }); + }); + } +}); diff --git a/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.json b/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.json new file mode 100644 index 0000000000..8d56c9bb11 --- /dev/null +++ b/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.json @@ -0,0 +1,81 @@ +{ + "actions": [], + "allow_rename": 1, + "autoname": "format:ACC-REPOST-{#####}", + "creation": "2023-07-04 13:07:32.923675", + "default_view": "List", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "company", + "column_break_vpup", + "delete_cancelled_entries", + "section_break_metl", + "vouchers", + "amended_from" + ], + "fields": [ + { + "fieldname": "company", + "fieldtype": "Link", + "label": "Company", + "options": "Company" + }, + { + "fieldname": "amended_from", + "fieldtype": "Link", + "label": "Amended From", + "no_copy": 1, + "options": "Repost Accounting Ledger", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "vouchers", + "fieldtype": "Table", + "label": "Vouchers", + "options": "Repost Accounting Ledger Items" + }, + { + "fieldname": "column_break_vpup", + "fieldtype": "Column Break" + }, + { + "fieldname": "section_break_metl", + "fieldtype": "Section Break" + }, + { + "default": "0", + "fieldname": "delete_cancelled_entries", + "fieldtype": "Check", + "label": "Delete Cancelled Ledger Entries" + } + ], + "index_web_pages_for_search": 1, + "is_submittable": 1, + "links": [], + "modified": "2023-07-27 15:47:58.975034", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Repost Accounting Ledger", + "naming_rule": "Expression", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + } + ], + "sort_field": "modified", + "sort_order": "DESC", + "states": [] +} \ No newline at end of file diff --git a/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.py b/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.py new file mode 100644 index 0000000000..4cf2ed2f46 --- /dev/null +++ b/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.py @@ -0,0 +1,183 @@ +# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +import frappe +from frappe import _, qb +from frappe.model.document import Document +from frappe.utils.data import comma_and + + +class RepostAccountingLedger(Document): + def __init__(self, *args, **kwargs): + super(RepostAccountingLedger, self).__init__(*args, **kwargs) + self._allowed_types = set( + ["Purchase Invoice", "Sales Invoice", "Payment Entry", "Journal Entry"] + ) + + def validate(self): + self.validate_vouchers() + self.validate_for_closed_fiscal_year() + self.validate_for_deferred_accounting() + + def validate_for_deferred_accounting(self): + sales_docs = [x.voucher_no for x in self.vouchers if x.voucher_type == "Sales Invoice"] + docs_with_deferred_revenue = frappe.db.get_all( + "Sales Invoice Item", + filters={"parent": ["in", sales_docs], "docstatus": 1, "enable_deferred_revenue": True}, + fields=["parent"], + as_list=1, + ) + + purchase_docs = [x.voucher_no for x in self.vouchers if x.voucher_type == "Purchase Invoice"] + docs_with_deferred_expense = frappe.db.get_all( + "Purchase Invoice Item", + filters={"parent": ["in", purchase_docs], "docstatus": 1, "enable_deferred_expense": 1}, + fields=["parent"], + as_list=1, + ) + + if docs_with_deferred_revenue or docs_with_deferred_expense: + frappe.throw( + _("Documents: {0} have deferred revenue/expense enabled for them. Cannot repost.").format( + frappe.bold( + comma_and([x[0] for x in docs_with_deferred_expense + docs_with_deferred_revenue]) + ) + ) + ) + + def validate_for_closed_fiscal_year(self): + if self.vouchers: + latest_pcv = ( + frappe.db.get_all( + "Period Closing Voucher", + filters={"company": self.company}, + order_by="posting_date desc", + pluck="posting_date", + limit=1, + ) + or None + ) + if not latest_pcv: + return + + for vtype in self._allowed_types: + if names := [x.voucher_no for x in self.vouchers if x.voucher_type == vtype]: + latest_voucher = frappe.db.get_all( + vtype, + filters={"name": ["in", names]}, + pluck="posting_date", + order_by="posting_date desc", + limit=1, + )[0] + if latest_voucher and latest_pcv[0] >= latest_voucher: + frappe.throw(_("Cannot Resubmit Ledger entries for vouchers in Closed fiscal year.")) + + def validate_vouchers(self): + if self.vouchers: + # Validate voucher types + voucher_types = set([x.voucher_type for x in self.vouchers]) + if disallowed_types := voucher_types.difference(self._allowed_types): + frappe.throw( + _("{0} types are not allowed. Only {1} are.").format( + frappe.bold(comma_and(list(disallowed_types))), + frappe.bold(comma_and(list(self._allowed_types))), + ) + ) + + def get_existing_ledger_entries(self): + vouchers = [x.voucher_no for x in self.vouchers] + gl = qb.DocType("GL Entry") + existing_gles = ( + qb.from_(gl) + .select(gl.star) + .where((gl.voucher_no.isin(vouchers)) & (gl.is_cancelled == 0)) + .run(as_dict=True) + ) + self.gles = frappe._dict({}) + + for gle in existing_gles: + self.gles.setdefault((gle.voucher_type, gle.voucher_no), frappe._dict({})).setdefault( + "existing", [] + ).append(gle.update({"old": True})) + + def generate_preview_data(self): + self.gl_entries = [] + self.get_existing_ledger_entries() + for x in self.vouchers: + doc = frappe.get_doc(x.voucher_type, x.voucher_no) + if doc.doctype in ["Payment Entry", "Journal Entry"]: + gle_map = doc.build_gl_map() + else: + gle_map = doc.get_gl_entries() + + old_entries = self.gles.get((x.voucher_type, x.voucher_no)) + if old_entries: + self.gl_entries.extend(old_entries.existing) + self.gl_entries.extend(gle_map) + + @frappe.whitelist() + def generate_preview(self): + from erpnext.accounts.report.general_ledger.general_ledger import get_columns as get_gl_columns + + gl_columns = [] + gl_data = [] + + self.generate_preview_data() + if self.gl_entries: + filters = {"company": self.company, "include_dimensions": 1} + for x in get_gl_columns(filters): + if x["fieldname"] == "gl_entry": + x["fieldname"] = "name" + gl_columns.append(x) + + gl_data = self.gl_entries + rendered_page = frappe.render_template( + "erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.html", + {"gl_columns": gl_columns, "gl_data": gl_data}, + ) + + return rendered_page + + def on_submit(self): + job_name = "repost_accounting_ledger_" + self.name + frappe.enqueue( + method="erpnext.accounts.doctype.repost_accounting_ledger.repost_accounting_ledger.start_repost", + account_repost_doc=self.name, + is_async=True, + job_name=job_name, + ) + frappe.msgprint(_("Repost has started in the background")) + + +@frappe.whitelist() +def start_repost(account_repost_doc=str) -> None: + if account_repost_doc: + repost_doc = frappe.get_doc("Repost Accounting Ledger", account_repost_doc) + + if repost_doc.docstatus == 1: + # Prevent repost on invoices with deferred accounting + repost_doc.validate_for_deferred_accounting() + + for x in repost_doc.vouchers: + doc = frappe.get_doc(x.voucher_type, x.voucher_no) + + if repost_doc.delete_cancelled_entries: + frappe.db.delete("GL Entry", filters={"voucher_type": doc.doctype, "voucher_no": doc.name}) + frappe.db.delete( + "Payment Ledger Entry", filters={"voucher_type": doc.doctype, "voucher_no": doc.name} + ) + + if doc.doctype in ["Sales Invoice", "Purchase Invoice"]: + if not repost_doc.delete_cancelled_entries: + doc.docstatus = 2 + doc.make_gl_entries_on_cancel() + + doc.docstatus = 1 + doc.make_gl_entries() + + elif doc.doctype in ["Payment Entry", "Journal Entry"]: + if not repost_doc.delete_cancelled_entries: + doc.make_gl_entries(1) + doc.make_gl_entries() + + frappe.db.commit() diff --git a/erpnext/accounts/doctype/repost_accounting_ledger/test_repost_accounting_ledger.py b/erpnext/accounts/doctype/repost_accounting_ledger/test_repost_accounting_ledger.py new file mode 100644 index 0000000000..0e75dd2e3e --- /dev/null +++ b/erpnext/accounts/doctype/repost_accounting_ledger/test_repost_accounting_ledger.py @@ -0,0 +1,202 @@ +# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt + +import frappe +from frappe import qb +from frappe.query_builder.functions import Sum +from frappe.tests.utils import FrappeTestCase, change_settings +from frappe.utils import add_days, nowdate, today + +from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry +from erpnext.accounts.doctype.payment_request.payment_request import make_payment_request +from erpnext.accounts.doctype.repost_accounting_ledger.repost_accounting_ledger import start_repost +from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice +from erpnext.accounts.test.accounts_mixin import AccountsTestMixin +from erpnext.accounts.utils import get_fiscal_year + + +class TestRepostAccountingLedger(AccountsTestMixin, FrappeTestCase): + def setUp(self): + self.create_company() + self.create_customer() + self.create_item() + + def teadDown(self): + frappe.db.rollback() + + def test_01_basic_functions(self): + si = create_sales_invoice( + item=self.item, + company=self.company, + customer=self.customer, + debit_to=self.debit_to, + parent_cost_center=self.cost_center, + cost_center=self.cost_center, + rate=100, + ) + + preq = frappe.get_doc( + make_payment_request( + dt=si.doctype, + dn=si.name, + payment_request_type="Inward", + party_type="Customer", + party=si.customer, + ) + ) + preq.save().submit() + + # Test Validation Error + ral = frappe.new_doc("Repost Accounting Ledger") + ral.company = self.company + ral.delete_cancelled_entries = True + ral.append("vouchers", {"voucher_type": si.doctype, "voucher_no": si.name}) + ral.append( + "vouchers", {"voucher_type": preq.doctype, "voucher_no": preq.name} + ) # this should throw validation error + self.assertRaises(frappe.ValidationError, ral.save) + ral.vouchers.pop() + preq.cancel() + preq.delete() + + pe = get_payment_entry(si.doctype, si.name) + pe.save().submit() + ral.append("vouchers", {"voucher_type": pe.doctype, "voucher_no": pe.name}) + ral.save() + + # manually set an incorrect debit amount in DB + gle = frappe.db.get_all("GL Entry", filters={"voucher_no": si.name, "account": self.debit_to}) + frappe.db.set_value("GL Entry", gle[0], "debit", 90) + + gl = qb.DocType("GL Entry") + res = ( + qb.from_(gl) + .select(gl.voucher_no, Sum(gl.debit).as_("debit"), Sum(gl.credit).as_("credit")) + .where((gl.voucher_no == si.name) & (gl.is_cancelled == 0)) + .run() + ) + + # Assert incorrect ledger balance + self.assertNotEqual(res[0], (si.name, 100, 100)) + + # Submit repost document + ral.save().submit() + + # background jobs don't run on test cases. Manually triggering repost function. + start_repost(ral.name) + + res = ( + qb.from_(gl) + .select(gl.voucher_no, Sum(gl.debit).as_("debit"), Sum(gl.credit).as_("credit")) + .where((gl.voucher_no == si.name) & (gl.is_cancelled == 0)) + .run() + ) + + # Ledger should reflect correct amount post repost + self.assertEqual(res[0], (si.name, 100, 100)) + + def test_02_deferred_accounting_valiations(self): + si = create_sales_invoice( + item=self.item, + company=self.company, + customer=self.customer, + debit_to=self.debit_to, + parent_cost_center=self.cost_center, + cost_center=self.cost_center, + rate=100, + do_not_submit=True, + ) + si.items[0].enable_deferred_revenue = True + si.items[0].deferred_revenue_account = self.deferred_revenue + si.items[0].service_start_date = nowdate() + si.items[0].service_end_date = add_days(nowdate(), 90) + si.save().submit() + + ral = frappe.new_doc("Repost Accounting Ledger") + ral.company = self.company + ral.append("vouchers", {"voucher_type": si.doctype, "voucher_no": si.name}) + self.assertRaises(frappe.ValidationError, ral.save) + + @change_settings("Accounts Settings", {"delete_linked_ledger_entries": 1}) + def test_04_pcv_validation(self): + # Clear old GL entries so PCV can be submitted. + gl = frappe.qb.DocType("GL Entry") + qb.from_(gl).delete().where(gl.company == self.company).run() + + si = create_sales_invoice( + item=self.item, + company=self.company, + customer=self.customer, + debit_to=self.debit_to, + parent_cost_center=self.cost_center, + cost_center=self.cost_center, + rate=100, + ) + pcv = frappe.get_doc( + { + "doctype": "Period Closing Voucher", + "transaction_date": today(), + "posting_date": today(), + "company": self.company, + "fiscal_year": get_fiscal_year(today(), company=self.company)[0], + "cost_center": self.cost_center, + "closing_account_head": self.retained_earnings, + "remarks": "test", + } + ) + pcv.save().submit() + + ral = frappe.new_doc("Repost Accounting Ledger") + ral.company = self.company + ral.append("vouchers", {"voucher_type": si.doctype, "voucher_no": si.name}) + self.assertRaises(frappe.ValidationError, ral.save) + + pcv.reload() + pcv.cancel() + pcv.delete() + + def test_03_deletion_flag_and_preview_function(self): + si = create_sales_invoice( + item=self.item, + company=self.company, + customer=self.customer, + debit_to=self.debit_to, + parent_cost_center=self.cost_center, + cost_center=self.cost_center, + rate=100, + ) + + pe = get_payment_entry(si.doctype, si.name) + pe.save().submit() + + # without deletion flag set + ral = frappe.new_doc("Repost Accounting Ledger") + ral.company = self.company + ral.delete_cancelled_entries = False + ral.append("vouchers", {"voucher_type": si.doctype, "voucher_no": si.name}) + ral.append("vouchers", {"voucher_type": pe.doctype, "voucher_no": pe.name}) + ral.save() + + # assert preview data is generated + preview = ral.generate_preview() + self.assertIsNotNone(preview) + + ral.save().submit() + + # background jobs don't run on test cases. Manually triggering repost function. + start_repost(ral.name) + + self.assertIsNotNone(frappe.db.exists("GL Entry", {"voucher_no": si.name, "is_cancelled": 1})) + self.assertIsNotNone(frappe.db.exists("GL Entry", {"voucher_no": pe.name, "is_cancelled": 1})) + + # with deletion flag set + ral = frappe.new_doc("Repost Accounting Ledger") + ral.company = self.company + ral.delete_cancelled_entries = True + ral.append("vouchers", {"voucher_type": si.doctype, "voucher_no": si.name}) + ral.append("vouchers", {"voucher_type": pe.doctype, "voucher_no": pe.name}) + ral.save().submit() + + start_repost(ral.name) + self.assertIsNone(frappe.db.exists("GL Entry", {"voucher_no": si.name, "is_cancelled": 1})) + self.assertIsNone(frappe.db.exists("GL Entry", {"voucher_no": pe.name, "is_cancelled": 1})) diff --git a/erpnext/accounts/doctype/repost_accounting_ledger_items/__init__.py b/erpnext/accounts/doctype/repost_accounting_ledger_items/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/accounts/doctype/repost_accounting_ledger_items/repost_accounting_ledger_items.json b/erpnext/accounts/doctype/repost_accounting_ledger_items/repost_accounting_ledger_items.json new file mode 100644 index 0000000000..4a2041f88c --- /dev/null +++ b/erpnext/accounts/doctype/repost_accounting_ledger_items/repost_accounting_ledger_items.json @@ -0,0 +1,40 @@ +{ + "actions": [], + "allow_rename": 1, + "creation": "2023-07-04 14:14:01.243848", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "voucher_type", + "voucher_no" + ], + "fields": [ + { + "fieldname": "voucher_type", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Voucher Type", + "options": "DocType" + }, + { + "fieldname": "voucher_no", + "fieldtype": "Dynamic Link", + "in_list_view": 1, + "label": "Voucher No", + "options": "voucher_type" + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2023-07-04 14:15:51.165584", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Repost Accounting Ledger Items", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "states": [] +} \ No newline at end of file diff --git a/erpnext/accounts/doctype/repost_accounting_ledger_items/repost_accounting_ledger_items.py b/erpnext/accounts/doctype/repost_accounting_ledger_items/repost_accounting_ledger_items.py new file mode 100644 index 0000000000..9221f44735 --- /dev/null +++ b/erpnext/accounts/doctype/repost_accounting_ledger_items/repost_accounting_ledger_items.py @@ -0,0 +1,9 @@ +# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + + +class RepostAccountingLedgerItems(Document): + pass diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js index b45bc41e96..a4bcdb41db 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js @@ -37,7 +37,7 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends e super.onload(); this.frm.ignore_doctypes_on_cancel_all = ['POS Invoice', 'Timesheet', 'POS Invoice Merge Log', - 'POS Closing Entry', 'Journal Entry', 'Payment Entry', "Repost Payment Ledger"]; + 'POS Closing Entry', 'Journal Entry', 'Payment Entry', "Repost Payment Ledger", "Repost Accounting Ledger"]; if(!this.frm.doc.__islocal && !this.frm.doc.customer && this.frm.doc.debit_to) { // show debit_to in print format diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index e331c0b1bd..db120740dc 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -386,6 +386,8 @@ class SalesInvoice(SellingController): "Repost Item Valuation", "Repost Payment Ledger", "Repost Payment Ledger Items", + "Repost Accounting Ledger", + "Repost Accounting Ledger Items", "Payment Ledger Entry", "Serial and Batch Bundle", ) diff --git a/erpnext/accounts/test/accounts_mixin.py b/erpnext/accounts/test/accounts_mixin.py index c82164ef64..70bbf7e694 100644 --- a/erpnext/accounts/test/accounts_mixin.py +++ b/erpnext/accounts/test/accounts_mixin.py @@ -4,7 +4,7 @@ from erpnext.stock.doctype.item.test_item import create_item class AccountsTestMixin: - def create_customer(self, customer_name, currency=None): + def create_customer(self, customer_name="_Test Customer", currency=None): if not frappe.db.exists("Customer", customer_name): customer = frappe.new_doc("Customer") customer.customer_name = customer_name @@ -17,7 +17,7 @@ class AccountsTestMixin: else: self.customer = customer_name - def create_supplier(self, supplier_name, currency=None): + def create_supplier(self, supplier_name="_Test Supplier", currency=None): if not frappe.db.exists("Supplier", supplier_name): supplier = frappe.new_doc("Supplier") supplier.supplier_name = supplier_name @@ -31,7 +31,7 @@ class AccountsTestMixin: else: self.supplier = supplier_name - def create_item(self, item_name, is_stock=0, warehouse=None, company=None): + def create_item(self, item_name="_Test Item", is_stock=0, warehouse=None, company=None): item = create_item(item_name, is_stock_item=is_stock, warehouse=warehouse, company=company) self.item = item.name @@ -62,19 +62,44 @@ class AccountsTestMixin: self.debit_usd = "Debtors USD - " + abbr self.cash = "Cash - " + abbr self.creditors = "Creditors - " + abbr + self.retained_earnings = "Retained Earnings - " + abbr - # create bank account - bank_account = "HDFC - " + abbr - if frappe.db.exists("Account", bank_account): - self.bank = bank_account - else: - bank_acc = frappe.get_doc( + # Deferred revenue, expense and bank accounts + other_accounts = [ + frappe._dict( { - "doctype": "Account", + "attribute_name": "deferred_revenue", + "account_name": "Deferred Revenue", + "parent_account": "Current Liabilities - " + abbr, + } + ), + frappe._dict( + { + "attribute_name": "deferred_expense", + "account_name": "Deferred Expense", + "parent_account": "Current Assets - " + abbr, + } + ), + frappe._dict( + { + "attribute_name": "bank", "account_name": "HDFC", "parent_account": "Bank Accounts - " + abbr, - "company": self.company, } - ) - bank_acc.save() - self.bank = bank_acc.name + ), + ] + for acc in other_accounts: + acc_name = acc.account_name + " - " + abbr + if frappe.db.exists("Account", acc_name): + setattr(self, acc.attribute_name, acc_name) + else: + new_acc = frappe.get_doc( + { + "doctype": "Account", + "account_name": acc.account_name, + "parent_account": acc.parent_account, + "company": self.company, + } + ) + new_acc.save() + setattr(self, acc.attribute_name, new_acc.name)