Merge pull request #37477 from GursheenK/editable-journal-entries
feat: editable journal entries
This commit is contained in:
commit
b193fafe49
@ -11,6 +11,10 @@ from frappe.model import core_doctypes_list
|
|||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
from frappe.utils import cstr
|
from frappe.utils import cstr
|
||||||
|
|
||||||
|
from erpnext.accounts.doctype.repost_accounting_ledger.repost_accounting_ledger import (
|
||||||
|
get_allowed_types_from_settings,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class AccountingDimension(Document):
|
class AccountingDimension(Document):
|
||||||
# begin: auto-generated types
|
# begin: auto-generated types
|
||||||
@ -106,6 +110,7 @@ def make_dimension_in_accounting_doctypes(doc, doclist=None):
|
|||||||
|
|
||||||
doc_count = len(get_accounting_dimensions())
|
doc_count = len(get_accounting_dimensions())
|
||||||
count = 0
|
count = 0
|
||||||
|
repostable_doctypes = get_allowed_types_from_settings()
|
||||||
|
|
||||||
for doctype in doclist:
|
for doctype in doclist:
|
||||||
|
|
||||||
@ -121,6 +126,7 @@ def make_dimension_in_accounting_doctypes(doc, doclist=None):
|
|||||||
"options": doc.document_type,
|
"options": doc.document_type,
|
||||||
"insert_after": insert_after_field,
|
"insert_after": insert_after_field,
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
|
"allow_on_submit": 1 if doctype in repostable_doctypes else 0,
|
||||||
}
|
}
|
||||||
|
|
||||||
meta = frappe.get_meta(doctype, cached=False)
|
meta = frappe.get_meta(doctype, cached=False)
|
||||||
|
@ -14,6 +14,25 @@ frappe.ui.form.on("Journal Entry", {
|
|||||||
refresh: function(frm) {
|
refresh: function(frm) {
|
||||||
erpnext.toggle_naming_series();
|
erpnext.toggle_naming_series();
|
||||||
|
|
||||||
|
if (frm.doc.repost_required && frm.doc.docstatus===1) {
|
||||||
|
frm.set_intro(__("Accounting entries for this Journal Entry need to be reposted. Please click on 'Repost' button to update."));
|
||||||
|
frm.add_custom_button(__('Repost Accounting Entries'),
|
||||||
|
() => {
|
||||||
|
frm.call({
|
||||||
|
doc: frm.doc,
|
||||||
|
method: 'repost_accounting_entries',
|
||||||
|
freeze: true,
|
||||||
|
freeze_message: __('Reposting...'),
|
||||||
|
callback: (r) => {
|
||||||
|
if (!r.exc) {
|
||||||
|
frappe.msgprint(__('Accounting Entries are reposted.'));
|
||||||
|
frm.refresh();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}).removeClass('btn-default').addClass('btn-warning');
|
||||||
|
}
|
||||||
|
|
||||||
if(frm.doc.docstatus > 0) {
|
if(frm.doc.docstatus > 0) {
|
||||||
frm.add_custom_button(__('Ledger'), function() {
|
frm.add_custom_button(__('Ledger'), function() {
|
||||||
frappe.route_options = {
|
frappe.route_options = {
|
||||||
@ -184,7 +203,6 @@ var update_jv_details = function(doc, r) {
|
|||||||
$.each(r, function(i, d) {
|
$.each(r, function(i, d) {
|
||||||
var row = frappe.model.add_child(doc, "Journal Entry Account", "accounts");
|
var row = frappe.model.add_child(doc, "Journal Entry Account", "accounts");
|
||||||
frappe.model.set_value(row.doctype, row.name, "account", d.account)
|
frappe.model.set_value(row.doctype, row.name, "account", d.account)
|
||||||
frappe.model.set_value(row.doctype, row.name, "balance", d.balance)
|
|
||||||
});
|
});
|
||||||
refresh_field("accounts");
|
refresh_field("accounts");
|
||||||
}
|
}
|
||||||
@ -193,7 +211,6 @@ erpnext.accounts.JournalEntry = class JournalEntry extends frappe.ui.form.Contro
|
|||||||
onload() {
|
onload() {
|
||||||
this.load_defaults();
|
this.load_defaults();
|
||||||
this.setup_queries();
|
this.setup_queries();
|
||||||
this.setup_balance_formatter();
|
|
||||||
erpnext.accounts.dimensions.setup_dimension_filters(this.frm, this.frm.doctype);
|
erpnext.accounts.dimensions.setup_dimension_filters(this.frm, this.frm.doctype);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -292,19 +309,6 @@ erpnext.accounts.JournalEntry = class JournalEntry extends frappe.ui.form.Contro
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setup_balance_formatter() {
|
|
||||||
const formatter = function(value, df, options, doc) {
|
|
||||||
var currency = frappe.meta.get_field_currency(df, doc);
|
|
||||||
var dr_or_cr = value ? ('<label>' + (value > 0.0 ? __("Dr") : __("Cr")) + '</label>') : "";
|
|
||||||
return "<div style='text-align: right'>"
|
|
||||||
+ ((value==null || value==="") ? "" : format_currency(Math.abs(value), currency))
|
|
||||||
+ " " + dr_or_cr
|
|
||||||
+ "</div>";
|
|
||||||
};
|
|
||||||
this.frm.fields_dict.accounts.grid.update_docfield_property('balance', 'formatter', formatter);
|
|
||||||
this.frm.fields_dict.accounts.grid.update_docfield_property('party_balance', 'formatter', formatter);
|
|
||||||
}
|
|
||||||
|
|
||||||
reference_name(doc, cdt, cdn) {
|
reference_name(doc, cdt, cdn) {
|
||||||
var d = frappe.get_doc(cdt, cdn);
|
var d = frappe.get_doc(cdt, cdn);
|
||||||
|
|
||||||
@ -400,23 +404,22 @@ frappe.ui.form.on("Journal Entry Account", {
|
|||||||
if(!d.account && d.party_type && d.party) {
|
if(!d.account && d.party_type && d.party) {
|
||||||
if(!frm.doc.company) frappe.throw(__("Please select Company"));
|
if(!frm.doc.company) frappe.throw(__("Please select Company"));
|
||||||
return frm.call({
|
return frm.call({
|
||||||
method: "erpnext.accounts.doctype.journal_entry.journal_entry.get_party_account_and_balance",
|
method: "erpnext.accounts.doctype.journal_entry.journal_entry.get_party_account_and_currency",
|
||||||
child: d,
|
child: d,
|
||||||
args: {
|
args: {
|
||||||
company: frm.doc.company,
|
company: frm.doc.company,
|
||||||
party_type: d.party_type,
|
party_type: d.party_type,
|
||||||
party: d.party,
|
party: d.party,
|
||||||
cost_center: d.cost_center
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
cost_center: function(frm, dt, dn) {
|
cost_center: function(frm, dt, dn) {
|
||||||
erpnext.journal_entry.set_account_balance(frm, dt, dn);
|
erpnext.journal_entry.set_account_details(frm, dt, dn);
|
||||||
},
|
},
|
||||||
|
|
||||||
account: function(frm, dt, dn) {
|
account: function(frm, dt, dn) {
|
||||||
erpnext.journal_entry.set_account_balance(frm, dt, dn);
|
erpnext.journal_entry.set_account_details(frm, dt, dn);
|
||||||
},
|
},
|
||||||
|
|
||||||
debit_in_account_currency: function(frm, cdt, cdn) {
|
debit_in_account_currency: function(frm, cdt, cdn) {
|
||||||
@ -600,14 +603,14 @@ $.extend(erpnext.journal_entry, {
|
|||||||
});
|
});
|
||||||
|
|
||||||
$.extend(erpnext.journal_entry, {
|
$.extend(erpnext.journal_entry, {
|
||||||
set_account_balance: function(frm, dt, dn) {
|
set_account_details: function(frm, dt, dn) {
|
||||||
var d = locals[dt][dn];
|
var d = locals[dt][dn];
|
||||||
if(d.account) {
|
if(d.account) {
|
||||||
if(!frm.doc.company) frappe.throw(__("Please select Company first"));
|
if(!frm.doc.company) frappe.throw(__("Please select Company first"));
|
||||||
if(!frm.doc.posting_date) frappe.throw(__("Please select Posting Date first"));
|
if(!frm.doc.posting_date) frappe.throw(__("Please select Posting Date first"));
|
||||||
|
|
||||||
return frappe.call({
|
return frappe.call({
|
||||||
method: "erpnext.accounts.doctype.journal_entry.journal_entry.get_account_balance_and_party_type",
|
method: "erpnext.accounts.doctype.journal_entry.journal_entry.get_account_details_and_party_type",
|
||||||
args: {
|
args: {
|
||||||
account: d.account,
|
account: d.account,
|
||||||
date: frm.doc.posting_date,
|
date: frm.doc.posting_date,
|
||||||
@ -615,7 +618,6 @@ $.extend(erpnext.journal_entry, {
|
|||||||
debit: flt(d.debit_in_account_currency),
|
debit: flt(d.debit_in_account_currency),
|
||||||
credit: flt(d.credit_in_account_currency),
|
credit: flt(d.credit_in_account_currency),
|
||||||
exchange_rate: d.exchange_rate,
|
exchange_rate: d.exchange_rate,
|
||||||
cost_center: d.cost_center
|
|
||||||
},
|
},
|
||||||
callback: function(r) {
|
callback: function(r) {
|
||||||
if(r.message) {
|
if(r.message) {
|
||||||
|
@ -64,7 +64,8 @@
|
|||||||
"stock_entry",
|
"stock_entry",
|
||||||
"subscription_section",
|
"subscription_section",
|
||||||
"auto_repeat",
|
"auto_repeat",
|
||||||
"amended_from"
|
"amended_from",
|
||||||
|
"repost_required"
|
||||||
],
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
@ -543,6 +544,15 @@
|
|||||||
"label": "Is System Generated",
|
"label": "Is System Generated",
|
||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"fieldname": "repost_required",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"hidden": 1,
|
||||||
|
"label": "Repost Required",
|
||||||
|
"print_hide": 1,
|
||||||
|
"read_only": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"icon": "fa fa-file-text",
|
"icon": "fa fa-file-text",
|
||||||
@ -558,6 +568,7 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"modified": "2023-11-23 12:11:04.128015",
|
"modified": "2023-11-23 12:11:04.128015",
|
||||||
|
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Journal Entry",
|
"name": "Journal Entry",
|
||||||
|
@ -13,6 +13,10 @@ from erpnext.accounts.deferred_revenue import get_deferred_booking_accounts
|
|||||||
from erpnext.accounts.doctype.invoice_discounting.invoice_discounting import (
|
from erpnext.accounts.doctype.invoice_discounting.invoice_discounting import (
|
||||||
get_party_account_based_on_invoice_discounting,
|
get_party_account_based_on_invoice_discounting,
|
||||||
)
|
)
|
||||||
|
from erpnext.accounts.doctype.repost_accounting_ledger.repost_accounting_ledger import (
|
||||||
|
validate_docs_for_deferred_accounting,
|
||||||
|
validate_docs_for_voucher_types,
|
||||||
|
)
|
||||||
from erpnext.accounts.doctype.tax_withholding_category.tax_withholding_category import (
|
from erpnext.accounts.doctype.tax_withholding_category.tax_withholding_category import (
|
||||||
get_party_tax_withholding_details,
|
get_party_tax_withholding_details,
|
||||||
)
|
)
|
||||||
@ -140,7 +144,6 @@ class JournalEntry(AccountsController):
|
|||||||
self.set_print_format_fields()
|
self.set_print_format_fields()
|
||||||
self.validate_credit_debit_note()
|
self.validate_credit_debit_note()
|
||||||
self.validate_empty_accounts_table()
|
self.validate_empty_accounts_table()
|
||||||
self.set_account_and_party_balance()
|
|
||||||
self.validate_inter_company_accounts()
|
self.validate_inter_company_accounts()
|
||||||
self.validate_depr_entry_voucher_type()
|
self.validate_depr_entry_voucher_type()
|
||||||
|
|
||||||
@ -150,6 +153,10 @@ class JournalEntry(AccountsController):
|
|||||||
if not self.title:
|
if not self.title:
|
||||||
self.title = self.get_title()
|
self.title = self.get_title()
|
||||||
|
|
||||||
|
def validate_for_repost(self):
|
||||||
|
validate_docs_for_voucher_types(["Journal Entry"])
|
||||||
|
validate_docs_for_deferred_accounting([self.name], [])
|
||||||
|
|
||||||
def submit(self):
|
def submit(self):
|
||||||
if len(self.accounts) > 100:
|
if len(self.accounts) > 100:
|
||||||
msgprint(_("The task has been enqueued as a background job."), alert=True)
|
msgprint(_("The task has been enqueued as a background job."), alert=True)
|
||||||
@ -173,6 +180,15 @@ class JournalEntry(AccountsController):
|
|||||||
self.update_inter_company_jv()
|
self.update_inter_company_jv()
|
||||||
self.update_invoice_discounting()
|
self.update_invoice_discounting()
|
||||||
|
|
||||||
|
def on_update_after_submit(self):
|
||||||
|
if hasattr(self, "repost_required"):
|
||||||
|
self.needs_repost = self.check_if_fields_updated(
|
||||||
|
fields_to_check=[], child_tables={"accounts": []}
|
||||||
|
)
|
||||||
|
if self.needs_repost:
|
||||||
|
self.validate_for_repost()
|
||||||
|
self.db_set("repost_required", self.needs_repost)
|
||||||
|
|
||||||
def on_cancel(self):
|
def on_cancel(self):
|
||||||
# References for this Journal are removed on the `on_cancel` event in accounts_controller
|
# References for this Journal are removed on the `on_cancel` event in accounts_controller
|
||||||
super(JournalEntry, self).on_cancel()
|
super(JournalEntry, self).on_cancel()
|
||||||
@ -1152,21 +1168,6 @@ class JournalEntry(AccountsController):
|
|||||||
if not self.get("accounts"):
|
if not self.get("accounts"):
|
||||||
frappe.throw(_("Accounts table cannot be blank."))
|
frappe.throw(_("Accounts table cannot be blank."))
|
||||||
|
|
||||||
def set_account_and_party_balance(self):
|
|
||||||
account_balance = {}
|
|
||||||
party_balance = {}
|
|
||||||
for d in self.get("accounts"):
|
|
||||||
if d.account not in account_balance:
|
|
||||||
account_balance[d.account] = get_balance_on(account=d.account, date=self.posting_date)
|
|
||||||
|
|
||||||
if (d.party_type, d.party) not in party_balance:
|
|
||||||
party_balance[(d.party_type, d.party)] = get_balance_on(
|
|
||||||
party_type=d.party_type, party=d.party, date=self.posting_date, company=self.company
|
|
||||||
)
|
|
||||||
|
|
||||||
d.account_balance = account_balance[d.account]
|
|
||||||
d.party_balance = party_balance[(d.party_type, d.party)]
|
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_default_bank_cash_account(
|
def get_default_bank_cash_account(
|
||||||
@ -1334,8 +1335,6 @@ def get_payment_entry(ref_doc, args):
|
|||||||
"account_type": frappe.get_cached_value("Account", args.get("party_account"), "account_type"),
|
"account_type": frappe.get_cached_value("Account", args.get("party_account"), "account_type"),
|
||||||
"account_currency": args.get("party_account_currency")
|
"account_currency": args.get("party_account_currency")
|
||||||
or get_account_currency(args.get("party_account")),
|
or get_account_currency(args.get("party_account")),
|
||||||
"balance": get_balance_on(args.get("party_account")),
|
|
||||||
"party_balance": get_balance_on(party=args.get("party"), party_type=args.get("party_type")),
|
|
||||||
"exchange_rate": exchange_rate,
|
"exchange_rate": exchange_rate,
|
||||||
args.get("amount_field_party"): args.get("amount"),
|
args.get("amount_field_party"): args.get("amount"),
|
||||||
"is_advance": args.get("is_advance"),
|
"is_advance": args.get("is_advance"),
|
||||||
@ -1483,30 +1482,23 @@ def get_outstanding(args):
|
|||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_party_account_and_balance(company, party_type, party, cost_center=None):
|
def get_party_account_and_currency(company, party_type, party):
|
||||||
if not frappe.has_permission("Account"):
|
if not frappe.has_permission("Account"):
|
||||||
frappe.msgprint(_("No Permission"), raise_exception=1)
|
frappe.msgprint(_("No Permission"), raise_exception=1)
|
||||||
|
|
||||||
account = get_party_account(party_type, party, company)
|
account = get_party_account(party_type, party, company)
|
||||||
|
|
||||||
account_balance = get_balance_on(account=account, cost_center=cost_center)
|
|
||||||
party_balance = get_balance_on(
|
|
||||||
party_type=party_type, party=party, company=company, cost_center=cost_center
|
|
||||||
)
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"account": account,
|
"account": account,
|
||||||
"balance": account_balance,
|
|
||||||
"party_balance": party_balance,
|
|
||||||
"account_currency": frappe.get_cached_value("Account", account, "account_currency"),
|
"account_currency": frappe.get_cached_value("Account", account, "account_currency"),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_account_balance_and_party_type(
|
def get_account_details_and_party_type(
|
||||||
account, date, company, debit=None, credit=None, exchange_rate=None, cost_center=None
|
account, date, company, debit=None, credit=None, exchange_rate=None
|
||||||
):
|
):
|
||||||
"""Returns dict of account balance and party type to be set in Journal Entry on selection of account."""
|
"""Returns dict of account details and party type to be set in Journal Entry on selection of account."""
|
||||||
if not frappe.has_permission("Account"):
|
if not frappe.has_permission("Account"):
|
||||||
frappe.msgprint(_("No Permission"), raise_exception=1)
|
frappe.msgprint(_("No Permission"), raise_exception=1)
|
||||||
|
|
||||||
@ -1526,7 +1518,6 @@ def get_account_balance_and_party_type(
|
|||||||
party_type = ""
|
party_type = ""
|
||||||
|
|
||||||
grid_values = {
|
grid_values = {
|
||||||
"balance": get_balance_on(account, date, cost_center=cost_center),
|
|
||||||
"party_type": party_type,
|
"party_type": party_type,
|
||||||
"account_type": account_details.account_type,
|
"account_type": account_details.account_type,
|
||||||
"account_currency": account_details.account_currency or company_currency,
|
"account_currency": account_details.account_currency or company_currency,
|
||||||
|
@ -166,43 +166,37 @@ class TestJournalEntry(unittest.TestCase):
|
|||||||
jv.get("accounts")[1].credit_in_account_currency = 5000
|
jv.get("accounts")[1].credit_in_account_currency = 5000
|
||||||
jv.submit()
|
jv.submit()
|
||||||
|
|
||||||
gl_entries = frappe.db.sql(
|
self.voucher_no = jv.name
|
||||||
"""select account, account_currency, debit, credit,
|
|
||||||
debit_in_account_currency, credit_in_account_currency
|
|
||||||
from `tabGL Entry` where voucher_type='Journal Entry' and voucher_no=%s
|
|
||||||
order by account asc""",
|
|
||||||
jv.name,
|
|
||||||
as_dict=1,
|
|
||||||
)
|
|
||||||
|
|
||||||
self.assertTrue(gl_entries)
|
self.fields = [
|
||||||
|
"account",
|
||||||
|
"account_currency",
|
||||||
|
"debit",
|
||||||
|
"debit_in_account_currency",
|
||||||
|
"credit",
|
||||||
|
"credit_in_account_currency",
|
||||||
|
]
|
||||||
|
|
||||||
expected_values = {
|
self.expected_gle = [
|
||||||
"_Test Bank USD - _TC": {
|
{
|
||||||
"account_currency": "USD",
|
"account": "_Test Bank - _TC",
|
||||||
"debit": 5000,
|
|
||||||
"debit_in_account_currency": 100,
|
|
||||||
"credit": 0,
|
|
||||||
"credit_in_account_currency": 0,
|
|
||||||
},
|
|
||||||
"_Test Bank - _TC": {
|
|
||||||
"account_currency": "INR",
|
"account_currency": "INR",
|
||||||
"debit": 0,
|
"debit": 0,
|
||||||
"debit_in_account_currency": 0,
|
"debit_in_account_currency": 0,
|
||||||
"credit": 5000,
|
"credit": 5000,
|
||||||
"credit_in_account_currency": 5000,
|
"credit_in_account_currency": 5000,
|
||||||
},
|
},
|
||||||
}
|
{
|
||||||
|
"account": "_Test Bank USD - _TC",
|
||||||
|
"account_currency": "USD",
|
||||||
|
"debit": 5000,
|
||||||
|
"debit_in_account_currency": 100,
|
||||||
|
"credit": 0,
|
||||||
|
"credit_in_account_currency": 0,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
for field in (
|
self.check_gl_entries()
|
||||||
"account_currency",
|
|
||||||
"debit",
|
|
||||||
"debit_in_account_currency",
|
|
||||||
"credit",
|
|
||||||
"credit_in_account_currency",
|
|
||||||
):
|
|
||||||
for i, gle in enumerate(gl_entries):
|
|
||||||
self.assertEqual(expected_values[gle.account][field], gle[field])
|
|
||||||
|
|
||||||
# cancel
|
# cancel
|
||||||
jv.cancel()
|
jv.cancel()
|
||||||
@ -228,43 +222,37 @@ class TestJournalEntry(unittest.TestCase):
|
|||||||
rjv.posting_date = nowdate()
|
rjv.posting_date = nowdate()
|
||||||
rjv.submit()
|
rjv.submit()
|
||||||
|
|
||||||
gl_entries = frappe.db.sql(
|
self.voucher_no = rjv.name
|
||||||
"""select account, account_currency, debit, credit,
|
|
||||||
debit_in_account_currency, credit_in_account_currency
|
|
||||||
from `tabGL Entry` where voucher_type='Journal Entry' and voucher_no=%s
|
|
||||||
order by account asc""",
|
|
||||||
rjv.name,
|
|
||||||
as_dict=1,
|
|
||||||
)
|
|
||||||
|
|
||||||
self.assertTrue(gl_entries)
|
self.fields = [
|
||||||
|
"account",
|
||||||
|
"account_currency",
|
||||||
|
"debit",
|
||||||
|
"credit",
|
||||||
|
"debit_in_account_currency",
|
||||||
|
"credit_in_account_currency",
|
||||||
|
]
|
||||||
|
|
||||||
expected_values = {
|
self.expected_gle = [
|
||||||
"_Test Bank USD - _TC": {
|
{
|
||||||
|
"account": "_Test Bank USD - _TC",
|
||||||
"account_currency": "USD",
|
"account_currency": "USD",
|
||||||
"debit": 0,
|
"debit": 0,
|
||||||
"debit_in_account_currency": 0,
|
"debit_in_account_currency": 0,
|
||||||
"credit": 5000,
|
"credit": 5000,
|
||||||
"credit_in_account_currency": 100,
|
"credit_in_account_currency": 100,
|
||||||
},
|
},
|
||||||
"Sales - _TC": {
|
{
|
||||||
|
"account": "Sales - _TC",
|
||||||
"account_currency": "INR",
|
"account_currency": "INR",
|
||||||
"debit": 5000,
|
"debit": 5000,
|
||||||
"debit_in_account_currency": 5000,
|
"debit_in_account_currency": 5000,
|
||||||
"credit": 0,
|
"credit": 0,
|
||||||
"credit_in_account_currency": 0,
|
"credit_in_account_currency": 0,
|
||||||
},
|
},
|
||||||
}
|
]
|
||||||
|
|
||||||
for field in (
|
self.check_gl_entries()
|
||||||
"account_currency",
|
|
||||||
"debit",
|
|
||||||
"debit_in_account_currency",
|
|
||||||
"credit",
|
|
||||||
"credit_in_account_currency",
|
|
||||||
):
|
|
||||||
for i, gle in enumerate(gl_entries):
|
|
||||||
self.assertEqual(expected_values[gle.account][field], gle[field])
|
|
||||||
|
|
||||||
def test_disallow_change_in_account_currency_for_a_party(self):
|
def test_disallow_change_in_account_currency_for_a_party(self):
|
||||||
# create jv in USD
|
# create jv in USD
|
||||||
@ -344,23 +332,25 @@ class TestJournalEntry(unittest.TestCase):
|
|||||||
jv.insert()
|
jv.insert()
|
||||||
jv.submit()
|
jv.submit()
|
||||||
|
|
||||||
expected_values = {
|
self.voucher_no = jv.name
|
||||||
"_Test Cash - _TC": {"cost_center": cost_center},
|
|
||||||
"_Test Bank - _TC": {"cost_center": cost_center},
|
|
||||||
}
|
|
||||||
|
|
||||||
gl_entries = frappe.db.sql(
|
self.fields = [
|
||||||
"""select account, cost_center, debit, credit
|
"account",
|
||||||
from `tabGL Entry` where voucher_type='Journal Entry' and voucher_no=%s
|
"cost_center",
|
||||||
order by account asc""",
|
]
|
||||||
jv.name,
|
|
||||||
as_dict=1,
|
|
||||||
)
|
|
||||||
|
|
||||||
self.assertTrue(gl_entries)
|
self.expected_gle = [
|
||||||
|
{
|
||||||
|
"account": "_Test Bank - _TC",
|
||||||
|
"cost_center": cost_center,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"account": "_Test Cash - _TC",
|
||||||
|
"cost_center": cost_center,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
for gle in gl_entries:
|
self.check_gl_entries()
|
||||||
self.assertEqual(expected_values[gle.account]["cost_center"], gle.cost_center)
|
|
||||||
|
|
||||||
def test_jv_with_project(self):
|
def test_jv_with_project(self):
|
||||||
from erpnext.projects.doctype.project.test_project import make_project
|
from erpnext.projects.doctype.project.test_project import make_project
|
||||||
@ -387,23 +377,22 @@ class TestJournalEntry(unittest.TestCase):
|
|||||||
jv.insert()
|
jv.insert()
|
||||||
jv.submit()
|
jv.submit()
|
||||||
|
|
||||||
expected_values = {
|
self.voucher_no = jv.name
|
||||||
"_Test Cash - _TC": {"project": project_name},
|
|
||||||
"_Test Bank - _TC": {"project": project_name},
|
|
||||||
}
|
|
||||||
|
|
||||||
gl_entries = frappe.db.sql(
|
self.fields = ["account", "project"]
|
||||||
"""select account, project, debit, credit
|
|
||||||
from `tabGL Entry` where voucher_type='Journal Entry' and voucher_no=%s
|
|
||||||
order by account asc""",
|
|
||||||
jv.name,
|
|
||||||
as_dict=1,
|
|
||||||
)
|
|
||||||
|
|
||||||
self.assertTrue(gl_entries)
|
self.expected_gle = [
|
||||||
|
{
|
||||||
|
"account": "_Test Bank - _TC",
|
||||||
|
"project": project_name,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"account": "_Test Cash - _TC",
|
||||||
|
"project": project_name,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
for gle in gl_entries:
|
self.check_gl_entries()
|
||||||
self.assertEqual(expected_values[gle.account]["project"], gle.project)
|
|
||||||
|
|
||||||
def test_jv_account_and_party_balance_with_cost_centre(self):
|
def test_jv_account_and_party_balance_with_cost_centre(self):
|
||||||
from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center
|
from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center
|
||||||
@ -426,6 +415,79 @@ class TestJournalEntry(unittest.TestCase):
|
|||||||
account_balance = get_balance_on(account="_Test Bank - _TC", cost_center=cost_center)
|
account_balance = get_balance_on(account="_Test Bank - _TC", cost_center=cost_center)
|
||||||
self.assertEqual(expected_account_balance, account_balance)
|
self.assertEqual(expected_account_balance, account_balance)
|
||||||
|
|
||||||
|
def test_repost_accounting_entries(self):
|
||||||
|
from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center
|
||||||
|
|
||||||
|
# Configure Repost Accounting Ledger for JVs
|
||||||
|
settings = frappe.get_doc("Repost Accounting Ledger Settings")
|
||||||
|
if not [x for x in settings.allowed_types if x.document_type == "Journal Entry"]:
|
||||||
|
settings.append("allowed_types", {"document_type": "Journal Entry", "allowed": True})
|
||||||
|
settings.save()
|
||||||
|
|
||||||
|
# Create JV with defaut cost center - _Test Cost Center
|
||||||
|
jv = make_journal_entry("_Test Cash - _TC", "_Test Bank - _TC", 100, save=False)
|
||||||
|
jv.multi_currency = 0
|
||||||
|
jv.submit()
|
||||||
|
|
||||||
|
# Check GL entries before reposting
|
||||||
|
self.voucher_no = jv.name
|
||||||
|
|
||||||
|
self.fields = [
|
||||||
|
"account",
|
||||||
|
"debit_in_account_currency",
|
||||||
|
"credit_in_account_currency",
|
||||||
|
"cost_center",
|
||||||
|
]
|
||||||
|
|
||||||
|
self.expected_gle = [
|
||||||
|
{
|
||||||
|
"account": "_Test Bank - _TC",
|
||||||
|
"debit_in_account_currency": 0,
|
||||||
|
"credit_in_account_currency": 100,
|
||||||
|
"cost_center": "_Test Cost Center - _TC",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"account": "_Test Cash - _TC",
|
||||||
|
"debit_in_account_currency": 100,
|
||||||
|
"credit_in_account_currency": 0,
|
||||||
|
"cost_center": "_Test Cost Center - _TC",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
self.check_gl_entries()
|
||||||
|
|
||||||
|
# Change cost center for bank account - _Test Cost Center for BS Account
|
||||||
|
create_cost_center(cost_center_name="_Test Cost Center for BS Account", company="_Test Company")
|
||||||
|
jv.accounts[1].cost_center = "_Test Cost Center for BS Account - _TC"
|
||||||
|
jv.save()
|
||||||
|
|
||||||
|
# Check if repost flag gets set on update after submit
|
||||||
|
self.assertTrue(jv.repost_required)
|
||||||
|
jv.repost_accounting_entries()
|
||||||
|
|
||||||
|
# Check GL entries after reposting
|
||||||
|
jv.load_from_db()
|
||||||
|
self.expected_gle[0]["cost_center"] = "_Test Cost Center for BS Account - _TC"
|
||||||
|
self.check_gl_entries()
|
||||||
|
|
||||||
|
def check_gl_entries(self):
|
||||||
|
gl = frappe.qb.DocType("GL Entry")
|
||||||
|
query = frappe.qb.from_(gl)
|
||||||
|
for field in self.fields:
|
||||||
|
query = query.select(gl[field])
|
||||||
|
|
||||||
|
query = query.where(
|
||||||
|
(gl.voucher_type == "Journal Entry")
|
||||||
|
& (gl.voucher_no == self.voucher_no)
|
||||||
|
& (gl.is_cancelled == 0)
|
||||||
|
).orderby(gl.account)
|
||||||
|
|
||||||
|
gl_entries = query.run(as_dict=True)
|
||||||
|
|
||||||
|
for i in range(len(self.expected_gle)):
|
||||||
|
for field in self.fields:
|
||||||
|
self.assertEqual(self.expected_gle[i][field], gl_entries[i][field])
|
||||||
|
|
||||||
|
|
||||||
def make_journal_entry(
|
def make_journal_entry(
|
||||||
account1,
|
account1,
|
||||||
|
@ -9,12 +9,10 @@
|
|||||||
"field_order": [
|
"field_order": [
|
||||||
"account",
|
"account",
|
||||||
"account_type",
|
"account_type",
|
||||||
"balance",
|
|
||||||
"col_break1",
|
"col_break1",
|
||||||
"bank_account",
|
"bank_account",
|
||||||
"party_type",
|
"party_type",
|
||||||
"party",
|
"party",
|
||||||
"party_balance",
|
|
||||||
"accounting_dimensions_section",
|
"accounting_dimensions_section",
|
||||||
"cost_center",
|
"cost_center",
|
||||||
"dimension_col_break",
|
"dimension_col_break",
|
||||||
@ -64,17 +62,7 @@
|
|||||||
"print_hide": 1
|
"print_hide": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "balance",
|
"allow_on_submit": 1,
|
||||||
"fieldtype": "Currency",
|
|
||||||
"label": "Account Balance",
|
|
||||||
"no_copy": 1,
|
|
||||||
"oldfieldname": "balance",
|
|
||||||
"oldfieldtype": "Data",
|
|
||||||
"options": "account_currency",
|
|
||||||
"print_hide": 1,
|
|
||||||
"read_only": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"default": ":Company",
|
"default": ":Company",
|
||||||
"description": "If Income or Expense",
|
"description": "If Income or Expense",
|
||||||
"fieldname": "cost_center",
|
"fieldname": "cost_center",
|
||||||
@ -107,14 +95,6 @@
|
|||||||
"label": "Party",
|
"label": "Party",
|
||||||
"options": "party_type"
|
"options": "party_type"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"fieldname": "party_balance",
|
|
||||||
"fieldtype": "Currency",
|
|
||||||
"label": "Party Balance",
|
|
||||||
"options": "account_currency",
|
|
||||||
"print_hide": 1,
|
|
||||||
"read_only": 1
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"fieldname": "currency_section",
|
"fieldname": "currency_section",
|
||||||
"fieldtype": "Section Break",
|
"fieldtype": "Section Break",
|
||||||
@ -223,6 +203,7 @@
|
|||||||
"no_copy": 1
|
"no_copy": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"allow_on_submit": 1,
|
||||||
"fieldname": "project",
|
"fieldname": "project",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Project",
|
"label": "Project",
|
||||||
@ -286,7 +267,7 @@
|
|||||||
"idx": 1,
|
"idx": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-12-03 23:21:22.205409",
|
"modified": "2024-02-05 01:10:50.224840",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Journal Entry Account",
|
"name": "Journal Entry Account",
|
||||||
|
@ -724,6 +724,7 @@ class PurchaseInvoice(BuyingController):
|
|||||||
"cash_bank_account",
|
"cash_bank_account",
|
||||||
"write_off_account",
|
"write_off_account",
|
||||||
"unrealized_profit_loss_account",
|
"unrealized_profit_loss_account",
|
||||||
|
"is_opening",
|
||||||
]
|
]
|
||||||
child_tables = {"items": ("expense_account",), "taxes": ("account_head",)}
|
child_tables = {"items": ("expense_account",), "taxes": ("account_head",)}
|
||||||
self.needs_repost = self.check_if_fields_updated(fields_to_check, child_tables)
|
self.needs_repost = self.check_if_fields_updated(fields_to_check, child_tables)
|
||||||
|
@ -727,6 +727,7 @@ class SalesInvoice(SellingController):
|
|||||||
"write_off_account",
|
"write_off_account",
|
||||||
"loyalty_redemption_account",
|
"loyalty_redemption_account",
|
||||||
"unrealized_profit_loss_account",
|
"unrealized_profit_loss_account",
|
||||||
|
"is_opening",
|
||||||
]
|
]
|
||||||
child_tables = {
|
child_tables = {
|
||||||
"items": ("income_account", "expense_account", "discount_account"),
|
"items": ("income_account", "expense_account", "discount_account"),
|
||||||
|
@ -2453,27 +2453,20 @@ class AccountsController(TransactionBase):
|
|||||||
doc_before_update = self.get_doc_before_save()
|
doc_before_update = self.get_doc_before_save()
|
||||||
accounting_dimensions = get_accounting_dimensions() + ["cost_center", "project"]
|
accounting_dimensions = get_accounting_dimensions() + ["cost_center", "project"]
|
||||||
|
|
||||||
# Check if opening entry check updated
|
|
||||||
needs_repost = doc_before_update.get("is_opening") != self.is_opening
|
|
||||||
|
|
||||||
if not needs_repost:
|
|
||||||
# Parent Level Accounts excluding party account
|
# Parent Level Accounts excluding party account
|
||||||
fields_to_check += accounting_dimensions
|
fields_to_check += accounting_dimensions
|
||||||
for field in fields_to_check:
|
for field in fields_to_check:
|
||||||
if doc_before_update.get(field) != self.get(field):
|
if doc_before_update.get(field) != self.get(field):
|
||||||
needs_repost = 1
|
return True
|
||||||
break
|
|
||||||
|
|
||||||
if not needs_repost:
|
|
||||||
# Check for child tables
|
# Check for child tables
|
||||||
for table in child_tables:
|
for table in child_tables:
|
||||||
needs_repost = check_if_child_table_updated(
|
if check_if_child_table_updated(
|
||||||
doc_before_update.get(table), self.get(table), child_tables[table]
|
doc_before_update.get(table), self.get(table), child_tables[table]
|
||||||
)
|
):
|
||||||
if needs_repost:
|
return True
|
||||||
break
|
|
||||||
|
|
||||||
return needs_repost
|
return False
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def repost_accounting_entries(self):
|
def repost_accounting_entries(self):
|
||||||
@ -3502,15 +3495,12 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil
|
|||||||
def check_if_child_table_updated(
|
def check_if_child_table_updated(
|
||||||
child_table_before_update, child_table_after_update, fields_to_check
|
child_table_before_update, child_table_after_update, fields_to_check
|
||||||
):
|
):
|
||||||
accounting_dimensions = get_accounting_dimensions() + ["cost_center", "project"]
|
fields_to_check = list(fields_to_check) + get_accounting_dimensions() + ["cost_center", "project"]
|
||||||
# Check if any field affecting accounting entry is altered
|
|
||||||
for index, item in enumerate(child_table_after_update):
|
|
||||||
for field in fields_to_check:
|
|
||||||
if child_table_before_update[index].get(field) != item.get(field):
|
|
||||||
return True
|
|
||||||
|
|
||||||
for dimension in accounting_dimensions:
|
# Check if any field affecting accounting entry is altered
|
||||||
if child_table_before_update[index].get(dimension) != item.get(dimension):
|
for index, item in enumerate(child_table_before_update):
|
||||||
|
for field in fields_to_check:
|
||||||
|
if child_table_after_update[index].get(field) != item.get(field):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
@ -354,6 +354,7 @@ execute:frappe.db.set_single_value("Buying Settings", "project_update_frequency"
|
|||||||
execute:frappe.db.set_default("date_format", frappe.db.get_single_value("System Settings", "date_format"))
|
execute:frappe.db.set_default("date_format", frappe.db.get_single_value("System Settings", "date_format"))
|
||||||
erpnext.patches.v14_0.update_total_asset_cost_field
|
erpnext.patches.v14_0.update_total_asset_cost_field
|
||||||
erpnext.patches.v15_0.create_advance_payment_status
|
erpnext.patches.v15_0.create_advance_payment_status
|
||||||
|
erpnext.patches.v15_0.allow_on_submit_dimensions_for_repostable_doctypes
|
||||||
erpnext.patches.v14_0.create_accounting_dimensions_in_reconciliation_tool
|
erpnext.patches.v14_0.create_accounting_dimensions_in_reconciliation_tool
|
||||||
# below migration patch should always run last
|
# below migration patch should always run last
|
||||||
erpnext.patches.v14_0.migrate_gl_to_payment_ledger
|
erpnext.patches.v14_0.migrate_gl_to_payment_ledger
|
||||||
|
@ -0,0 +1,14 @@
|
|||||||
|
import frappe
|
||||||
|
|
||||||
|
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
|
||||||
|
get_accounting_dimensions,
|
||||||
|
)
|
||||||
|
from erpnext.accounts.doctype.repost_accounting_ledger.repost_accounting_ledger import (
|
||||||
|
get_allowed_types_from_settings,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def execute():
|
||||||
|
for dt in get_allowed_types_from_settings():
|
||||||
|
for dimension in get_accounting_dimensions():
|
||||||
|
frappe.db.set_value("Custom Field", dt + "-" + dimension, "allow_on_submit", 1)
|
Loading…
x
Reference in New Issue
Block a user