diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 21addab240b..b79af71bef3 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -1485,11 +1485,17 @@ class PurchaseInvoice(BuyingController): if po_details: updated_pr += update_billed_amount_based_on_po(po_details, update_modified) + adjust_incoming_rate = frappe.db.get_single_value( + "Buying Settings", "set_landed_cost_based_on_purchase_invoice_rate" + ) + for pr in set(updated_pr): from erpnext.stock.doctype.purchase_receipt.purchase_receipt import update_billing_percentage pr_doc = frappe.get_doc("Purchase Receipt", pr) - update_billing_percentage(pr_doc, update_modified=update_modified) + update_billing_percentage( + pr_doc, update_modified=update_modified, adjust_incoming_rate=adjust_incoming_rate + ) def get_pr_details_billed_amt(self): # Get billed amount based on purchase receipt item reference (pr_detail) in purchase invoice diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index f901257ccf6..a6d7df6971f 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -1523,6 +1523,94 @@ class TestPurchaseInvoice(unittest.TestCase, StockTestMixin): company.enable_provisional_accounting_for_non_stock_items = 0 company.save() + def test_adjust_incoming_rate(self): + frappe.db.set_single_value("Buying Settings", "maintain_same_rate", 0) + + frappe.db.set_single_value( + "Buying Settings", "set_landed_cost_based_on_purchase_invoice_rate", 1 + ) + + # Increase the cost of the item + + pr = make_purchase_receipt(qty=1, rate=100) + + stock_value_difference = frappe.db.get_value( + "Stock Ledger Entry", + {"voucher_type": "Purchase Receipt", "voucher_no": pr.name}, + "stock_value_difference", + ) + self.assertEqual(stock_value_difference, 100) + + pi = create_purchase_invoice_from_receipt(pr.name) + for row in pi.items: + row.rate = 150 + + pi.save() + pi.submit() + + stock_value_difference = frappe.db.get_value( + "Stock Ledger Entry", + {"voucher_type": "Purchase Receipt", "voucher_no": pr.name}, + "stock_value_difference", + ) + self.assertEqual(stock_value_difference, 150) + + # Reduce the cost of the item + + pr = make_purchase_receipt(qty=1, rate=100) + + stock_value_difference = frappe.db.get_value( + "Stock Ledger Entry", + {"voucher_type": "Purchase Receipt", "voucher_no": pr.name}, + "stock_value_difference", + ) + self.assertEqual(stock_value_difference, 100) + + pi = create_purchase_invoice_from_receipt(pr.name) + for row in pi.items: + row.rate = 50 + + pi.save() + pi.submit() + + stock_value_difference = frappe.db.get_value( + "Stock Ledger Entry", + {"voucher_type": "Purchase Receipt", "voucher_no": pr.name}, + "stock_value_difference", + ) + self.assertEqual(stock_value_difference, 50) + + frappe.db.set_single_value( + "Buying Settings", "set_landed_cost_based_on_purchase_invoice_rate", 0 + ) + + # Don't adjust incoming rate + + pr = make_purchase_receipt(qty=1, rate=100) + + stock_value_difference = frappe.db.get_value( + "Stock Ledger Entry", + {"voucher_type": "Purchase Receipt", "voucher_no": pr.name}, + "stock_value_difference", + ) + self.assertEqual(stock_value_difference, 100) + + pi = create_purchase_invoice_from_receipt(pr.name) + for row in pi.items: + row.rate = 50 + + pi.save() + pi.submit() + + stock_value_difference = frappe.db.get_value( + "Stock Ledger Entry", + {"voucher_type": "Purchase Receipt", "voucher_no": pr.name}, + "stock_value_difference", + ) + self.assertEqual(stock_value_difference, 100) + + frappe.db.set_single_value("Buying Settings", "maintain_same_rate", 1) + def test_item_less_defaults(self): pi = frappe.new_doc("Purchase Invoice") diff --git a/erpnext/buying/doctype/buying_settings/buying_settings.json b/erpnext/buying/doctype/buying_settings/buying_settings.json index 652dcf0d43c..95857e4604d 100644 --- a/erpnext/buying/doctype/buying_settings/buying_settings.json +++ b/erpnext/buying/doctype/buying_settings/buying_settings.json @@ -18,6 +18,7 @@ "pr_required", "column_break_12", "maintain_same_rate", + "set_landed_cost_based_on_purchase_invoice_rate", "allow_multiple_items", "bill_for_rejected_quantity_in_purchase_invoice", "disable_last_purchase_rate", @@ -147,6 +148,14 @@ "fieldname": "show_pay_button", "fieldtype": "Check", "label": "Show Pay Button in Purchase Order Portal" + }, + { + "default": "0", + "depends_on": "eval: !doc.maintain_same_rate", + "description": "Users can enable the checkbox If they want to adjust the incoming rate (set using purchase receipt) based on the purchase invoice rate.", + "fieldname": "set_landed_cost_based_on_purchase_invoice_rate", + "fieldtype": "Check", + "label": "Set Landed Cost Based on Purchase Invoice Rate" } ], "icon": "fa fa-cog", @@ -154,7 +163,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2023-02-15 14:42:10.200679", + "modified": "2023-02-28 15:41:32.686805", "modified_by": "Administrator", "module": "Buying", "name": "Buying Settings", diff --git a/erpnext/buying/doctype/buying_settings/buying_settings.py b/erpnext/buying/doctype/buying_settings/buying_settings.py index be1ebdeb64e..4680a889d3a 100644 --- a/erpnext/buying/doctype/buying_settings/buying_settings.py +++ b/erpnext/buying/doctype/buying_settings/buying_settings.py @@ -21,3 +21,10 @@ class BuyingSettings(Document): self.get("supp_master_name") == "Naming Series", hide_name_field=False, ) + + def before_save(self): + self.check_maintain_same_rate() + + def check_maintain_same_rate(self): + if self.maintain_same_rate: + self.set_landed_cost_based_on_purchase_invoice_rate = 0 diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py index 4f7d9ad92e8..e15b61287eb 100644 --- a/erpnext/controllers/buying_controller.py +++ b/erpnext/controllers/buying_controller.py @@ -265,7 +265,10 @@ class BuyingController(SubcontractingController): ) / qty_in_stock_uom else: item.valuation_rate = ( - item.base_net_amount + item.item_tax_amount + flt(item.landed_cost_voucher_amount) + item.base_net_amount + + item.item_tax_amount + + flt(item.landed_cost_voucher_amount) + + flt(item.get("rate_difference_with_purchase_invoice")) ) / qty_in_stock_uom else: item.valuation_rate = 0.0 diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index c8a4bd3d276..c1abd31bcc1 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -293,6 +293,7 @@ class PurchaseReceipt(BuyingController): get_purchase_document_details, ) + stock_rbnb = None 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) @@ -450,6 +451,21 @@ class PurchaseReceipt(BuyingController): item=d, ) + if d.rate_difference_with_purchase_invoice and stock_rbnb: + account_currency = get_account_currency(stock_rbnb) + self.add_gl_entry( + gl_entries=gl_entries, + account=stock_rbnb, + cost_center=d.cost_center, + debit=0.0, + credit=flt(d.rate_difference_with_purchase_invoice), + remarks=_("Adjustment based on Purchase Invoice rate"), + against_account=warehouse_account_name, + account_currency=account_currency, + project=d.project, + item=d, + ) + # sub-contracting warehouse if flt(d.rm_supp_cost) and warehouse_account.get(self.supplier_warehouse): self.add_gl_entry( @@ -470,6 +486,7 @@ class PurchaseReceipt(BuyingController): + flt(d.landed_cost_voucher_amount) + flt(d.rm_supp_cost) + flt(d.item_tax_amount) + + flt(d.rate_difference_with_purchase_invoice) ) divisional_loss = flt( @@ -765,7 +782,7 @@ class PurchaseReceipt(BuyingController): updated_pr += update_billed_amount_based_on_po(po_details, update_modified) for pr in set(updated_pr): - pr_doc = self if (pr == self.name) else frappe.get_cached_doc("Purchase Receipt", pr) + pr_doc = self if (pr == self.name) else frappe.get_doc("Purchase Receipt", pr) update_billing_percentage(pr_doc, update_modified=update_modified) self.load_from_db() @@ -881,7 +898,7 @@ def get_billed_amount_against_po(po_items): return {d.po_detail: flt(d.billed_amt) for d in query} -def update_billing_percentage(pr_doc, update_modified=True): +def update_billing_percentage(pr_doc, update_modified=True, adjust_incoming_rate=False): # Reload as billed amount was set in db directly pr_doc.load_from_db() @@ -897,6 +914,12 @@ def update_billing_percentage(pr_doc, update_modified=True): total_amount += total_billable_amount total_billed_amount += flt(item.billed_amt) + if adjust_incoming_rate: + adjusted_amt = 0.0 + if item.billed_amt and item.amount: + adjusted_amt = flt(item.billed_amt) - flt(item.amount) + + item.db_set("rate_difference_with_purchase_invoice", adjusted_amt, update_modified=False) percent_billed = round(100 * (total_billed_amount / (total_amount or 1)), 6) pr_doc.db_set("per_billed", percent_billed) @@ -906,6 +929,26 @@ def update_billing_percentage(pr_doc, update_modified=True): pr_doc.set_status(update=True) pr_doc.notify_update() + if adjust_incoming_rate: + adjust_incoming_rate_for_pr(pr_doc) + + +def adjust_incoming_rate_for_pr(doc): + doc.update_valuation_rate(reset_outgoing_rate=False) + + for item in doc.get("items"): + item.db_update() + + doc.docstatus = 2 + doc.update_stock_ledger(allow_negative_stock=True, via_landed_cost_voucher=True) + doc.make_gl_entries_on_cancel() + + # update stock & gl entries for submit state of PR + doc.docstatus = 1 + doc.update_stock_ledger(allow_negative_stock=True, via_landed_cost_voucher=True) + doc.make_gl_entries() + doc.repost_future_sle_and_gle() + def get_item_wise_returned_qty(pr_doc): items = [d.name for d in pr_doc.items] 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 7a350b9e446..cd320fdfcd0 100644 --- a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json +++ b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json @@ -69,6 +69,7 @@ "item_tax_amount", "rm_supp_cost", "landed_cost_voucher_amount", + "rate_difference_with_purchase_invoice", "billed_amt", "warehouse_and_reference", "warehouse", @@ -1007,12 +1008,20 @@ "fieldtype": "Check", "label": "Has Item Scanned", "read_only": 1 + }, + { + "fieldname": "rate_difference_with_purchase_invoice", + "fieldtype": "Currency", + "label": "Rate Difference with Purchase Invoice", + "no_copy": 1, + "print_hide": 1, + "read_only": 1 } ], "idx": 1, "istable": 1, "links": [], - "modified": "2023-01-18 15:48:58.114923", + "modified": "2023-02-28 15:43:04.470104", "modified_by": "Administrator", "module": "Stock", "name": "Purchase Receipt Item",