fix: set landed cost based on purchase invoice rate
(cherry picked from commit 17d415b105)
# Conflicts:
# erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py
# erpnext/patches.txt
# erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json
This commit is contained in:
@@ -2464,6 +2464,7 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin):
|
|||||||
|
|
||||||
frappe.db.set_single_value("Buying Settings", "maintain_same_rate", 1)
|
frappe.db.set_single_value("Buying Settings", "maintain_same_rate", 1)
|
||||||
|
|
||||||
|
<<<<<<< HEAD
|
||||||
def test_last_purchase_rate(self):
|
def test_last_purchase_rate(self):
|
||||||
item = create_item("_Test Item For Last Purchase Rate from PI", is_stock_item=1)
|
item = create_item("_Test Item For Last Purchase Rate from PI", is_stock_item=1)
|
||||||
pi1 = make_purchase_invoice(item_code=item.item_code, qty=10, rate=100)
|
pi1 = make_purchase_invoice(item_code=item.item_code, qty=10, rate=100)
|
||||||
@@ -2481,6 +2482,77 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin):
|
|||||||
pi1.cancel()
|
pi1.cancel()
|
||||||
item.reload()
|
item.reload()
|
||||||
self.assertEqual(item.last_purchase_rate, 0)
|
self.assertEqual(item.last_purchase_rate, 0)
|
||||||
|
=======
|
||||||
|
def test_adjust_incoming_rate_from_pi_with_multi_currency_and_partial_billing(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)
|
||||||
|
|
||||||
|
pr = make_purchase_receipt(
|
||||||
|
qty=10, rate=10, currency="USD", do_not_save=1, supplier="_Test Supplier USD"
|
||||||
|
)
|
||||||
|
pr.conversion_rate = 5300
|
||||||
|
pr.save()
|
||||||
|
pr.submit()
|
||||||
|
|
||||||
|
incoming_rate = frappe.db.get_value(
|
||||||
|
"Stock Ledger Entry",
|
||||||
|
{"voucher_type": "Purchase Receipt", "voucher_no": pr.name},
|
||||||
|
"incoming_rate",
|
||||||
|
)
|
||||||
|
self.assertEqual(incoming_rate, 53000) # Asserting to confirm if the default calculation is correct
|
||||||
|
|
||||||
|
pi = create_purchase_invoice_from_receipt(pr.name)
|
||||||
|
for row in pi.items:
|
||||||
|
row.qty = 1
|
||||||
|
|
||||||
|
pi.save()
|
||||||
|
pi.submit()
|
||||||
|
|
||||||
|
incoming_rate = frappe.db.get_value(
|
||||||
|
"Stock Ledger Entry",
|
||||||
|
{"voucher_type": "Purchase Receipt", "voucher_no": pr.name},
|
||||||
|
"incoming_rate",
|
||||||
|
)
|
||||||
|
# Test 1 : Incoming rate should not change as only the qty has changed and not the rate (this was not the case before)
|
||||||
|
self.assertEqual(incoming_rate, 53000)
|
||||||
|
|
||||||
|
pi = create_purchase_invoice_from_receipt(pr.name)
|
||||||
|
for row in pi.items:
|
||||||
|
row.qty = 1
|
||||||
|
row.rate = 9
|
||||||
|
|
||||||
|
pi.save()
|
||||||
|
pi.submit()
|
||||||
|
|
||||||
|
incoming_rate = frappe.db.get_value(
|
||||||
|
"Stock Ledger Entry",
|
||||||
|
{"voucher_type": "Purchase Receipt", "voucher_no": pr.name},
|
||||||
|
"incoming_rate",
|
||||||
|
)
|
||||||
|
# Test 2 : Rate in new PI is lower than PR, so incoming rate should also be lower
|
||||||
|
self.assertEqual(incoming_rate, 50350)
|
||||||
|
|
||||||
|
pi = create_purchase_invoice_from_receipt(pr.name)
|
||||||
|
for row in pi.items:
|
||||||
|
row.qty = 1
|
||||||
|
row.rate = 12
|
||||||
|
|
||||||
|
pi.save()
|
||||||
|
pi.submit()
|
||||||
|
|
||||||
|
incoming_rate = frappe.db.get_value(
|
||||||
|
"Stock Ledger Entry",
|
||||||
|
{"voucher_type": "Purchase Receipt", "voucher_no": pr.name},
|
||||||
|
"incoming_rate",
|
||||||
|
)
|
||||||
|
# Test 3 : Rate in new PI is higher than PR, so incoming rate should also be higher
|
||||||
|
self.assertEqual(incoming_rate, 54766.667)
|
||||||
|
|
||||||
|
frappe.db.set_single_value("Buying Settings", "set_landed_cost_based_on_purchase_invoice_rate", 0)
|
||||||
|
|
||||||
|
frappe.db.set_single_value("Buying Settings", "maintain_same_rate", 1)
|
||||||
|
>>>>>>> 17d415b105 (fix: set landed cost based on purchase invoice rate)
|
||||||
|
|
||||||
def test_opening_invoice_rounding_adjustment_validation(self):
|
def test_opening_invoice_rounding_adjustment_validation(self):
|
||||||
pi = make_purchase_invoice(do_not_save=1)
|
pi = make_purchase_invoice(do_not_save=1)
|
||||||
|
|||||||
@@ -333,7 +333,7 @@ class BuyingController(SubcontractingController):
|
|||||||
net_rate
|
net_rate
|
||||||
+ item.item_tax_amount
|
+ item.item_tax_amount
|
||||||
+ flt(item.landed_cost_voucher_amount)
|
+ flt(item.landed_cost_voucher_amount)
|
||||||
+ flt(item.get("rate_difference_with_purchase_invoice"))
|
+ flt(item.get("amount_difference_with_purchase_invoice"))
|
||||||
) / qty_in_stock_uom
|
) / qty_in_stock_uom
|
||||||
else:
|
else:
|
||||||
item.valuation_rate = 0.0
|
item.valuation_rate = 0.0
|
||||||
|
|||||||
@@ -261,7 +261,11 @@ erpnext.patches.v14_0.show_loan_management_deprecation_warning
|
|||||||
erpnext.patches.v14_0.clear_reconciliation_values_from_singles
|
erpnext.patches.v14_0.clear_reconciliation_values_from_singles
|
||||||
execute:frappe.rename_doc("Report", "TDS Payable Monthly", "Tax Withholding Details", force=True)
|
execute:frappe.rename_doc("Report", "TDS Payable Monthly", "Tax Withholding Details", force=True)
|
||||||
erpnext.patches.v14_0.update_proprietorship_to_individual
|
erpnext.patches.v14_0.update_proprietorship_to_individual
|
||||||
|
<<<<<<< HEAD
|
||||||
erpnext.patches.v15_0.rename_subcontracting_fields
|
erpnext.patches.v15_0.rename_subcontracting_fields
|
||||||
|
=======
|
||||||
|
erpnext.stock.doctype.purchase_receipt_item.patches.rename_field_from_rate_difference_to_amount_difference
|
||||||
|
>>>>>>> 17d415b105 (fix: set landed cost based on purchase invoice rate)
|
||||||
|
|
||||||
[post_model_sync]
|
[post_model_sync]
|
||||||
erpnext.patches.v15_0.create_asset_depreciation_schedules_from_assets
|
erpnext.patches.v15_0.create_asset_depreciation_schedules_from_assets
|
||||||
@@ -393,8 +397,12 @@ erpnext.patches.v15_0.migrate_checkbox_to_select_for_reconciliation_effect
|
|||||||
erpnext.patches.v15_0.sync_auto_reconcile_config
|
erpnext.patches.v15_0.sync_auto_reconcile_config
|
||||||
execute:frappe.db.set_single_value("Accounts Settings", "exchange_gain_loss_posting_date", "Payment")
|
execute:frappe.db.set_single_value("Accounts Settings", "exchange_gain_loss_posting_date", "Payment")
|
||||||
erpnext.patches.v14_0.disable_add_row_in_gross_profit
|
erpnext.patches.v14_0.disable_add_row_in_gross_profit
|
||||||
|
<<<<<<< HEAD
|
||||||
erpnext.patches.v15_0.set_difference_amount_in_asset_value_adjustment
|
erpnext.patches.v15_0.set_difference_amount_in_asset_value_adjustment
|
||||||
erpnext.patches.v14_0.update_posting_datetime
|
erpnext.patches.v14_0.update_posting_datetime
|
||||||
erpnext.stock.doctype.stock_ledger_entry.patches.ensure_sle_indexes
|
erpnext.stock.doctype.stock_ledger_entry.patches.ensure_sle_indexes
|
||||||
erpnext.patches.v15_0.rename_sla_fields
|
erpnext.patches.v15_0.rename_sla_fields
|
||||||
erpnext.patches.v15_0.update_query_report
|
erpnext.patches.v15_0.update_query_report
|
||||||
|
=======
|
||||||
|
erpnext.stock.doctype.purchase_receipt_item.patches.recalculate_amount_difference_field
|
||||||
|
>>>>>>> 17d415b105 (fix: set landed cost based on purchase invoice rate)
|
||||||
|
|||||||
@@ -424,6 +424,14 @@ class PurchaseReceipt(BuyingController):
|
|||||||
self.delete_auto_created_batches()
|
self.delete_auto_created_batches()
|
||||||
self.set_consumed_qty_in_subcontract_order()
|
self.set_consumed_qty_in_subcontract_order()
|
||||||
|
|
||||||
|
def before_cancel(self):
|
||||||
|
super().before_cancel()
|
||||||
|
self.remove_amount_difference_with_purchase_invoice()
|
||||||
|
|
||||||
|
def remove_amount_difference_with_purchase_invoice(self):
|
||||||
|
for item in self.items:
|
||||||
|
item.amount_difference_with_purchase_invoice = 0
|
||||||
|
|
||||||
def get_gl_entries(self, warehouse_account=None, via_landed_cost_voucher=False):
|
def get_gl_entries(self, warehouse_account=None, via_landed_cost_voucher=False):
|
||||||
from erpnext.accounts.general_ledger import process_gl_map
|
from erpnext.accounts.general_ledger import process_gl_map
|
||||||
|
|
||||||
@@ -571,15 +579,15 @@ class PurchaseReceipt(BuyingController):
|
|||||||
item=item,
|
item=item,
|
||||||
)
|
)
|
||||||
|
|
||||||
def make_rate_difference_entry(item):
|
def make_amount_difference_entry(item):
|
||||||
if item.rate_difference_with_purchase_invoice and stock_asset_rbnb:
|
if item.amount_difference_with_purchase_invoice and stock_asset_rbnb:
|
||||||
account_currency = get_account_currency(stock_asset_rbnb)
|
account_currency = get_account_currency(stock_asset_rbnb)
|
||||||
self.add_gl_entry(
|
self.add_gl_entry(
|
||||||
gl_entries=gl_entries,
|
gl_entries=gl_entries,
|
||||||
account=stock_asset_rbnb,
|
account=stock_asset_rbnb,
|
||||||
cost_center=item.cost_center,
|
cost_center=item.cost_center,
|
||||||
debit=0.0,
|
debit=0.0,
|
||||||
credit=flt(item.rate_difference_with_purchase_invoice),
|
credit=flt(item.amount_difference_with_purchase_invoice),
|
||||||
remarks=_("Adjustment based on Purchase Invoice rate"),
|
remarks=_("Adjustment based on Purchase Invoice rate"),
|
||||||
against_account=stock_asset_account_name,
|
against_account=stock_asset_account_name,
|
||||||
account_currency=account_currency,
|
account_currency=account_currency,
|
||||||
@@ -612,7 +620,7 @@ class PurchaseReceipt(BuyingController):
|
|||||||
+ flt(item.landed_cost_voucher_amount)
|
+ flt(item.landed_cost_voucher_amount)
|
||||||
+ flt(item.rm_supp_cost)
|
+ flt(item.rm_supp_cost)
|
||||||
+ flt(item.item_tax_amount)
|
+ flt(item.item_tax_amount)
|
||||||
+ flt(item.rate_difference_with_purchase_invoice)
|
+ flt(item.amount_difference_with_purchase_invoice)
|
||||||
)
|
)
|
||||||
|
|
||||||
divisional_loss = flt(
|
divisional_loss = flt(
|
||||||
@@ -712,7 +720,7 @@ class PurchaseReceipt(BuyingController):
|
|||||||
make_item_asset_inward_gl_entry(d, stock_value_diff, stock_asset_account_name)
|
make_item_asset_inward_gl_entry(d, stock_value_diff, stock_asset_account_name)
|
||||||
outgoing_amount = make_stock_received_but_not_billed_entry(d)
|
outgoing_amount = make_stock_received_but_not_billed_entry(d)
|
||||||
make_landed_cost_gl_entries(d)
|
make_landed_cost_gl_entries(d)
|
||||||
make_rate_difference_entry(d)
|
make_amount_difference_entry(d)
|
||||||
make_sub_contracting_gl_entries(d)
|
make_sub_contracting_gl_entries(d)
|
||||||
make_divisional_loss_gl_entry(d, outgoing_amount)
|
make_divisional_loss_gl_entry(d, outgoing_amount)
|
||||||
elif (d.warehouse and d.warehouse not in warehouse_with_no_account) or (
|
elif (d.warehouse and d.warehouse not in warehouse_with_no_account) or (
|
||||||
@@ -1094,11 +1102,19 @@ def update_billing_percentage(pr_doc, update_modified=True, adjust_incoming_rate
|
|||||||
|
|
||||||
if adjust_incoming_rate:
|
if adjust_incoming_rate:
|
||||||
adjusted_amt = 0.0
|
adjusted_amt = 0.0
|
||||||
if item.billed_amt is not None and item.amount is not None:
|
item_wise_billed_qty = get_billed_qty_against_purchase_receipt(pr_doc)
|
||||||
adjusted_amt = flt(item.billed_amt) - flt(item.amount)
|
|
||||||
|
|
||||||
adjusted_amt = adjusted_amt * flt(pr_doc.conversion_rate)
|
if (
|
||||||
item.db_set("rate_difference_with_purchase_invoice", adjusted_amt, update_modified=False)
|
item.billed_amt is not None
|
||||||
|
and item.amount is not None
|
||||||
|
and item_wise_billed_qty.get(item.name)
|
||||||
|
):
|
||||||
|
adjusted_amt = (
|
||||||
|
flt(item.billed_amt / item_wise_billed_qty.get(item.name)) - flt(item.rate)
|
||||||
|
) * item.qty
|
||||||
|
|
||||||
|
adjusted_amt = flt(adjusted_amt * flt(pr_doc.conversion_rate), item.precision("amount"))
|
||||||
|
item.db_set("amount_difference_with_purchase_invoice", adjusted_amt, update_modified=False)
|
||||||
|
|
||||||
percent_billed = round(100 * (total_billed_amount / (total_amount or 1)), 6)
|
percent_billed = round(100 * (total_billed_amount / (total_amount or 1)), 6)
|
||||||
pr_doc.db_set("per_billed", percent_billed)
|
pr_doc.db_set("per_billed", percent_billed)
|
||||||
@@ -1111,6 +1127,21 @@ def update_billing_percentage(pr_doc, update_modified=True, adjust_incoming_rate
|
|||||||
adjust_incoming_rate_for_pr(pr_doc)
|
adjust_incoming_rate_for_pr(pr_doc)
|
||||||
|
|
||||||
|
|
||||||
|
def get_billed_qty_against_purchase_receipt(pr_doc):
|
||||||
|
pr_names = [d.name for d in pr_doc.items]
|
||||||
|
table = frappe.qb.DocType("Purchase Invoice Item")
|
||||||
|
query = (
|
||||||
|
frappe.qb.from_(table)
|
||||||
|
.select(table.pr_detail, fn.Sum(table.qty).as_("qty"))
|
||||||
|
.where((table.pr_detail.isin(pr_names)) & (table.docstatus == 1))
|
||||||
|
)
|
||||||
|
invoice_data = query.run(as_list=1)
|
||||||
|
|
||||||
|
if not invoice_data:
|
||||||
|
return frappe._dict()
|
||||||
|
return frappe._dict(invoice_data)
|
||||||
|
|
||||||
|
|
||||||
def adjust_incoming_rate_for_pr(doc):
|
def adjust_incoming_rate_for_pr(doc):
|
||||||
doc.update_valuation_rate(reset_outgoing_rate=False)
|
doc.update_valuation_rate(reset_outgoing_rate=False)
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
import frappe
|
||||||
|
from frappe.utils import flt
|
||||||
|
|
||||||
|
from erpnext.stock.doctype.purchase_receipt.purchase_receipt import (
|
||||||
|
adjust_incoming_rate_for_pr,
|
||||||
|
get_billed_qty_against_purchase_receipt,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def execute():
|
||||||
|
table = frappe.qb.DocType("Purchase Receipt Item")
|
||||||
|
query = (
|
||||||
|
frappe.qb.from_(table)
|
||||||
|
.select(table.parent)
|
||||||
|
.distinct()
|
||||||
|
.where((table.amount_difference_with_purchase_invoice > 0) & (table.docstatus == 1))
|
||||||
|
)
|
||||||
|
pr_names = [item.parent for item in query.run(as_dict=True)]
|
||||||
|
|
||||||
|
for pr_name in pr_names:
|
||||||
|
pr_doc = frappe.get_doc("Purchase Receipt", pr_name)
|
||||||
|
for item in pr_doc.items:
|
||||||
|
adjusted_amt = 0.0
|
||||||
|
item_wise_billed_qty = get_billed_qty_against_purchase_receipt(pr_doc)
|
||||||
|
|
||||||
|
if (
|
||||||
|
item.billed_amt is not None
|
||||||
|
and item.amount is not None
|
||||||
|
and item_wise_billed_qty.get(item.name)
|
||||||
|
):
|
||||||
|
adjusted_amt = (
|
||||||
|
flt(item.billed_amt / item_wise_billed_qty.get(item.name)) - flt(item.rate)
|
||||||
|
) * item.qty
|
||||||
|
|
||||||
|
adjusted_amt = flt(adjusted_amt * flt(pr_doc.conversion_rate), item.precision("amount"))
|
||||||
|
item.db_set("amount_difference_with_purchase_invoice", adjusted_amt, update_modified=False)
|
||||||
|
adjust_incoming_rate_for_pr(pr_doc)
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import frappe
|
||||||
|
from frappe.model.utils.rename_field import rename_field
|
||||||
|
|
||||||
|
|
||||||
|
def execute():
|
||||||
|
frappe.db.set_value(
|
||||||
|
"DocField",
|
||||||
|
{"parent": "Purchase Receipt Item", "fieldname": "rate_difference_with_purchase_invoice"},
|
||||||
|
"label",
|
||||||
|
"Amount Difference with Purchase Invoice",
|
||||||
|
)
|
||||||
|
rename_field(
|
||||||
|
"Purchase Receipt Item",
|
||||||
|
"rate_difference_with_purchase_invoice",
|
||||||
|
"amount_difference_with_purchase_invoice",
|
||||||
|
)
|
||||||
|
frappe.clear_cache(doctype="Purchase Receipt Item")
|
||||||
@@ -71,7 +71,7 @@
|
|||||||
"item_tax_amount",
|
"item_tax_amount",
|
||||||
"rm_supp_cost",
|
"rm_supp_cost",
|
||||||
"landed_cost_voucher_amount",
|
"landed_cost_voucher_amount",
|
||||||
"rate_difference_with_purchase_invoice",
|
"amount_difference_with_purchase_invoice",
|
||||||
"billed_amt",
|
"billed_amt",
|
||||||
"warehouse_and_reference",
|
"warehouse_and_reference",
|
||||||
"warehouse",
|
"warehouse",
|
||||||
@@ -998,14 +998,6 @@
|
|||||||
"label": "Has Item Scanned",
|
"label": "Has Item Scanned",
|
||||||
"read_only": 1
|
"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
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"depends_on": "eval:doc.use_serial_batch_fields === 0 || doc.docstatus === 1",
|
"depends_on": "eval:doc.use_serial_batch_fields === 0 || doc.docstatus === 1",
|
||||||
"fieldname": "serial_and_batch_bundle",
|
"fieldname": "serial_and_batch_bundle",
|
||||||
@@ -1135,12 +1127,29 @@
|
|||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"options": "Company:company:default_currency",
|
"options": "Company:company:default_currency",
|
||||||
"print_hide": 1
|
"print_hide": 1
|
||||||
|
<<<<<<< HEAD
|
||||||
|
=======
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "distributed_discount_amount",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"label": "Distributed Discount Amount",
|
||||||
|
"options": "currency"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "amount_difference_with_purchase_invoice",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"label": "Amount Difference with Purchase Invoice",
|
||||||
|
"no_copy": 1,
|
||||||
|
"print_hide": 1,
|
||||||
|
"read_only": 1
|
||||||
|
>>>>>>> 17d415b105 (fix: set landed cost based on purchase invoice rate)
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2024-07-19 12:14:21.521466",
|
"modified": "2025-02-17 13:15:36.692202",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Stock",
|
"module": "Stock",
|
||||||
"name": "Purchase Receipt Item",
|
"name": "Purchase Receipt Item",
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ class PurchaseReceiptItem(Document):
|
|||||||
|
|
||||||
allow_zero_valuation_rate: DF.Check
|
allow_zero_valuation_rate: DF.Check
|
||||||
amount: DF.Currency
|
amount: DF.Currency
|
||||||
|
amount_difference_with_purchase_invoice: DF.Currency
|
||||||
apply_tds: DF.Check
|
apply_tds: DF.Check
|
||||||
asset_category: DF.Link | None
|
asset_category: DF.Link | None
|
||||||
asset_location: DF.Link | None
|
asset_location: DF.Link | None
|
||||||
@@ -76,7 +77,6 @@ class PurchaseReceiptItem(Document):
|
|||||||
qty: DF.Float
|
qty: DF.Float
|
||||||
quality_inspection: DF.Link | None
|
quality_inspection: DF.Link | None
|
||||||
rate: DF.Currency
|
rate: DF.Currency
|
||||||
rate_difference_with_purchase_invoice: DF.Currency
|
|
||||||
rate_with_margin: DF.Currency
|
rate_with_margin: DF.Currency
|
||||||
received_qty: DF.Float
|
received_qty: DF.Float
|
||||||
received_stock_qty: DF.Float
|
received_stock_qty: DF.Float
|
||||||
|
|||||||
Reference in New Issue
Block a user