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%}
+ {{ col.label }} |
+ {% endfor %}
+
+
+{% for gl in gl_data%}
+{% if gl["old"]%}
+
+{% else %}
+
+{% endif %}
+ {% for col in gl_columns %}
+
+ {{ gl[col.fieldname] }}
+ |
+ {% endfor %}
+
+{% endfor %}
+
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)