From 061a569f4d57c3470db07757d93e0cabfcdf52ac Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Tue, 1 Feb 2022 14:42:55 +0530 Subject: [PATCH] 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 (cherry picked from commit 528c71382f972ec4464c31c9bc633c179a882072) # 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 a54c84728fcf95c3f5540d576080b7ca39348a65) # Conflicts: # erpnext/patches.txt --- .../purchase_invoice/purchase_invoice.py | 27 ++++- .../purchase_invoice/test_purchase_invoice.py | 46 +++++++- erpnext/controllers/stock_controller.py | 5 +- erpnext/patches.txt | 22 ++++ .../v13_0/enable_provisional_accounting.py | 19 +++ erpnext/setup/doctype/company/company.json | 47 +++++--- erpnext/setup/doctype/company/company.py | 14 ++- .../purchase_receipt/purchase_receipt.json | 19 ++- .../purchase_receipt/purchase_receipt.py | 109 +++++++++++++----- .../purchase_receipt/test_purchase_receipt.py | 42 +++++++ .../purchase_receipt_item.json | 5 +- 11 files changed, 289 insertions(+), 66 deletions(-) create mode 100644 erpnext/patches/v13_0/enable_provisional_accounting.py diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index c5cc05749a75..916cdc58e551 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -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) @@ -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({ diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index d01baebe6360..1d2dcdf27765 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -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, @@ -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', @@ -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` diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index 2912d3eb0bda..8d17683953ec 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -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: diff --git a/erpnext/patches.txt b/erpnext/patches.txt index bcc0c019bf1f..79a02181d54b 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -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)) diff --git a/erpnext/patches/v13_0/enable_provisional_accounting.py b/erpnext/patches/v13_0/enable_provisional_accounting.py new file mode 100644 index 000000000000..8e222700f86c --- /dev/null +++ b/erpnext/patches/v13_0/enable_provisional_accounting.py @@ -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() \ No newline at end of file diff --git a/erpnext/setup/doctype/company/company.json b/erpnext/setup/doctype/company/company.json index dae64e4ad65a..00ceb95bc746 100644 --- a/erpnext/setup/doctype/company/company.json +++ b/erpnext/setup/doctype/company/company.json @@ -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", @@ -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", @@ -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", @@ -735,6 +721,28 @@ "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", @@ -742,7 +750,11 @@ "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", @@ -802,5 +814,6 @@ "show_name_in_global_search": 1, "sort_field": "modified", "sort_order": "ASC", + "states": [], "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/setup/doctype/company/company.py b/erpnext/setup/doctype/company/company.py index 142fe04b6f27..3347935234cd 100644 --- a/erpnext/setup/doctype/company/company.py +++ b/erpnext/setup/doctype/company/company.py @@ -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 @@ -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() @@ -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 diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json index 112ddedac297..b54a90eed355 100755 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json @@ -106,6 +106,8 @@ "terms", "bill_no", "bill_date", + "accounting_details_section", + "provisional_expense_account", "more_info", "project", "status", @@ -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": [ { @@ -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 diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index e7e9e9c1d7fd..218ec583f939 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -9,6 +9,7 @@ from frappe.utils import cint, flt, getdate, nowdate from six import iteritems +import erpnext 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_category.asset_category import get_asset_category_account @@ -113,6 +114,7 @@ def validate(self): self.validate_uom_is_integer("uom", ["qty", "received_qty"]) self.validate_uom_is_integer("stock_uom", "stock_qty") self.validate_cwip_accounts() + self.validate_provisional_expense_account() self.check_on_hold_or_closed_status() @@ -134,6 +136,15 @@ def validate_cwip_accounts(self): company = self.company) 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): super(PurchaseReceipt, self).validate_with_previous_doc({ "Purchase Order": { @@ -255,13 +266,26 @@ def get_gl_entries(self, warehouse_account=None): return process_gl_map(gl_entries) def make_item_gl_entries(self, gl_entries, warehouse_account=None): +<<<<<<< HEAD stock_rbnb = self.get_company_default("stock_received_but_not_billed") landed_cost_entries = get_item_account_wise_additional_cost(self.name) expenses_included_in_valuation = self.get_company_default("expenses_included_in_valuation") auto_accounting_for_non_stock_items = cint(frappe.db.get_value('Company', self.company, 'enable_perpetual_inventory_for_non_stock_items')) +======= + from erpnext.accounts.doctype.purchase_invoice.purchase_invoice import ( + get_purchase_document_details, + ) + + if erpnext.is_perpetual_inventory_enabled(self.company): + stock_rbnb = self.get_company_default("stock_received_but_not_billed") + landed_cost_entries = get_item_account_wise_additional_cost(self.name) + expenses_included_in_valuation = self.get_company_default("expenses_included_in_valuation") +>>>>>>> 528c71382f (feat: Provisional accounting for expenses (#29451)) warehouse_with_no_account = [] 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')) for d in self.get("items"): if d.item_code in stock_items and flt(d.valuation_rate) and flt(d.qty): @@ -384,43 +408,58 @@ def make_item_gl_entries(self, gl_entries, warehouse_account=None): elif d.warehouse not in warehouse_with_no_account or \ d.rejected_warehouse not in warehouse_with_no_account: 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: - service_received_but_not_billed_account = self.get_company_default("service_received_but_not_billed") - 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) + 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: + self.add_provisional_gl_entry(d, gl_entries, self.posting_date) if warehouse_with_no_account: frappe.msgprint(_("No accounting entries for the following warehouses") + ": \n" + "\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): - 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')]) # Cost center-wise amount breakup for other charges included for valuation valuation_tax = {} @@ -477,7 +516,8 @@ def make_tax_gl_entries(self, gl_entries): 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, - project=None, voucher_detail_no=None, item=None): + project=None, voucher_detail_no=None, item=None, posting_date=None): + gl_entry = { "account": account, "cost_center": cost_center, @@ -496,6 +536,9 @@ def add_gl_entry(self, gl_entries, account, cost_center, debit, credit, remarks, if 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)) def get_asset_gl_entry(self, gl_entries): @@ -524,6 +567,7 @@ def add_asset_gl_entries(self, item, gl_entries): # debit cwip account debit_in_account_currency = (base_asset_amount if cwip_account_currency == self.company_currency else asset_amount) + self.add_gl_entry( gl_entries=gl_entries, account=cwip_account, @@ -539,6 +583,7 @@ def add_asset_gl_entries(self, item, gl_entries): # credit arbnb account credit_in_account_currency = (base_asset_amount if asset_rbnb_currency == self.company_currency else asset_amount) + self.add_gl_entry( gl_entries=gl_entries, account=arbnb_account, diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index 6774dafb68cb..5d11955ed907 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -1331,6 +1331,7 @@ def test_po_to_pi_and_po_to_pr_worflow_partial(self): self.assertEqual(pr.status, "To Bill") self.assertAlmostEqual(pr.per_billed, 50.0, places=2) +<<<<<<< HEAD def test_service_item_purchase_with_perpetual_inventory(self): company = '_Test Company with perpetual inventory' service_item = '_Test Non Stock Item' @@ -1355,10 +1356,31 @@ def test_service_item_purchase_with_perpetual_inventory(self): 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) +======= + def test_purchase_receipt_with_exchange_rate_difference(self): + from erpnext.accounts.doctype.purchase_invoice.purchase_invoice import ( + make_purchase_receipt as create_purchase_receipt, + ) + from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import ( + make_purchase_invoice as create_purchase_invoice, + ) + + pi = create_purchase_invoice(company="_Test Company with perpetual inventory", + cost_center = "Main - TCP1", + warehouse = "Stores - TCP1", + expense_account ="_Test Account Cost for Goods Sold - TCP1", + currency = "USD", conversion_rate = 70) + + pr = create_purchase_receipt(pi.name) + pr.conversion_rate = 80 + pr.items[0].purchase_invoice = pi.name + pr.items[0].purchase_invoice_item = pi.items[0].name +>>>>>>> 528c71382f (feat: Provisional accounting for expenses (#29451)) pr.save() pr.submit() +<<<<<<< HEAD item_one_gl_entry = frappe.db.get_all("GL Entry", { 'voucher_type': pr.doctype, 'voucher_no': pr.name, @@ -1383,6 +1405,26 @@ def test_service_item_purchase_with_perpetual_inventory(self): 'enable_perpetual_inventory_for_non_stock_items', before_test_value ) +======= + # Get exchnage gain and loss account + exchange_gain_loss_account = frappe.db.get_value( + 'Company', pr.company, 'exchange_gain_loss_account' + ) + + # fetching the latest GL Entry with exchange gain and loss account account + amount = frappe.db.get_value( + 'GL Entry', + { + 'account': exchange_gain_loss_account, + 'voucher_no': pr.name + }, + 'credit' + ) + discrepancy_caused_by_exchange_rate_diff = abs(pi.items[0].base_net_amount - pr.items[0].base_net_amount) + + self.assertEqual(discrepancy_caused_by_exchange_rate_diff, amount) + +>>>>>>> 528c71382f (feat: Provisional accounting for expenses (#29451)) def test_payment_terms_are_fetched_when_creating_purchase_invoice(self): from erpnext.accounts.doctype.payment_entry.test_payment_entry import ( create_payment_terms_template, diff --git a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json index 30ea1c3cadc2..e5994b2dd48e 100644 --- a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json +++ b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json @@ -976,7 +976,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2021-11-15 15:46:10.591600", + "modified": "2022-02-01 11:32:27.980524", "modified_by": "Administrator", "module": "Stock", "name": "Purchase Receipt Item", @@ -985,5 +985,6 @@ "permissions": [], "quick_entry": 1, "sort_field": "modified", - "sort_order": "DESC" + "sort_order": "DESC", + "states": [] } \ No newline at end of file