feat: Provisional accounting for expenses (#29451)
* 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
This commit is contained in:
parent
cb3754a102
commit
528c71382f
@ -548,6 +548,10 @@ class PurchaseInvoice(BuyingController):
|
|||||||
exchange_rate_map, net_rate_map = get_purchase_document_details(self)
|
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'))
|
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 = {}
|
||||||
|
|
||||||
for item in self.get("items"):
|
for item in self.get("items"):
|
||||||
if flt(item.base_net_amount):
|
if flt(item.base_net_amount):
|
||||||
@ -643,19 +647,23 @@ class PurchaseInvoice(BuyingController):
|
|||||||
else:
|
else:
|
||||||
amount = flt(item.base_net_amount + item.item_tax_amount, item.precision("base_net_amount"))
|
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 auto_accounting_for_non_stock_items:
|
|
||||||
service_received_but_not_billed_account = self.get_company_default("service_received_but_not_billed")
|
|
||||||
|
|
||||||
if item.purchase_receipt:
|
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 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
|
||||||
|
|
||||||
# Post reverse entry for Stock-Received-But-Not-Billed if it is booked in 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,
|
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,
|
'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:
|
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():
|
if not self.is_internal_transfer():
|
||||||
gl_entries.append(self.get_gl_dict({
|
gl_entries.append(self.get_gl_dict({
|
||||||
|
@ -11,12 +11,17 @@ from frappe.utils import add_days, cint, flt, getdate, nowdate, today
|
|||||||
import erpnext
|
import erpnext
|
||||||
from erpnext.accounts.doctype.account.test_account import create_account, get_inventory_account
|
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.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.buying.doctype.supplier.test_supplier import create_supplier
|
||||||
from erpnext.controllers.accounts_controller import get_payment_terms
|
from erpnext.controllers.accounts_controller import get_payment_terms
|
||||||
from erpnext.controllers.buying_controller import QtyMismatchError
|
from erpnext.controllers.buying_controller import QtyMismatchError
|
||||||
from erpnext.exceptions import InvalidCurrency
|
from erpnext.exceptions import InvalidCurrency
|
||||||
from erpnext.projects.doctype.project.test_project import make_project
|
from erpnext.projects.doctype.project.test_project import make_project
|
||||||
from erpnext.stock.doctype.item.test_item import create_item
|
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 (
|
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import (
|
||||||
get_taxes,
|
get_taxes,
|
||||||
make_purchase_receipt,
|
make_purchase_receipt,
|
||||||
@ -1147,8 +1152,6 @@ class TestPurchaseInvoice(unittest.TestCase):
|
|||||||
|
|
||||||
def test_purchase_invoice_advance_taxes(self):
|
def test_purchase_invoice_advance_taxes(self):
|
||||||
from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
|
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
|
# create a new supplier to test
|
||||||
supplier = create_supplier(supplier_name = '_Test TDS Advance Supplier',
|
supplier = create_supplier(supplier_name = '_Test TDS Advance Supplier',
|
||||||
@ -1221,6 +1224,45 @@ class TestPurchaseInvoice(unittest.TestCase):
|
|||||||
payment_entry.load_from_db()
|
payment_entry.load_from_db()
|
||||||
self.assertEqual(payment_entry.taxes[0].allocated_amount, 0)
|
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):
|
def check_gl_entries(doc, voucher_no, expected_gle, posting_date):
|
||||||
gl_entries = frappe.db.sql("""select account, debit, credit, posting_date
|
gl_entries = frappe.db.sql("""select account, debit, credit, posting_date
|
||||||
from `tabGL Entry`
|
from `tabGL Entry`
|
||||||
|
@ -40,7 +40,10 @@ class StockController(AccountsController):
|
|||||||
if self.docstatus == 2:
|
if self.docstatus == 2:
|
||||||
make_reverse_gl_entries(voucher_type=self.doctype, voucher_no=self.name)
|
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)
|
warehouse_account = get_warehouse_account_map(self.company)
|
||||||
|
|
||||||
if self.docstatus==1:
|
if self.docstatus==1:
|
||||||
|
@ -328,6 +328,7 @@ erpnext.patches.v13_0.hospitality_deprecation_warning
|
|||||||
erpnext.patches.v13_0.update_exchange_rate_settings
|
erpnext.patches.v13_0.update_exchange_rate_settings
|
||||||
erpnext.patches.v13_0.update_asset_quantity_field
|
erpnext.patches.v13_0.update_asset_quantity_field
|
||||||
erpnext.patches.v13_0.delete_bank_reconciliation_detail
|
erpnext.patches.v13_0.delete_bank_reconciliation_detail
|
||||||
|
erpnext.patches.v13_0.enable_provisional_accounting
|
||||||
|
|
||||||
[post_model_sync]
|
[post_model_sync]
|
||||||
erpnext.patches.v14_0.rename_ongoing_status_in_sla_documents
|
erpnext.patches.v14_0.rename_ongoing_status_in_sla_documents
|
||||||
|
19
erpnext/patches/v13_0/enable_provisional_accounting.py
Normal file
19
erpnext/patches/v13_0/enable_provisional_accounting.py
Normal file
@ -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()
|
@ -3,7 +3,7 @@
|
|||||||
"allow_import": 1,
|
"allow_import": 1,
|
||||||
"allow_rename": 1,
|
"allow_rename": 1,
|
||||||
"autoname": "field:company_name",
|
"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.",
|
"description": "Legal Entity / Subsidiary with a separate Chart of Accounts belonging to the Organization.",
|
||||||
"doctype": "DocType",
|
"doctype": "DocType",
|
||||||
"document_type": "Setup",
|
"document_type": "Setup",
|
||||||
@ -77,13 +77,13 @@
|
|||||||
"default_finance_book",
|
"default_finance_book",
|
||||||
"auto_accounting_for_stock_settings",
|
"auto_accounting_for_stock_settings",
|
||||||
"enable_perpetual_inventory",
|
"enable_perpetual_inventory",
|
||||||
"enable_perpetual_inventory_for_non_stock_items",
|
"enable_provisional_accounting_for_non_stock_items",
|
||||||
"default_inventory_account",
|
"default_inventory_account",
|
||||||
"stock_adjustment_account",
|
"stock_adjustment_account",
|
||||||
"default_in_transit_warehouse",
|
"default_in_transit_warehouse",
|
||||||
"column_break_32",
|
"column_break_32",
|
||||||
"stock_received_but_not_billed",
|
"stock_received_but_not_billed",
|
||||||
"service_received_but_not_billed",
|
"default_provisional_account",
|
||||||
"expenses_included_in_valuation",
|
"expenses_included_in_valuation",
|
||||||
"fixed_asset_defaults",
|
"fixed_asset_defaults",
|
||||||
"accumulated_depreciation_account",
|
"accumulated_depreciation_account",
|
||||||
@ -684,20 +684,6 @@
|
|||||||
"label": "Default Buying Terms",
|
"label": "Default Buying Terms",
|
||||||
"options": "Terms and Conditions"
|
"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",
|
"fieldname": "default_in_transit_warehouse",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
@ -741,6 +727,20 @@
|
|||||||
"fieldname": "section_break_28",
|
"fieldname": "section_break_28",
|
||||||
"fieldtype": "Section Break",
|
"fieldtype": "Section Break",
|
||||||
"label": "Chart of Accounts"
|
"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"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"icon": "fa fa-building",
|
"icon": "fa fa-building",
|
||||||
@ -748,7 +748,7 @@
|
|||||||
"image_field": "company_logo",
|
"image_field": "company_logo",
|
||||||
"is_tree": 1,
|
"is_tree": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2021-10-04 12:09:25.833133",
|
"modified": "2022-01-25 10:33:16.826067",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Setup",
|
"module": "Setup",
|
||||||
"name": "Company",
|
"name": "Company",
|
||||||
@ -809,5 +809,6 @@
|
|||||||
"show_name_in_global_search": 1,
|
"show_name_in_global_search": 1,
|
||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
"sort_order": "ASC",
|
"sort_order": "ASC",
|
||||||
|
"states": [],
|
||||||
"track_changes": 1
|
"track_changes": 1
|
||||||
}
|
}
|
@ -10,6 +10,7 @@ import frappe.defaults
|
|||||||
from frappe import _
|
from frappe import _
|
||||||
from frappe.cache_manager import clear_defaults_cache
|
from frappe.cache_manager import clear_defaults_cache
|
||||||
from frappe.contacts.address_and_contact import load_address_and_contact
|
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 import cint, formatdate, get_timestamp, today
|
||||||
from frappe.utils.nestedset import NestedSet
|
from frappe.utils.nestedset import NestedSet
|
||||||
|
|
||||||
@ -45,7 +46,7 @@ class Company(NestedSet):
|
|||||||
self.validate_currency()
|
self.validate_currency()
|
||||||
self.validate_coa_input()
|
self.validate_coa_input()
|
||||||
self.validate_perpetual_inventory()
|
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_country_change()
|
||||||
self.check_parent_changed()
|
self.check_parent_changed()
|
||||||
self.set_chart_of_accounts()
|
self.set_chart_of_accounts()
|
||||||
@ -187,11 +188,14 @@ class Company(NestedSet):
|
|||||||
frappe.msgprint(_("Set default inventory account for perpetual inventory"),
|
frappe.msgprint(_("Set default inventory account for perpetual inventory"),
|
||||||
alert=True, indicator='orange')
|
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 not self.get("__islocal"):
|
||||||
if cint(self.enable_perpetual_inventory_for_non_stock_items) == 1 and not self.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 perpetual inventory for non stock items").format(
|
frappe.throw(_("Set default {0} account for non stock items").format(
|
||||||
frappe.bold('Service Received But Not Billed')))
|
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):
|
def check_country_change(self):
|
||||||
frappe.flags.country_change = False
|
frappe.flags.country_change = False
|
||||||
|
@ -106,6 +106,8 @@
|
|||||||
"terms",
|
"terms",
|
||||||
"bill_no",
|
"bill_no",
|
||||||
"bill_date",
|
"bill_date",
|
||||||
|
"accounting_details_section",
|
||||||
|
"provisional_expense_account",
|
||||||
"more_info",
|
"more_info",
|
||||||
"project",
|
"project",
|
||||||
"status",
|
"status",
|
||||||
@ -1144,16 +1146,30 @@
|
|||||||
"label": "Represents Company",
|
"label": "Represents Company",
|
||||||
"options": "Company",
|
"options": "Company",
|
||||||
"read_only": 1
|
"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",
|
"icon": "fa fa-truck",
|
||||||
"idx": 261,
|
"idx": 261,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2021-09-28 13:11:10.181328",
|
"modified": "2022-02-01 11:40:52.690984",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Stock",
|
"module": "Stock",
|
||||||
"name": "Purchase Receipt",
|
"name": "Purchase Receipt",
|
||||||
|
"naming_rule": "By \"Naming Series\" field",
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"permissions": [
|
"permissions": [
|
||||||
{
|
{
|
||||||
@ -1214,6 +1230,7 @@
|
|||||||
"show_name_in_global_search": 1,
|
"show_name_in_global_search": 1,
|
||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
"sort_order": "DESC",
|
"sort_order": "DESC",
|
||||||
|
"states": [],
|
||||||
"timeline_field": "supplier",
|
"timeline_field": "supplier",
|
||||||
"title_field": "title",
|
"title_field": "title",
|
||||||
"track_changes": 1
|
"track_changes": 1
|
||||||
|
@ -8,6 +8,7 @@ from frappe.desk.notifications import clear_doctype_notifications
|
|||||||
from frappe.model.mapper import get_mapped_doc
|
from frappe.model.mapper import get_mapped_doc
|
||||||
from frappe.utils import cint, flt, getdate, nowdate
|
from frappe.utils import cint, flt, getdate, nowdate
|
||||||
|
|
||||||
|
import erpnext
|
||||||
from erpnext.accounts.utils import get_account_currency
|
from erpnext.accounts.utils import get_account_currency
|
||||||
from erpnext.assets.doctype.asset.asset import get_asset_account, is_cwip_accounting_enabled
|
from erpnext.assets.doctype.asset.asset import get_asset_account, is_cwip_accounting_enabled
|
||||||
from erpnext.assets.doctype.asset_category.asset_category import get_asset_category_account
|
from erpnext.assets.doctype.asset_category.asset_category import get_asset_category_account
|
||||||
@ -112,6 +113,7 @@ class PurchaseReceipt(BuyingController):
|
|||||||
self.validate_uom_is_integer("uom", ["qty", "received_qty"])
|
self.validate_uom_is_integer("uom", ["qty", "received_qty"])
|
||||||
self.validate_uom_is_integer("stock_uom", "stock_qty")
|
self.validate_uom_is_integer("stock_uom", "stock_qty")
|
||||||
self.validate_cwip_accounts()
|
self.validate_cwip_accounts()
|
||||||
|
self.validate_provisional_expense_account()
|
||||||
|
|
||||||
self.check_on_hold_or_closed_status()
|
self.check_on_hold_or_closed_status()
|
||||||
|
|
||||||
@ -133,6 +135,15 @@ class PurchaseReceipt(BuyingController):
|
|||||||
company = self.company)
|
company = self.company)
|
||||||
break
|
break
|
||||||
|
|
||||||
|
def validate_provisional_expense_account(self):
|
||||||
|
provisional_accounting_for_non_stock_items = \
|
||||||
|
cint(frappe.db.get_value('Company', self.company, 'enable_provisional_accounting_for_non_stock_items'))
|
||||||
|
|
||||||
|
if provisional_accounting_for_non_stock_items:
|
||||||
|
default_provisional_account = self.get_company_default("default_provisional_account")
|
||||||
|
if not self.provisional_expense_account:
|
||||||
|
self.provisional_expense_account = default_provisional_account
|
||||||
|
|
||||||
def validate_with_previous_doc(self):
|
def validate_with_previous_doc(self):
|
||||||
super(PurchaseReceipt, self).validate_with_previous_doc({
|
super(PurchaseReceipt, self).validate_with_previous_doc({
|
||||||
"Purchase Order": {
|
"Purchase Order": {
|
||||||
@ -258,13 +269,15 @@ class PurchaseReceipt(BuyingController):
|
|||||||
get_purchase_document_details,
|
get_purchase_document_details,
|
||||||
)
|
)
|
||||||
|
|
||||||
stock_rbnb = self.get_company_default("stock_received_but_not_billed")
|
if erpnext.is_perpetual_inventory_enabled(self.company):
|
||||||
landed_cost_entries = get_item_account_wise_additional_cost(self.name)
|
stock_rbnb = self.get_company_default("stock_received_but_not_billed")
|
||||||
expenses_included_in_valuation = self.get_company_default("expenses_included_in_valuation")
|
landed_cost_entries = get_item_account_wise_additional_cost(self.name)
|
||||||
auto_accounting_for_non_stock_items = cint(frappe.db.get_value('Company', self.company, 'enable_perpetual_inventory_for_non_stock_items'))
|
expenses_included_in_valuation = self.get_company_default("expenses_included_in_valuation")
|
||||||
|
|
||||||
warehouse_with_no_account = []
|
warehouse_with_no_account = []
|
||||||
stock_items = self.get_stock_items()
|
stock_items = self.get_stock_items()
|
||||||
|
provisional_accounting_for_non_stock_items = \
|
||||||
|
cint(frappe.db.get_value('Company', self.company, 'enable_provisional_accounting_for_non_stock_items'))
|
||||||
|
|
||||||
exchange_rate_map, net_rate_map = get_purchase_document_details(self)
|
exchange_rate_map, net_rate_map = get_purchase_document_details(self)
|
||||||
|
|
||||||
@ -422,43 +435,58 @@ class PurchaseReceipt(BuyingController):
|
|||||||
elif d.warehouse not in warehouse_with_no_account or \
|
elif d.warehouse not in warehouse_with_no_account or \
|
||||||
d.rejected_warehouse not in warehouse_with_no_account:
|
d.rejected_warehouse not in warehouse_with_no_account:
|
||||||
warehouse_with_no_account.append(d.warehouse)
|
warehouse_with_no_account.append(d.warehouse)
|
||||||
elif d.item_code not in stock_items and not d.is_fixed_asset and flt(d.qty) and auto_accounting_for_non_stock_items:
|
elif d.item_code not in stock_items and not d.is_fixed_asset and flt(d.qty) and provisional_accounting_for_non_stock_items:
|
||||||
service_received_but_not_billed_account = self.get_company_default("service_received_but_not_billed")
|
self.add_provisional_gl_entry(d, gl_entries, self.posting_date)
|
||||||
credit_currency = get_account_currency(service_received_but_not_billed_account)
|
|
||||||
debit_currency = get_account_currency(d.expense_account)
|
|
||||||
remarks = self.get("remarks") or _("Accounting Entry for Service")
|
|
||||||
|
|
||||||
self.add_gl_entry(
|
|
||||||
gl_entries=gl_entries,
|
|
||||||
account=service_received_but_not_billed_account,
|
|
||||||
cost_center=d.cost_center,
|
|
||||||
debit=0.0,
|
|
||||||
credit=d.amount,
|
|
||||||
remarks=remarks,
|
|
||||||
against_account=d.expense_account,
|
|
||||||
account_currency=credit_currency,
|
|
||||||
project=d.project,
|
|
||||||
voucher_detail_no=d.name, item=d)
|
|
||||||
|
|
||||||
self.add_gl_entry(
|
|
||||||
gl_entries=gl_entries,
|
|
||||||
account=d.expense_account,
|
|
||||||
cost_center=d.cost_center,
|
|
||||||
debit=d.amount,
|
|
||||||
credit=0.0,
|
|
||||||
remarks=remarks,
|
|
||||||
against_account=service_received_but_not_billed_account,
|
|
||||||
account_currency = debit_currency,
|
|
||||||
project=d.project,
|
|
||||||
voucher_detail_no=d.name,
|
|
||||||
item=d)
|
|
||||||
|
|
||||||
if warehouse_with_no_account:
|
if warehouse_with_no_account:
|
||||||
frappe.msgprint(_("No accounting entries for the following warehouses") + ": \n" +
|
frappe.msgprint(_("No accounting entries for the following warehouses") + ": \n" +
|
||||||
"\n".join(warehouse_with_no_account))
|
"\n".join(warehouse_with_no_account))
|
||||||
|
|
||||||
|
def add_provisional_gl_entry(self, item, gl_entries, posting_date, reverse=0):
|
||||||
|
provisional_expense_account = self.get('provisional_expense_account')
|
||||||
|
credit_currency = get_account_currency(provisional_expense_account)
|
||||||
|
debit_currency = get_account_currency(item.expense_account)
|
||||||
|
expense_account = item.expense_account
|
||||||
|
remarks = self.get("remarks") or _("Accounting Entry for Service")
|
||||||
|
multiplication_factor = 1
|
||||||
|
|
||||||
|
if reverse:
|
||||||
|
multiplication_factor = -1
|
||||||
|
expense_account = frappe.db.get_value('Purchase Receipt Item', {'name': item.get('pr_detail')}, ['expense_account'])
|
||||||
|
|
||||||
|
self.add_gl_entry(
|
||||||
|
gl_entries=gl_entries,
|
||||||
|
account=provisional_expense_account,
|
||||||
|
cost_center=item.cost_center,
|
||||||
|
debit=0.0,
|
||||||
|
credit=multiplication_factor * item.amount,
|
||||||
|
remarks=remarks,
|
||||||
|
against_account=expense_account,
|
||||||
|
account_currency=credit_currency,
|
||||||
|
project=item.project,
|
||||||
|
voucher_detail_no=item.name,
|
||||||
|
item=item,
|
||||||
|
posting_date=posting_date)
|
||||||
|
|
||||||
|
self.add_gl_entry(
|
||||||
|
gl_entries=gl_entries,
|
||||||
|
account=expense_account,
|
||||||
|
cost_center=item.cost_center,
|
||||||
|
debit=multiplication_factor * item.amount,
|
||||||
|
credit=0.0,
|
||||||
|
remarks=remarks,
|
||||||
|
against_account=provisional_expense_account,
|
||||||
|
account_currency = debit_currency,
|
||||||
|
project=item.project,
|
||||||
|
voucher_detail_no=item.name,
|
||||||
|
item=item,
|
||||||
|
posting_date=posting_date)
|
||||||
|
|
||||||
def make_tax_gl_entries(self, gl_entries):
|
def make_tax_gl_entries(self, gl_entries):
|
||||||
expenses_included_in_valuation = self.get_company_default("expenses_included_in_valuation")
|
|
||||||
|
if erpnext.is_perpetual_inventory_enabled(self.company):
|
||||||
|
expenses_included_in_valuation = self.get_company_default("expenses_included_in_valuation")
|
||||||
|
|
||||||
negative_expense_to_be_booked = sum([flt(d.item_tax_amount) for d in self.get('items')])
|
negative_expense_to_be_booked = sum([flt(d.item_tax_amount) for d in self.get('items')])
|
||||||
# Cost center-wise amount breakup for other charges included for valuation
|
# Cost center-wise amount breakup for other charges included for valuation
|
||||||
valuation_tax = {}
|
valuation_tax = {}
|
||||||
@ -515,7 +543,8 @@ class PurchaseReceipt(BuyingController):
|
|||||||
|
|
||||||
def add_gl_entry(self, gl_entries, account, cost_center, debit, credit, remarks, against_account,
|
def add_gl_entry(self, gl_entries, account, cost_center, debit, credit, remarks, against_account,
|
||||||
debit_in_account_currency=None, credit_in_account_currency=None, account_currency=None,
|
debit_in_account_currency=None, credit_in_account_currency=None, account_currency=None,
|
||||||
project=None, voucher_detail_no=None, item=None):
|
project=None, voucher_detail_no=None, item=None, posting_date=None):
|
||||||
|
|
||||||
gl_entry = {
|
gl_entry = {
|
||||||
"account": account,
|
"account": account,
|
||||||
"cost_center": cost_center,
|
"cost_center": cost_center,
|
||||||
@ -534,6 +563,9 @@ class PurchaseReceipt(BuyingController):
|
|||||||
if credit_in_account_currency:
|
if credit_in_account_currency:
|
||||||
gl_entry.update({"credit_in_account_currency": credit_in_account_currency})
|
gl_entry.update({"credit_in_account_currency": credit_in_account_currency})
|
||||||
|
|
||||||
|
if posting_date:
|
||||||
|
gl_entry.update({"posting_date": posting_date})
|
||||||
|
|
||||||
gl_entries.append(self.get_gl_dict(gl_entry, item=item))
|
gl_entries.append(self.get_gl_dict(gl_entry, item=item))
|
||||||
|
|
||||||
def get_asset_gl_entry(self, gl_entries):
|
def get_asset_gl_entry(self, gl_entries):
|
||||||
@ -562,6 +594,7 @@ class PurchaseReceipt(BuyingController):
|
|||||||
# debit cwip account
|
# debit cwip account
|
||||||
debit_in_account_currency = (base_asset_amount
|
debit_in_account_currency = (base_asset_amount
|
||||||
if cwip_account_currency == self.company_currency else asset_amount)
|
if cwip_account_currency == self.company_currency else asset_amount)
|
||||||
|
|
||||||
self.add_gl_entry(
|
self.add_gl_entry(
|
||||||
gl_entries=gl_entries,
|
gl_entries=gl_entries,
|
||||||
account=cwip_account,
|
account=cwip_account,
|
||||||
@ -577,6 +610,7 @@ class PurchaseReceipt(BuyingController):
|
|||||||
# credit arbnb account
|
# credit arbnb account
|
||||||
credit_in_account_currency = (base_asset_amount
|
credit_in_account_currency = (base_asset_amount
|
||||||
if asset_rbnb_currency == self.company_currency else asset_amount)
|
if asset_rbnb_currency == self.company_currency else asset_amount)
|
||||||
|
|
||||||
self.add_gl_entry(
|
self.add_gl_entry(
|
||||||
gl_entries=gl_entries,
|
gl_entries=gl_entries,
|
||||||
account=arbnb_account,
|
account=arbnb_account,
|
||||||
|
@ -1312,58 +1312,6 @@ class TestPurchaseReceipt(ERPNextTestCase):
|
|||||||
self.assertEqual(pr.status, "To Bill")
|
self.assertEqual(pr.status, "To Bill")
|
||||||
self.assertAlmostEqual(pr.per_billed, 50.0, places=2)
|
self.assertAlmostEqual(pr.per_billed, 50.0, places=2)
|
||||||
|
|
||||||
def test_service_item_purchase_with_perpetual_inventory(self):
|
|
||||||
company = '_Test Company with perpetual inventory'
|
|
||||||
service_item = '_Test Non Stock Item'
|
|
||||||
|
|
||||||
before_test_value = frappe.db.get_value(
|
|
||||||
'Company', company, 'enable_perpetual_inventory_for_non_stock_items'
|
|
||||||
)
|
|
||||||
frappe.db.set_value(
|
|
||||||
'Company', company,
|
|
||||||
'enable_perpetual_inventory_for_non_stock_items', 1
|
|
||||||
)
|
|
||||||
srbnb_account = 'Stock Received But Not Billed - TCP1'
|
|
||||||
frappe.db.set_value(
|
|
||||||
'Company', company,
|
|
||||||
'service_received_but_not_billed', srbnb_account
|
|
||||||
)
|
|
||||||
|
|
||||||
pr = make_purchase_receipt(
|
|
||||||
company=company, item=service_item,
|
|
||||||
warehouse='Finished Goods - TCP1', do_not_save=1
|
|
||||||
)
|
|
||||||
item_row_with_diff_rate = frappe.copy_doc(pr.items[0])
|
|
||||||
item_row_with_diff_rate.rate = 100
|
|
||||||
pr.append('items', item_row_with_diff_rate)
|
|
||||||
|
|
||||||
pr.save()
|
|
||||||
pr.submit()
|
|
||||||
|
|
||||||
item_one_gl_entry = frappe.db.get_all("GL Entry", {
|
|
||||||
'voucher_type': pr.doctype,
|
|
||||||
'voucher_no': pr.name,
|
|
||||||
'account': srbnb_account,
|
|
||||||
'voucher_detail_no': pr.items[0].name
|
|
||||||
}, pluck="name")
|
|
||||||
|
|
||||||
item_two_gl_entry = frappe.db.get_all("GL Entry", {
|
|
||||||
'voucher_type': pr.doctype,
|
|
||||||
'voucher_no': pr.name,
|
|
||||||
'account': srbnb_account,
|
|
||||||
'voucher_detail_no': pr.items[1].name
|
|
||||||
}, pluck="name")
|
|
||||||
|
|
||||||
# check if the entries are not merged into one
|
|
||||||
# seperate entries should be made since voucher_detail_no is different
|
|
||||||
self.assertEqual(len(item_one_gl_entry), 1)
|
|
||||||
self.assertEqual(len(item_two_gl_entry), 1)
|
|
||||||
|
|
||||||
frappe.db.set_value(
|
|
||||||
'Company', company,
|
|
||||||
'enable_perpetual_inventory_for_non_stock_items', before_test_value
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_purchase_receipt_with_exchange_rate_difference(self):
|
def test_purchase_receipt_with_exchange_rate_difference(self):
|
||||||
from erpnext.accounts.doctype.purchase_invoice.purchase_invoice import (
|
from erpnext.accounts.doctype.purchase_invoice.purchase_invoice import (
|
||||||
make_purchase_receipt as create_purchase_receipt,
|
make_purchase_receipt as create_purchase_receipt,
|
||||||
|
@ -976,7 +976,7 @@
|
|||||||
"idx": 1,
|
"idx": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2021-11-15 15:46:10.591600",
|
"modified": "2022-02-01 11:32:27.980524",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Stock",
|
"module": "Stock",
|
||||||
"name": "Purchase Receipt Item",
|
"name": "Purchase Receipt Item",
|
||||||
@ -985,5 +985,6 @@
|
|||||||
"permissions": [],
|
"permissions": [],
|
||||||
"quick_entry": 1,
|
"quick_entry": 1,
|
||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
"sort_order": "DESC"
|
"sort_order": "DESC",
|
||||||
|
"states": []
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user