Skip to content

Commit

Permalink
feat: Provisional accounting for expenses (#29451)
Browse files Browse the repository at this point in the history
* feat: Provisonal accounting for expenses

* fix: Method for provisional accounting entry

* chore: Add test case

* fix: Remove test case

* fix: Use company doctype

* fix: Add provisional expense account field in Purchase Receipt Item

* fix: Test case

* fix: Move provisional expense account to parent

* fix: Patch

(cherry picked from commit 528c713)

# Conflicts:
#	erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
#	erpnext/patches.txt
#	erpnext/setup/doctype/company/company.json
#	erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
#	erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
(cherry picked from commit a54c847)

# Conflicts:
#	erpnext/patches.txt
  • Loading branch information
deepeshgarg007 authored and mergify-bot committed Feb 1, 2022
1 parent 8c0613a commit 061a569
Show file tree
Hide file tree
Showing 11 changed files with 289 additions and 66 deletions.
27 changes: 21 additions & 6 deletions erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
Original file line number Diff line number Diff line change
Expand Up @@ -543,6 +543,17 @@ def make_item_gl_entries(self, gl_entries):
if d.category in ('Valuation', 'Total and Valuation')
and flt(d.base_tax_amount_after_discount_amount)]

<<<<<<< HEAD
=======
exchange_rate_map, net_rate_map = get_purchase_document_details(self)

enable_discount_accounting = cint(frappe.db.get_single_value('Accounts Settings', 'enable_discount_accounting'))
provisional_accounting_for_non_stock_items = cint(frappe.db.get_value('Company', self.company, \
'enable_provisional_accounting_for_non_stock_items'))

purchase_receipt_doc_map = {}

>>>>>>> 528c71382f (feat: Provisional accounting for expenses (#29451))
for item in self.get("items"):
if flt(item.base_net_amount):
account_currency = get_account_currency(item.expense_account)
Expand Down Expand Up @@ -637,19 +648,23 @@ def make_item_gl_entries(self, gl_entries):
else:
amount = flt(item.base_net_amount + item.item_tax_amount, item.precision("base_net_amount"))

auto_accounting_for_non_stock_items = cint(frappe.db.get_value('Company', self.company, 'enable_perpetual_inventory_for_non_stock_items'))
if provisional_accounting_for_non_stock_items:
if item.purchase_receipt:
provisional_account = self.get_company_default("default_provisional_account")
purchase_receipt_doc = purchase_receipt_doc_map.get(item.purchase_receipt)

if auto_accounting_for_non_stock_items:
service_received_but_not_billed_account = self.get_company_default("service_received_but_not_billed")
if not purchase_receipt_doc:
purchase_receipt_doc = frappe.get_doc("Purchase Receipt", item.purchase_receipt)
purchase_receipt_doc_map[item.purchase_receipt] = purchase_receipt_doc

if item.purchase_receipt:
# Post reverse entry for Stock-Received-But-Not-Billed if it is booked in Purchase Receipt
expense_booked_in_pr = frappe.db.get_value('GL Entry', {'is_cancelled': 0,
'voucher_type': 'Purchase Receipt', 'voucher_no': item.purchase_receipt, 'voucher_detail_no': item.pr_detail,
'account':service_received_but_not_billed_account}, ['name'])
'account':provisional_account}, ['name'])

if expense_booked_in_pr:
expense_account = service_received_but_not_billed_account
# Intentionally passing purchase invoice item to handle partial billing
purchase_receipt_doc.add_provisional_gl_entry(item, gl_entries, self.posting_date, reverse=1)

if not self.is_internal_transfer():
gl_entries.append(self.get_gl_dict({
Expand Down
46 changes: 44 additions & 2 deletions erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,17 @@
import erpnext
from erpnext.accounts.doctype.account.test_account import create_account, get_inventory_account
from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
from erpnext.buying.doctype.purchase_order.purchase_order import get_mapped_purchase_invoice
from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order
from erpnext.buying.doctype.supplier.test_supplier import create_supplier
from erpnext.controllers.accounts_controller import get_payment_terms
from erpnext.controllers.buying_controller import QtyMismatchError
from erpnext.exceptions import InvalidCurrency
from erpnext.projects.doctype.project.test_project import make_project
from erpnext.stock.doctype.item.test_item import create_item
from erpnext.stock.doctype.purchase_receipt.purchase_receipt import (
make_purchase_invoice as create_purchase_invoice_from_receipt,
)
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import (
get_taxes,
make_purchase_receipt,
Expand Down Expand Up @@ -1124,8 +1129,6 @@ def test_gain_loss_with_advance_entry(self):

def test_purchase_invoice_advance_taxes(self):
from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
from erpnext.buying.doctype.purchase_order.purchase_order import get_mapped_purchase_invoice
from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order

# create a new supplier to test
supplier = create_supplier(supplier_name = '_Test TDS Advance Supplier',
Expand Down Expand Up @@ -1198,6 +1201,45 @@ def test_purchase_invoice_advance_taxes(self):
payment_entry.load_from_db()
self.assertEqual(payment_entry.taxes[0].allocated_amount, 0)

def test_provisional_accounting_entry(self):
item = create_item("_Test Non Stock Item", is_stock_item=0)
provisional_account = create_account(account_name="Provision Account",
parent_account="Current Liabilities - _TC", company="_Test Company")

company = frappe.get_doc('Company', '_Test Company')
company.enable_provisional_accounting_for_non_stock_items = 1
company.default_provisional_account = provisional_account
company.save()

pr = make_purchase_receipt(item_code="_Test Non Stock Item", posting_date=add_days(nowdate(), -2))

pi = create_purchase_invoice_from_receipt(pr.name)
pi.set_posting_time = 1
pi.posting_date = add_days(pr.posting_date, -1)
pi.items[0].expense_account = 'Cost of Goods Sold - _TC'
pi.save()
pi.submit()

# Check GLE for Purchase Invoice
expected_gle = [
['Cost of Goods Sold - _TC', 250, 0, add_days(pr.posting_date, -1)],
['Creditors - _TC', 0, 250, add_days(pr.posting_date, -1)]
]

check_gl_entries(self, pi.name, expected_gle, pi.posting_date)

expected_gle_for_purchase_receipt = [
["Provision Account - _TC", 250, 0, pr.posting_date],
["_Test Account Cost for Goods Sold - _TC", 0, 250, pr.posting_date],
["Provision Account - _TC", 0, 250, pi.posting_date],
["_Test Account Cost for Goods Sold - _TC", 250, 0, pi.posting_date]
]

check_gl_entries(self, pr.name, expected_gle_for_purchase_receipt, pr.posting_date)

company.enable_provisional_accounting_for_non_stock_items = 0
company.save()

def check_gl_entries(doc, voucher_no, expected_gle, posting_date):
gl_entries = frappe.db.sql("""select account, debit, credit, posting_date
from `tabGL Entry`
Expand Down
5 changes: 4 additions & 1 deletion erpnext/controllers/stock_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,10 @@ def make_gl_entries(self, gl_entries=None, from_repost=False):
if self.docstatus == 2:
make_reverse_gl_entries(voucher_type=self.doctype, voucher_no=self.name)

if cint(erpnext.is_perpetual_inventory_enabled(self.company)):
provisional_accounting_for_non_stock_items = \
cint(frappe.db.get_value('Company', self.company, 'enable_provisional_accounting_for_non_stock_items'))

if cint(erpnext.is_perpetual_inventory_enabled(self.company)) or provisional_accounting_for_non_stock_items:
warehouse_account = get_warehouse_account_map(self.company)

if self.docstatus==1:
Expand Down
22 changes: 22 additions & 0 deletions erpnext/patches.txt
Original file line number Diff line number Diff line change
Expand Up @@ -345,4 +345,26 @@ erpnext.patches.v13_0.agriculture_deprecation_warning
erpnext.patches.v13_0.update_maintenance_schedule_field_in_visit
erpnext.patches.v13_0.hospitality_deprecation_warning
erpnext.patches.v13_0.delete_bank_reconciliation_detail
<<<<<<< HEAD
erpnext.patches.v13_0.update_sane_transfer_against
=======
<<<<<<< HEAD
erpnext.patches.v13_0.update_sane_transfer_against
=======
erpnext.patches.v13_0.enable_provisional_accounting

[post_model_sync]
erpnext.patches.v14_0.rename_ongoing_status_in_sla_documents
erpnext.patches.v14_0.add_default_exit_questionnaire_notification_template
erpnext.patches.v14_0.delete_shopify_doctypes
erpnext.patches.v14_0.delete_hub_doctypes
erpnext.patches.v14_0.delete_hospitality_doctypes # 20-01-2022
erpnext.patches.v14_0.delete_agriculture_doctypes
erpnext.patches.v14_0.rearrange_company_fields
erpnext.patches.v14_0.update_leave_notification_template
erpnext.patches.v14_0.restore_einvoice_fields
erpnext.patches.v13_0.update_sane_transfer_against
erpnext.patches.v12_0.add_company_link_to_einvoice_settings
erpnext.patches.v14_0.migrate_cost_center_allocations
>>>>>>> 528c71382f (feat: Provisional accounting for expenses (#29451))
>>>>>>> a54c84728f (feat: Provisional accounting for expenses (#29451))
19 changes: 19 additions & 0 deletions erpnext/patches/v13_0/enable_provisional_accounting.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import frappe


def execute():
frappe.reload_doc("setup", "doctype", "company")

company = frappe.qb.DocType("Company")

frappe.qb.update(
company
).set(
company.enable_provisional_accounting_for_non_stock_items, company.enable_perpetual_inventory_for_non_stock_items
).set(
company.default_provisional_account, company.service_received_but_not_billed
).where(
company.enable_perpetual_inventory_for_non_stock_items == 1
).where(
company.service_received_but_not_billed.isnotnull()
).run()
47 changes: 30 additions & 17 deletions erpnext/setup/doctype/company/company.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"allow_import": 1,
"allow_rename": 1,
"autoname": "field:company_name",
"creation": "2013-04-10 08:35:39",
"creation": "2022-01-25 10:29:55.938239",
"description": "Legal Entity / Subsidiary with a separate Chart of Accounts belonging to the Organization.",
"doctype": "DocType",
"document_type": "Setup",
Expand Down Expand Up @@ -66,12 +66,12 @@
"payment_terms",
"auto_accounting_for_stock_settings",
"enable_perpetual_inventory",
"enable_perpetual_inventory_for_non_stock_items",
"enable_provisional_accounting_for_non_stock_items",
"default_inventory_account",
"stock_adjustment_account",
"column_break_32",
"stock_received_but_not_billed",
"service_received_but_not_billed",
"default_provisional_account",
"expenses_included_in_valuation",
"fixed_asset_defaults",
"accumulated_depreciation_account",
Expand Down Expand Up @@ -692,20 +692,6 @@
"label": "Default Buying Terms",
"options": "Terms and Conditions"
},
{
"fieldname": "service_received_but_not_billed",
"fieldtype": "Link",
"ignore_user_permissions": 1,
"label": "Service Received But Not Billed",
"no_copy": 1,
"options": "Account"
},
{
"default": "0",
"fieldname": "enable_perpetual_inventory_for_non_stock_items",
"fieldtype": "Check",
"label": "Enable Perpetual Inventory For Non Stock Items"
},
{
"fieldname": "default_in_transit_warehouse",
"fieldtype": "Link",
Expand Down Expand Up @@ -735,14 +721,40 @@
"fieldtype": "Link",
"label": "Repair and Maintenance Account",
"options": "Account"
<<<<<<< HEAD
=======
},
{
"fieldname": "section_break_28",
"fieldtype": "Section Break",
"label": "Chart of Accounts"
},
{
"default": "0",
"fieldname": "enable_provisional_accounting_for_non_stock_items",
"fieldtype": "Check",
"label": "Enable Provisional Accounting For Non Stock Items"
},
{
"fieldname": "default_provisional_account",
"fieldtype": "Link",
"ignore_user_permissions": 1,
"label": "Default Provisional Account",
"no_copy": 1,
"options": "Account"
>>>>>>> 528c71382f (feat: Provisional accounting for expenses (#29451))
}
],
"icon": "fa fa-building",
"idx": 1,
"image_field": "company_logo",
"is_tree": 1,
"links": [],
<<<<<<< HEAD
"modified": "2021-12-02 14:52:08.187233",
=======
"modified": "2022-01-25 10:33:16.826067",
>>>>>>> 528c71382f (feat: Provisional accounting for expenses (#29451))
"modified_by": "Administrator",
"module": "Setup",
"name": "Company",
Expand Down Expand Up @@ -802,5 +814,6 @@
"show_name_in_global_search": 1,
"sort_field": "modified",
"sort_order": "ASC",
"states": [],
"track_changes": 1
}
14 changes: 9 additions & 5 deletions erpnext/setup/doctype/company/company.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from frappe import _
from frappe.cache_manager import clear_defaults_cache
from frappe.contacts.address_and_contact import load_address_and_contact
from frappe.custom.doctype.property_setter.property_setter import make_property_setter
from frappe.utils import cint, formatdate, get_timestamp, today
from frappe.utils.nestedset import NestedSet
from past.builtins import cmp
Expand Down Expand Up @@ -47,7 +48,7 @@ def validate(self):
self.validate_currency()
self.validate_coa_input()
self.validate_perpetual_inventory()
self.validate_perpetual_inventory_for_non_stock_items()
self.validate_provisional_account_for_non_stock_items()
self.check_country_change()
self.check_parent_changed()
self.set_chart_of_accounts()
Expand Down Expand Up @@ -189,11 +190,14 @@ def validate_perpetual_inventory(self):
frappe.msgprint(_("Set default inventory account for perpetual inventory"),
alert=True, indicator='orange')

def validate_perpetual_inventory_for_non_stock_items(self):
def validate_provisional_account_for_non_stock_items(self):
if not self.get("__islocal"):
if cint(self.enable_perpetual_inventory_for_non_stock_items) == 1 and not self.service_received_but_not_billed:
frappe.throw(_("Set default {0} account for perpetual inventory for non stock items").format(
frappe.bold('Service Received But Not Billed')))
if cint(self.enable_provisional_accounting_for_non_stock_items) == 1 and not self.default_provisional_account:
frappe.throw(_("Set default {0} account for non stock items").format(
frappe.bold('Provisional Account')))

make_property_setter("Purchase Receipt", "provisional_expense_account", "hidden",
not self.enable_provisional_accounting_for_non_stock_items, "Check", validate_fields_for_doctype=False)

def check_country_change(self):
frappe.flags.country_change = False
Expand Down
19 changes: 18 additions & 1 deletion erpnext/stock/doctype/purchase_receipt/purchase_receipt.json
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,8 @@
"terms",
"bill_no",
"bill_date",
"accounting_details_section",
"provisional_expense_account",
"more_info",
"project",
"status",
Expand Down Expand Up @@ -1144,16 +1146,30 @@
"label": "Represents Company",
"options": "Company",
"read_only": 1
},
{
"collapsible": 1,
"fieldname": "accounting_details_section",
"fieldtype": "Section Break",
"label": "Accounting Details"
},
{
"fieldname": "provisional_expense_account",
"fieldtype": "Link",
"hidden": 1,
"label": "Provisional Expense Account",
"options": "Account"
}
],
"icon": "fa fa-truck",
"idx": 261,
"is_submittable": 1,
"links": [],
"modified": "2021-09-28 13:11:10.181328",
"modified": "2022-02-01 11:40:52.690984",
"modified_by": "Administrator",
"module": "Stock",
"name": "Purchase Receipt",
"naming_rule": "By \"Naming Series\" field",
"owner": "Administrator",
"permissions": [
{
Expand Down Expand Up @@ -1214,6 +1230,7 @@
"show_name_in_global_search": 1,
"sort_field": "modified",
"sort_order": "DESC",
"states": [],
"timeline_field": "supplier",
"title_field": "title",
"track_changes": 1
Expand Down
Loading

0 comments on commit 061a569

Please sign in to comment.