mirror of
https://github.com/frappe/erpnext.git
synced 2026-05-07 07:20:26 +00:00
fix: last purchase rate for purchase invoice
(cherry picked from commit fb9d106633)
# Conflicts:
# erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py
# erpnext/public/js/controllers/transaction.js
# erpnext/stock/doctype/item/item.py
This commit is contained in:
committed by
Mergify
parent
619550942f
commit
ad56f93449
@@ -1960,6 +1960,309 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin):
|
||||
|
||||
self.assertRaises(frappe.ValidationError, dr_note.save)
|
||||
|
||||
<<<<<<< HEAD
|
||||
=======
|
||||
def test_debit_note_without_item(self):
|
||||
pi = make_purchase_invoice(item_name="_Test Item", qty=10, do_not_submit=True)
|
||||
pi.items[0].item_code = ""
|
||||
pi.save()
|
||||
|
||||
self.assertFalse(pi.items[0].item_code)
|
||||
pi.submit()
|
||||
|
||||
return_pi = make_purchase_invoice(
|
||||
item_name="_Test Item",
|
||||
is_return=1,
|
||||
return_against=pi.name,
|
||||
qty=-10,
|
||||
do_not_save=True,
|
||||
)
|
||||
return_pi.items[0].item_code = ""
|
||||
return_pi.save()
|
||||
return_pi.submit()
|
||||
self.assertEqual(return_pi.docstatus, 1)
|
||||
|
||||
def test_purchase_invoice_with_use_serial_batch_field_for_rejected_qty(self):
|
||||
from erpnext.stock.doctype.item.test_item import make_item
|
||||
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
|
||||
|
||||
batch_item = make_item(
|
||||
"_Test Purchase Invoice Batch Item For Rejected Qty",
|
||||
properties={"has_batch_no": 1, "create_new_batch": 1, "is_stock_item": 1},
|
||||
).name
|
||||
|
||||
serial_item = make_item(
|
||||
"_Test Purchase Invoice Serial Item for Rejected Qty",
|
||||
properties={"has_serial_no": 1, "is_stock_item": 1},
|
||||
).name
|
||||
|
||||
rej_warehouse = create_warehouse("_Test Purchase INV Warehouse For Rejected Qty")
|
||||
|
||||
batch_no = "BATCH-PI-BNU-TPRBI-0001"
|
||||
serial_nos = ["SNU-PI-TPRSI-0001", "SNU-PI-TPRSI-0002", "SNU-PI-TPRSI-0003"]
|
||||
|
||||
if not frappe.db.exists("Batch", batch_no):
|
||||
frappe.get_doc(
|
||||
{
|
||||
"doctype": "Batch",
|
||||
"batch_id": batch_no,
|
||||
"item": batch_item,
|
||||
}
|
||||
).insert()
|
||||
|
||||
for serial_no in serial_nos:
|
||||
if not frappe.db.exists("Serial No", serial_no):
|
||||
frappe.get_doc(
|
||||
{
|
||||
"doctype": "Serial No",
|
||||
"item_code": serial_item,
|
||||
"serial_no": serial_no,
|
||||
}
|
||||
).insert()
|
||||
|
||||
pi = make_purchase_invoice(
|
||||
item_code=batch_item,
|
||||
received_qty=10,
|
||||
qty=8,
|
||||
rejected_qty=2,
|
||||
update_stock=1,
|
||||
rejected_warehouse=rej_warehouse,
|
||||
use_serial_batch_fields=1,
|
||||
batch_no=batch_no,
|
||||
rate=100,
|
||||
do_not_submit=1,
|
||||
)
|
||||
|
||||
pi.append(
|
||||
"items",
|
||||
{
|
||||
"item_code": serial_item,
|
||||
"qty": 2,
|
||||
"rate": 100,
|
||||
"base_rate": 100,
|
||||
"item_name": serial_item,
|
||||
"uom": "Nos",
|
||||
"stock_uom": "Nos",
|
||||
"conversion_factor": 1,
|
||||
"rejected_qty": 1,
|
||||
"warehouse": pi.items[0].warehouse,
|
||||
"rejected_warehouse": rej_warehouse,
|
||||
"use_serial_batch_fields": 1,
|
||||
"serial_no": "\n".join(serial_nos[:2]),
|
||||
"rejected_serial_no": serial_nos[2],
|
||||
},
|
||||
)
|
||||
|
||||
pi.save()
|
||||
pi.submit()
|
||||
|
||||
pi.reload()
|
||||
|
||||
for row in pi.items:
|
||||
self.assertTrue(row.serial_and_batch_bundle)
|
||||
self.assertTrue(row.rejected_serial_and_batch_bundle)
|
||||
|
||||
if row.item_code == batch_item:
|
||||
self.assertEqual(row.batch_no, batch_no)
|
||||
else:
|
||||
self.assertEqual(row.serial_no, "\n".join(serial_nos[:2]))
|
||||
self.assertEqual(row.rejected_serial_no, serial_nos[2])
|
||||
|
||||
def test_make_pr_and_pi_from_po(self):
|
||||
from erpnext.assets.doctype.asset.test_asset import create_asset_category
|
||||
|
||||
if not frappe.db.exists("Asset Category", "Computers"):
|
||||
create_asset_category()
|
||||
|
||||
item = create_item(
|
||||
item_code="_Test_Item", is_stock_item=0, is_fixed_asset=1, asset_category="Computers"
|
||||
)
|
||||
po = create_purchase_order(item_code=item.item_code)
|
||||
pr = create_pr_against_po(po.name, 10)
|
||||
pi = make_pi_from_po(po.name)
|
||||
pi.insert()
|
||||
pi.submit()
|
||||
|
||||
pr_gl_entries = frappe.db.sql(
|
||||
"""select account, debit, credit
|
||||
from `tabGL Entry` where voucher_type='Purchase Receipt' and voucher_no=%s
|
||||
order by account asc""",
|
||||
pr.name,
|
||||
as_dict=1,
|
||||
)
|
||||
|
||||
pr_expected_values = [
|
||||
["Asset Received But Not Billed - _TC", 0, 5000],
|
||||
["CWIP Account - _TC", 5000, 0],
|
||||
]
|
||||
|
||||
for i, gle in enumerate(pr_gl_entries):
|
||||
self.assertEqual(pr_expected_values[i][0], gle.account)
|
||||
self.assertEqual(pr_expected_values[i][1], gle.debit)
|
||||
self.assertEqual(pr_expected_values[i][2], gle.credit)
|
||||
|
||||
pi_gl_entries = frappe.db.sql(
|
||||
"""select account, debit, credit
|
||||
from `tabGL Entry` where voucher_type='Purchase Invoice' and voucher_no=%s
|
||||
order by account asc""",
|
||||
pi.name,
|
||||
as_dict=1,
|
||||
)
|
||||
pi_expected_values = [
|
||||
["Asset Received But Not Billed - _TC", 5000, 0],
|
||||
["Creditors - _TC", 0, 5000],
|
||||
]
|
||||
|
||||
for i, gle in enumerate(pi_gl_entries):
|
||||
self.assertEqual(pi_expected_values[i][0], gle.account)
|
||||
self.assertEqual(pi_expected_values[i][1], gle.debit)
|
||||
self.assertEqual(pi_expected_values[i][2], gle.credit)
|
||||
|
||||
def test_adjust_incoming_rate_from_pi_with_multi_currency(self):
|
||||
from erpnext.stock.doctype.landed_cost_voucher.test_landed_cost_voucher import (
|
||||
make_landed_cost_voucher,
|
||||
)
|
||||
|
||||
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=10, rate=1, currency="USD", do_not_save=1, supplier="_Test Supplier USD"
|
||||
)
|
||||
pr.conversion_rate = 6300
|
||||
pr.plc_conversion_rate = 1
|
||||
pr.save()
|
||||
pr.submit()
|
||||
|
||||
self.assertEqual(pr.conversion_rate, 6300)
|
||||
self.assertEqual(pr.plc_conversion_rate, 1)
|
||||
self.assertEqual(pr.base_grand_total, 6300 * 10)
|
||||
|
||||
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, 6300 * 10)
|
||||
|
||||
make_landed_cost_voucher(
|
||||
company=pr.company,
|
||||
receipt_document_type="Purchase Receipt",
|
||||
receipt_document=pr.name,
|
||||
charges=3000,
|
||||
distribute_charges_based_on="Qty",
|
||||
)
|
||||
|
||||
pi = create_purchase_invoice_from_receipt(pr.name)
|
||||
for row in pi.items:
|
||||
row.rate = 1.1
|
||||
|
||||
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, 7230 * 10)
|
||||
|
||||
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)
|
||||
|
||||
def test_opening_invoice_rounding_adjustment_validation(self):
|
||||
pi = make_purchase_invoice(do_not_save=1)
|
||||
pi.items[0].rate = 99.98
|
||||
pi.items[0].qty = 1
|
||||
pi.items[0].expense_account = "Temporary Opening - _TC"
|
||||
pi.is_opening = "Yes"
|
||||
pi.save()
|
||||
self.assertRaises(frappe.ValidationError, pi.submit)
|
||||
|
||||
def _create_opening_roundoff_account(self, company_name):
|
||||
liability_root = frappe.db.get_all(
|
||||
"Account",
|
||||
filters={"company": company_name, "root_type": "Liability", "disabled": 0},
|
||||
order_by="lft",
|
||||
limit=1,
|
||||
)[0]
|
||||
|
||||
# setup round off account
|
||||
if acc := frappe.db.exists(
|
||||
"Account",
|
||||
{
|
||||
"account_name": "Round Off for Opening",
|
||||
"account_type": "Round Off for Opening",
|
||||
"company": company_name,
|
||||
},
|
||||
):
|
||||
frappe.db.set_value("Company", company_name, "round_off_for_opening", acc)
|
||||
else:
|
||||
acc = frappe.new_doc("Account")
|
||||
acc.company = company_name
|
||||
acc.parent_account = liability_root.name
|
||||
acc.account_name = "Round Off for Opening"
|
||||
acc.account_type = "Round Off for Opening"
|
||||
acc.save()
|
||||
frappe.db.set_value("Company", company_name, "round_off_for_opening", acc.name)
|
||||
|
||||
def test_ledger_entries_of_opening_invoice_with_rounding_adjustment(self):
|
||||
pi = make_purchase_invoice(do_not_save=1)
|
||||
pi.items[0].rate = 99.98
|
||||
pi.items[0].qty = 1
|
||||
pi.items[0].expense_account = "Temporary Opening - _TC"
|
||||
pi.is_opening = "Yes"
|
||||
pi.save()
|
||||
self._create_opening_roundoff_account(pi.company)
|
||||
pi.submit()
|
||||
actual = frappe.db.get_all(
|
||||
"GL Entry",
|
||||
filters={"voucher_no": pi.name, "is_opening": "Yes", "is_cancelled": False},
|
||||
fields=["account", "debit", "credit", "is_opening"],
|
||||
order_by="account,debit",
|
||||
)
|
||||
expected = [
|
||||
{"account": "Creditors - _TC", "debit": 0.0, "credit": 100.0, "is_opening": "Yes"},
|
||||
{"account": "Round Off for Opening - _TC", "debit": 0.02, "credit": 0.0, "is_opening": "Yes"},
|
||||
{"account": "Temporary Opening - _TC", "debit": 99.98, "credit": 0.0, "is_opening": "Yes"},
|
||||
]
|
||||
self.assertEqual(len(actual), 3)
|
||||
self.assertEqual(expected, actual)
|
||||
|
||||
def test_last_purchase_rate(self):
|
||||
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)
|
||||
item.reload()
|
||||
self.assertEqual(item.last_purchase_rate, 100)
|
||||
|
||||
pi2 = make_purchase_invoice(item_code=item.item_code, qty=10, rate=200)
|
||||
item.reload()
|
||||
self.assertEqual(item.last_purchase_rate, 200)
|
||||
|
||||
pi2.cancel()
|
||||
item.reload()
|
||||
self.assertEqual(item.last_purchase_rate, 100)
|
||||
|
||||
pi1.cancel()
|
||||
item.reload()
|
||||
self.assertEqual(item.last_purchase_rate, 0)
|
||||
|
||||
|
||||
def set_advance_flag(company, flag, default_account):
|
||||
frappe.db.set_value(
|
||||
"Company",
|
||||
company,
|
||||
{
|
||||
"book_advance_payments_in_separate_party_account": flag,
|
||||
"default_advance_paid_account": default_account,
|
||||
},
|
||||
)
|
||||
|
||||
>>>>>>> fb9d106633 (fix: last purchase rate for purchase invoice)
|
||||
|
||||
def check_gl_entries(
|
||||
doc,
|
||||
|
||||
@@ -633,9 +633,11 @@ class BuyingController(SubcontractingController):
|
||||
if self.get("is_return"):
|
||||
return
|
||||
|
||||
if self.doctype in ["Purchase Order", "Purchase Receipt"] and not frappe.db.get_single_value(
|
||||
"Buying Settings", "disable_last_purchase_rate"
|
||||
):
|
||||
if self.doctype in [
|
||||
"Purchase Order",
|
||||
"Purchase Receipt",
|
||||
"Purchase Invoice",
|
||||
] and not frappe.db.get_single_value("Buying Settings", "disable_last_purchase_rate"):
|
||||
update_last_purchase_rate(self, is_submit=0)
|
||||
|
||||
if self.doctype in ["Purchase Receipt", "Purchase Invoice"]:
|
||||
|
||||
@@ -1143,9 +1143,26 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
||||
};
|
||||
const mappped_fields = mapped_item_field_map[item.doctype] || [];
|
||||
|
||||
<<<<<<< HEAD
|
||||
return mappped_fields
|
||||
.map((field) => item[field])
|
||||
.filter(Boolean).length > 0;
|
||||
=======
|
||||
if (item) {
|
||||
return mappped_fields
|
||||
.map((field) => item[field])
|
||||
.filter(Boolean).length > 0;
|
||||
} else if (this.frm.doc?.items) {
|
||||
let first_row = this.frm.doc.items[0];
|
||||
if (!first_row) {
|
||||
return false
|
||||
};
|
||||
|
||||
let mapped_rows = mappped_fields.filter(d => first_row[d])
|
||||
|
||||
return mapped_rows?.length > 0;
|
||||
}
|
||||
>>>>>>> fb9d106633 (fix: last purchase rate for purchase invoice)
|
||||
}
|
||||
|
||||
batch_no(doc, cdt, cdn) {
|
||||
|
||||
@@ -20,6 +20,7 @@ from frappe.utils import (
|
||||
strip_html,
|
||||
)
|
||||
from frappe.utils.html_utils import clean_html
|
||||
from pypika import Order
|
||||
|
||||
import erpnext
|
||||
from erpnext.controllers.item_variant import (
|
||||
@@ -1060,34 +1061,10 @@ def get_last_purchase_details(item_code, doc_name=None, conversion_rate=1.0):
|
||||
"""returns last purchase details in stock uom"""
|
||||
# get last purchase order item details
|
||||
|
||||
last_purchase_order = frappe.db.sql(
|
||||
"""\
|
||||
select po.name, po.transaction_date, po.conversion_rate,
|
||||
po_item.conversion_factor, po_item.base_price_list_rate,
|
||||
po_item.discount_percentage, po_item.base_rate, po_item.base_net_rate
|
||||
from `tabPurchase Order` po, `tabPurchase Order Item` po_item
|
||||
where po.docstatus = 1 and po_item.item_code = %s and po.name != %s and
|
||||
po.name = po_item.parent
|
||||
order by po.transaction_date desc, po.name desc
|
||||
limit 1""",
|
||||
(item_code, cstr(doc_name)),
|
||||
as_dict=1,
|
||||
)
|
||||
last_purchase_order = get_purchase_voucher_details("Purchase Order", item_code, doc_name)
|
||||
|
||||
# get last purchase receipt item details
|
||||
last_purchase_receipt = frappe.db.sql(
|
||||
"""\
|
||||
select pr.name, pr.posting_date, pr.posting_time, pr.conversion_rate,
|
||||
pr_item.conversion_factor, pr_item.base_price_list_rate, pr_item.discount_percentage,
|
||||
pr_item.base_rate, pr_item.base_net_rate
|
||||
from `tabPurchase Receipt` pr, `tabPurchase Receipt Item` pr_item
|
||||
where pr.docstatus = 1 and pr_item.item_code = %s and pr.name != %s and
|
||||
pr.name = pr_item.parent
|
||||
order by pr.posting_date desc, pr.posting_time desc, pr.name desc
|
||||
limit 1""",
|
||||
(item_code, cstr(doc_name)),
|
||||
as_dict=1,
|
||||
)
|
||||
last_purchase_receipt = get_purchase_voucher_details("Purchase Receipt", item_code, doc_name)
|
||||
|
||||
purchase_order_date = getdate(
|
||||
last_purchase_order and last_purchase_order[0].transaction_date or "1900-01-01"
|
||||
@@ -1108,7 +1085,13 @@ def get_last_purchase_details(item_code, doc_name=None, conversion_rate=1.0):
|
||||
purchase_date = purchase_receipt_date
|
||||
|
||||
else:
|
||||
return frappe._dict()
|
||||
last_purchase_invoice = get_purchase_voucher_details("Purchase Invoice", item_code, doc_name)
|
||||
|
||||
if last_purchase_invoice:
|
||||
last_purchase = last_purchase_invoice[0]
|
||||
purchase_date = getdate(last_purchase.posting_date)
|
||||
else:
|
||||
return frappe._dict()
|
||||
|
||||
conversion_factor = flt(last_purchase.conversion_factor)
|
||||
out = frappe._dict(
|
||||
@@ -1134,6 +1117,7 @@ def get_last_purchase_details(item_code, doc_name=None, conversion_rate=1.0):
|
||||
return out
|
||||
|
||||
|
||||
<<<<<<< HEAD
|
||||
def invalidate_cache_for_item(doc):
|
||||
"""Invalidate Item Group cache and rebuild ItemVariantsCacheManager."""
|
||||
invalidate_cache_for(doc, doc.item_group)
|
||||
@@ -1158,6 +1142,40 @@ def invalidate_item_variants_cache_for_website(doc):
|
||||
if item_code:
|
||||
item_cache = ItemVariantsCacheManager(item_code)
|
||||
item_cache.rebuild_cache()
|
||||
=======
|
||||
def get_purchase_voucher_details(doctype, item_code, document_name):
|
||||
parent_doc = frappe.qb.DocType(doctype)
|
||||
child_doc = frappe.qb.DocType(doctype + " Item")
|
||||
|
||||
query = (
|
||||
frappe.qb.from_(parent_doc)
|
||||
.inner_join(child_doc)
|
||||
.on(parent_doc.name == child_doc.parent)
|
||||
.select(
|
||||
parent_doc.name,
|
||||
parent_doc.conversion_rate,
|
||||
child_doc.conversion_factor,
|
||||
child_doc.base_price_list_rate,
|
||||
child_doc.discount_percentage,
|
||||
child_doc.base_rate,
|
||||
child_doc.base_net_rate,
|
||||
)
|
||||
.where(parent_doc.docstatus == 1)
|
||||
.where(child_doc.item_code == item_code)
|
||||
.where(parent_doc.name != document_name)
|
||||
)
|
||||
|
||||
if doctype in ("Purchase Receipt", "Purchase Invoice"):
|
||||
query = query.select(parent_doc.posting_date, parent_doc.posting_time)
|
||||
query = query.orderby(
|
||||
parent_doc.posting_date, parent_doc.posting_time, parent_doc.name, order=Order.desc
|
||||
)
|
||||
else:
|
||||
query = query.select(parent_doc.transaction_date)
|
||||
query = query.orderby(parent_doc.transaction_date, parent_doc.name, order=Order.desc)
|
||||
|
||||
return query.run(as_dict=1)
|
||||
>>>>>>> fb9d106633 (fix: last purchase rate for purchase invoice)
|
||||
|
||||
|
||||
def check_stock_uom_with_bin(item, stock_uom):
|
||||
|
||||
Reference in New Issue
Block a user