feat: periodic accounting
This commit is contained in:
@@ -20,6 +20,39 @@ frappe.ui.form.on("Journal Entry", {
|
|||||||
"Unreconcile Payment Entries",
|
"Unreconcile Payment Entries",
|
||||||
"Bank Transaction",
|
"Bank Transaction",
|
||||||
];
|
];
|
||||||
|
|
||||||
|
frm.trigger("set_queries");
|
||||||
|
},
|
||||||
|
|
||||||
|
set_queries(frm) {
|
||||||
|
frm.set_query("periodic_entry_difference_account", function () {
|
||||||
|
return {
|
||||||
|
filters: {
|
||||||
|
is_group: 0,
|
||||||
|
company: frm.doc.company,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
frm.set_query("stock_asset_account", function () {
|
||||||
|
return {
|
||||||
|
filters: {
|
||||||
|
is_group: 0,
|
||||||
|
account_type: "Stock",
|
||||||
|
company: frm.doc.company,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
get_balance_for_periodic_accounting(frm) {
|
||||||
|
frm.call({
|
||||||
|
method: "get_balance_for_periodic_accounting",
|
||||||
|
doc: frm.doc,
|
||||||
|
callback: function (r) {
|
||||||
|
refresh_field("accounts");
|
||||||
|
},
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
refresh: function (frm) {
|
refresh: function (frm) {
|
||||||
|
|||||||
@@ -13,15 +13,21 @@
|
|||||||
"title",
|
"title",
|
||||||
"voucher_type",
|
"voucher_type",
|
||||||
"naming_series",
|
"naming_series",
|
||||||
"finance_book",
|
|
||||||
"process_deferred_accounting",
|
"process_deferred_accounting",
|
||||||
"reversal_of",
|
"reversal_of",
|
||||||
"tax_withholding_category",
|
|
||||||
"column_break1",
|
"column_break1",
|
||||||
"from_template",
|
"from_template",
|
||||||
"company",
|
"company",
|
||||||
"posting_date",
|
"posting_date",
|
||||||
|
"finance_book",
|
||||||
"apply_tds",
|
"apply_tds",
|
||||||
|
"tax_withholding_category",
|
||||||
|
"section_break_tcvw",
|
||||||
|
"for_all_stock_asset_accounts",
|
||||||
|
"column_break_wpau",
|
||||||
|
"stock_asset_account",
|
||||||
|
"periodic_entry_difference_account",
|
||||||
|
"get_balance_for_periodic_accounting",
|
||||||
"2_add_edit_gl_entries",
|
"2_add_edit_gl_entries",
|
||||||
"accounts",
|
"accounts",
|
||||||
"section_break99",
|
"section_break99",
|
||||||
@@ -89,7 +95,7 @@
|
|||||||
"label": "Entry Type",
|
"label": "Entry Type",
|
||||||
"oldfieldname": "voucher_type",
|
"oldfieldname": "voucher_type",
|
||||||
"oldfieldtype": "Select",
|
"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\nAsset Disposal\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\nAsset Disposal\nPeriodic Accounting Entry\nExchange Rate Revaluation\nExchange Gain Or Loss\nDeferred Revenue\nDeferred Expense",
|
||||||
"reqd": 1,
|
"reqd": 1,
|
||||||
"search_index": 1
|
"search_index": 1
|
||||||
},
|
},
|
||||||
@@ -543,6 +549,42 @@
|
|||||||
"label": "Is System Generated",
|
"label": "Is System Generated",
|
||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "eval:doc.voucher_type === \"Periodic Accounting Entry\"",
|
||||||
|
"fieldname": "periodic_entry_difference_account",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Periodic Entry Difference Account",
|
||||||
|
"mandatory_depends_on": "eval:doc.voucher_type === \"Periodic Accounting Entry\"",
|
||||||
|
"options": "Account"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "eval:doc.voucher_type === \"Periodic Accounting Entry\"",
|
||||||
|
"fieldname": "section_break_tcvw",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Periodic Accounting"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "1",
|
||||||
|
"fieldname": "for_all_stock_asset_accounts",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "For All Stock Asset Accounts"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "eval:doc.for_all_stock_asset_accounts === 0",
|
||||||
|
"fieldname": "stock_asset_account",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Stock Asset Account",
|
||||||
|
"options": "Account"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_wpau",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "get_balance_for_periodic_accounting",
|
||||||
|
"fieldtype": "Button",
|
||||||
|
"label": "Get Balance"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"icon": "fa fa-file-text",
|
"icon": "fa fa-file-text",
|
||||||
@@ -557,7 +599,7 @@
|
|||||||
"table_fieldname": "payment_entries"
|
"table_fieldname": "payment_entries"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"modified": "2024-12-26 15:32:20.730666",
|
"modified": "2025-06-17 15:18:13.322681",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Journal Entry",
|
"name": "Journal Entry",
|
||||||
@@ -602,10 +644,11 @@
|
|||||||
"role": "Auditor"
|
"role": "Auditor"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"row_format": "Dynamic",
|
||||||
"search_fields": "voucher_type,posting_date, due_date, cheque_no",
|
"search_fields": "voucher_type,posting_date, due_date, cheque_no",
|
||||||
"sort_field": "creation",
|
"sort_field": "creation",
|
||||||
"sort_order": "DESC",
|
"sort_order": "DESC",
|
||||||
"states": [],
|
"states": [],
|
||||||
"title_field": "title",
|
"title_field": "title",
|
||||||
"track_changes": 1
|
"track_changes": 1
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,6 +62,7 @@ class JournalEntry(AccountsController):
|
|||||||
difference: DF.Currency
|
difference: DF.Currency
|
||||||
due_date: DF.Date | None
|
due_date: DF.Date | None
|
||||||
finance_book: DF.Link | None
|
finance_book: DF.Link | None
|
||||||
|
for_all_stock_asset_accounts: DF.Check
|
||||||
from_template: DF.Link | None
|
from_template: DF.Link | None
|
||||||
inter_company_journal_entry_reference: DF.Link | None
|
inter_company_journal_entry_reference: DF.Link | None
|
||||||
is_opening: DF.Literal["No", "Yes"]
|
is_opening: DF.Literal["No", "Yes"]
|
||||||
@@ -73,11 +74,13 @@ class JournalEntry(AccountsController):
|
|||||||
paid_loan: DF.Data | None
|
paid_loan: DF.Data | None
|
||||||
pay_to_recd_from: DF.Data | None
|
pay_to_recd_from: DF.Data | None
|
||||||
payment_order: DF.Link | None
|
payment_order: DF.Link | None
|
||||||
|
periodic_entry_difference_account: DF.Link | None
|
||||||
posting_date: DF.Date
|
posting_date: DF.Date
|
||||||
process_deferred_accounting: DF.Link | None
|
process_deferred_accounting: DF.Link | None
|
||||||
remark: DF.SmallText | None
|
remark: DF.SmallText | None
|
||||||
reversal_of: DF.Link | None
|
reversal_of: DF.Link | None
|
||||||
select_print_heading: DF.Link | None
|
select_print_heading: DF.Link | None
|
||||||
|
stock_asset_account: DF.Link | None
|
||||||
stock_entry: DF.Link | None
|
stock_entry: DF.Link | None
|
||||||
tax_withholding_category: DF.Link | None
|
tax_withholding_category: DF.Link | None
|
||||||
title: DF.Data | None
|
title: DF.Data | None
|
||||||
@@ -101,6 +104,7 @@ class JournalEntry(AccountsController):
|
|||||||
"Opening Entry",
|
"Opening Entry",
|
||||||
"Depreciation Entry",
|
"Depreciation Entry",
|
||||||
"Asset Disposal",
|
"Asset Disposal",
|
||||||
|
"Periodic Accounting Entry",
|
||||||
"Exchange Rate Revaluation",
|
"Exchange Rate Revaluation",
|
||||||
"Exchange Gain Or Loss",
|
"Exchange Gain Or Loss",
|
||||||
"Deferred Revenue",
|
"Deferred Revenue",
|
||||||
@@ -198,6 +202,76 @@ class JournalEntry(AccountsController):
|
|||||||
self.update_inter_company_jv()
|
self.update_inter_company_jv()
|
||||||
self.update_invoice_discounting()
|
self.update_invoice_discounting()
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def get_balance_for_periodic_accounting(self):
|
||||||
|
self.validate_company_for_periodic_accounting()
|
||||||
|
|
||||||
|
stock_accounts = self.get_stock_accounts_for_periodic_accounting()
|
||||||
|
self.set("accounts", [])
|
||||||
|
for account in stock_accounts:
|
||||||
|
account_bal, stock_bal, warehouse_list = get_stock_and_account_balance(
|
||||||
|
account, self.posting_date, self.company
|
||||||
|
)
|
||||||
|
|
||||||
|
difference_value = flt(stock_bal - account_bal, self.precision("difference"))
|
||||||
|
|
||||||
|
if difference_value == 0:
|
||||||
|
frappe.msgprint(
|
||||||
|
_("No difference found for stock account {0}").format(frappe.bold(account)),
|
||||||
|
alert=True,
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
|
||||||
|
self.append(
|
||||||
|
"accounts",
|
||||||
|
{
|
||||||
|
"account": account,
|
||||||
|
"debit_in_account_currency": difference_value if difference_value > 0 else 0,
|
||||||
|
"credit_in_account_currency": abs(difference_value) if difference_value < 0 else 0,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
self.append(
|
||||||
|
"accounts",
|
||||||
|
{
|
||||||
|
"account": self.periodic_entry_difference_account,
|
||||||
|
"credit_in_account_currency": difference_value if difference_value > 0 else 0,
|
||||||
|
"debit_in_account_currency": abs(difference_value) if difference_value < 0 else 0,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
def validate_company_for_periodic_accounting(self):
|
||||||
|
if erpnext.is_perpetual_inventory_enabled(self.company):
|
||||||
|
frappe.throw(
|
||||||
|
_(
|
||||||
|
"Periodic Accounting Entry is not allowed for company {0} with perpetual inventory enabled"
|
||||||
|
).format(self.company)
|
||||||
|
)
|
||||||
|
|
||||||
|
if not self.periodic_entry_difference_account:
|
||||||
|
frappe.throw(_("Please select Periodic Accounting Entry Difference Account"))
|
||||||
|
|
||||||
|
def get_stock_accounts_for_periodic_accounting(self):
|
||||||
|
if self.voucher_type != "Periodic Accounting Entry":
|
||||||
|
return []
|
||||||
|
|
||||||
|
if self.for_all_stock_asset_accounts:
|
||||||
|
return frappe.get_all(
|
||||||
|
"Account",
|
||||||
|
filters={
|
||||||
|
"company": self.company,
|
||||||
|
"account_type": "Stock",
|
||||||
|
"root_type": "Asset",
|
||||||
|
"is_group": 0,
|
||||||
|
},
|
||||||
|
pluck="name",
|
||||||
|
)
|
||||||
|
|
||||||
|
if not self.stock_asset_account:
|
||||||
|
frappe.throw(_("Please select Stock Asset Account"))
|
||||||
|
|
||||||
|
return [self.stock_asset_account]
|
||||||
|
|
||||||
def on_update_after_submit(self):
|
def on_update_after_submit(self):
|
||||||
# Flag will be set on Reconciliation
|
# Flag will be set on Reconciliation
|
||||||
# Reconciliation tool will anyways repost ledger entries. So, no need to check and do implicit repost.
|
# Reconciliation tool will anyways repost ledger entries. So, no need to check and do implicit repost.
|
||||||
@@ -280,6 +354,10 @@ class JournalEntry(AccountsController):
|
|||||||
frappe.throw(_("Account {0} should be of type Expense").format(d.account))
|
frappe.throw(_("Account {0} should be of type Expense").format(d.account))
|
||||||
|
|
||||||
def validate_stock_accounts(self):
|
def validate_stock_accounts(self):
|
||||||
|
if self.voucher_type == "Periodic Accounting Entry":
|
||||||
|
# Skip validation for periodic accounting entry
|
||||||
|
return
|
||||||
|
|
||||||
stock_accounts = get_stock_accounts(self.company, accounts=self.accounts)
|
stock_accounts = get_stock_accounts(self.company, accounts=self.accounts)
|
||||||
for account in stock_accounts:
|
for account in stock_accounts:
|
||||||
account_bal, stock_bal, warehouse_list = get_stock_and_account_balance(
|
account_bal, stock_bal, warehouse_list = get_stock_and_account_balance(
|
||||||
|
|||||||
@@ -2016,6 +2016,75 @@ class TestStockEntry(IntegrationTestCase):
|
|||||||
|
|
||||||
self.assertEqual(se.items[0].basic_rate, 300)
|
self.assertEqual(se.items[0].basic_rate, 300)
|
||||||
|
|
||||||
|
def test_periodic_accounting_entries(self):
|
||||||
|
item_code = "_Test Periodic Accounting Item"
|
||||||
|
make_item(item_code, {"is_stock_item": 1})
|
||||||
|
|
||||||
|
company = "_Test Periodic Accounting Company"
|
||||||
|
|
||||||
|
frappe.get_doc(
|
||||||
|
{
|
||||||
|
"doctype": "Company",
|
||||||
|
"company_name": company,
|
||||||
|
"abbr": "_TPC",
|
||||||
|
"default_currency": "INR",
|
||||||
|
"enable_perpetual_inventory": 0,
|
||||||
|
}
|
||||||
|
).insert(ignore_permissions=True)
|
||||||
|
|
||||||
|
warehouse = frappe.db.get_value("Warehouse", {"company": company, "is_group": 0}, "name")
|
||||||
|
|
||||||
|
make_stock_entry(
|
||||||
|
item_code=item_code,
|
||||||
|
qty=10,
|
||||||
|
to_warehouse=warehouse,
|
||||||
|
basic_rate=100,
|
||||||
|
posting_date=add_days(nowdate(), -2),
|
||||||
|
)
|
||||||
|
|
||||||
|
jv = frappe.new_doc("Journal Entry")
|
||||||
|
jv.voucher_type = "Periodic Accounting Entry"
|
||||||
|
jv.posting_date = add_days(nowdate(), -1)
|
||||||
|
jv.posting_time = nowtime()
|
||||||
|
jv.company = company
|
||||||
|
jv.for_all_stock_asset_accounts = 1
|
||||||
|
jv.periodic_entry_difference_account = "Stock Adjustment - _TPC"
|
||||||
|
jv.get_balance_for_periodic_accounting()
|
||||||
|
jv.save()
|
||||||
|
jv.submit()
|
||||||
|
|
||||||
|
self.assertEqual(len(jv.accounts), 2)
|
||||||
|
self.assertEqual(jv.accounts[0].debit_in_account_currency, 1000)
|
||||||
|
self.assertEqual(jv.accounts[1].credit_in_account_currency, 1000)
|
||||||
|
self.assertEqual(jv.accounts[0].account, "Stock In Hand - _TPC")
|
||||||
|
self.assertEqual(jv.accounts[1].account, "Stock Adjustment - _TPC")
|
||||||
|
|
||||||
|
make_stock_entry(
|
||||||
|
item_code=item_code,
|
||||||
|
qty=5,
|
||||||
|
from_warehouse=warehouse,
|
||||||
|
company=company,
|
||||||
|
posting_date=nowdate(),
|
||||||
|
posting_time=nowtime(),
|
||||||
|
)
|
||||||
|
|
||||||
|
jv = frappe.new_doc("Journal Entry")
|
||||||
|
jv.voucher_type = "Periodic Accounting Entry"
|
||||||
|
jv.posting_date = nowdate()
|
||||||
|
jv.posting_time = nowtime()
|
||||||
|
jv.company = company
|
||||||
|
jv.for_all_stock_asset_accounts = 1
|
||||||
|
jv.periodic_entry_difference_account = "Stock Adjustment - _TPC"
|
||||||
|
jv.get_balance_for_periodic_accounting()
|
||||||
|
jv.save()
|
||||||
|
jv.submit()
|
||||||
|
|
||||||
|
self.assertEqual(len(jv.accounts), 2)
|
||||||
|
self.assertEqual(jv.accounts[0].credit_in_account_currency, 500)
|
||||||
|
self.assertEqual(jv.accounts[1].debit_in_account_currency, 500)
|
||||||
|
self.assertEqual(jv.accounts[0].account, "Stock In Hand - _TPC")
|
||||||
|
self.assertEqual(jv.accounts[1].account, "Stock Adjustment - _TPC")
|
||||||
|
|
||||||
|
|
||||||
def make_serialized_item(self, **args):
|
def make_serialized_item(self, **args):
|
||||||
args = frappe._dict(args)
|
args = frappe._dict(args)
|
||||||
|
|||||||
Reference in New Issue
Block a user