feat: periodic accounting

This commit is contained in:
Rohit Waghchaure
2025-06-17 16:10:08 +05:30
parent e2c9e24f66
commit 938be22ae9
4 changed files with 228 additions and 5 deletions

View File

@@ -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) {

View File

@@ -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
} }

View File

@@ -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(

View File

@@ -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)