# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt

from unittest.mock import patch

import frappe
from frappe.utils import add_days, cint, cstr, flt, get_datetime, getdate, nowtime, today
from pypika import functions as fn

import erpnext
import erpnext.controllers
import erpnext.controllers.status_updater
from erpnext.accounts.doctype.account.test_account import get_inventory_account
from erpnext.buying.doctype.supplier.test_supplier import create_supplier
from erpnext.controllers.accounts_controller import InvalidQtyError
from erpnext.controllers.buying_controller import QtyMismatchError
from erpnext.stock import get_warehouse_account_map
from erpnext.stock.doctype.item.test_item import create_item, make_item
from erpnext.stock.doctype.material_request.material_request import make_purchase_order
from erpnext.stock.doctype.purchase_receipt.purchase_receipt import make_purchase_invoice
from erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle import (
	SerialNoDuplicateError,
	SerialNoExistsInFutureTransactionError,
)
from erpnext.stock.doctype.serial_and_batch_bundle.test_serial_and_batch_bundle import (
	get_batch_from_bundle,
	get_serial_nos_from_bundle,
	make_serial_batch_bundle,
)
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
from erpnext.tests.utils import ERPNextTestSuite


class TestPurchaseReceipt(ERPNextTestSuite):
	def setUp(self):
		frappe.local.future_sle = {}
		self.load_test_records("Purchase Receipt")

	def test_purchase_receipt_skips_validation(self):
		"""
		Test that validation is skipped when over delivery receipt allowance is reduced after PO submission
		and PR can be submitted with higher qty than MR.
		"""
		item = create_item("Test item for validation")
		mr = frappe.new_doc("Material Request")
		mr.material_request_type = "Purchase"
		mr.company = "_Test Company"
		mr.price_list = "_Test Price List"
		mr.append(
			"items",
			{
				"item_code": item.name,
				"item_name": item.item_name,
				"item_group": item.item_group,
				"schedule_date": add_days(today(), 1),
				"qty": 100,
				"uom": item.stock_uom,
			},
		)
		mr.insert()
		mr.submit()
		frappe.db.set_single_value("Buying Settings", "over_order_allowance", 200)
		po = make_purchase_order(mr.name)
		po.supplier = "_Test Supplier"
		po.items[0].qty = 300
		po.save()
		po.submit()
		frappe.db.set_single_value("Buying Settings", "over_order_allowance", 0)
		pr = make_purchase_receipt(qty=300, item_code=item.name, do_not_save=True)
		pr.save()
		pr.submit()

	def test_purchase_receipt_qty(self):
		pr = make_purchase_receipt(qty=0, rejected_qty=0, do_not_save=True)
		with self.assertRaises(InvalidQtyError):
			pr.save()

		# No error with qty=1
		pr.items[0].qty = 1
		pr.save()
		self.assertEqual(pr.items[0].qty, 1)

		# No error with rejected_qty=1
		pr.items[0].rejected_warehouse = "_Test Rejected Warehouse - _TC"
		pr.items[0].rejected_qty = 1
		pr.items[0].qty = 0
		pr.save()
		self.assertEqual(pr.items[0].rejected_qty, 1)

	def test_purchase_receipt_received_qty(self):
		"""
		1. Test if received qty is validated against accepted + rejected
		2. Test if received qty is auto set on save
		"""
		pr = make_purchase_receipt(
			qty=1, rejected_qty=1, received_qty=3, item_code="_Test Item Home Desktop 200", do_not_save=True
		)
		self.assertRaises(QtyMismatchError, pr.save)

		pr.items[0].received_qty = 0
		pr.save()
		self.assertEqual(pr.items[0].received_qty, 2)

		# teardown
		pr.delete()

	def test_reverse_purchase_receipt_sle(self):
		pr = make_purchase_receipt(qty=0.5, item_code="_Test Item Home Desktop 200")

		sl_entry = frappe.db.get_all(
			"Stock Ledger Entry",
			{"voucher_type": "Purchase Receipt", "voucher_no": pr.name},
			["actual_qty"],
		)

		self.assertEqual(len(sl_entry), 1)
		self.assertEqual(sl_entry[0].actual_qty, 0.5)

		pr.cancel()

		sl_entry_cancelled = frappe.db.get_all(
			"Stock Ledger Entry",
			{"voucher_type": "Purchase Receipt", "voucher_no": pr.name},
			["actual_qty"],
			order_by="creation",
		)

		self.assertEqual(len(sl_entry_cancelled), 2)
		self.assertEqual(sl_entry_cancelled[1].actual_qty, -0.5)

	def test_make_purchase_invoice(self):
		from erpnext.accounts.doctype.payment_entry.test_payment_entry import create_payment_term

		create_payment_term("_Test Payment Term 1 for Purchase Invoice")
		create_payment_term("_Test Payment Term 2 for Purchase Invoice")

		if not frappe.db.exists(
			"Payment Terms Template", "_Test Payment Terms Template For Purchase Invoice"
		):
			frappe.get_doc(
				{
					"doctype": "Payment Terms Template",
					"template_name": "_Test Payment Terms Template For Purchase Invoice",
					"allocate_payment_based_on_payment_terms": 1,
					"terms": [
						{
							"doctype": "Payment Terms Template Detail",
							"payment_term": "_Test Payment Term 1 for Purchase Invoice",
							"invoice_portion": 50.00,
							"credit_days_based_on": "Day(s) after invoice date",
							"credit_days": 00,
						},
						{
							"doctype": "Payment Terms Template Detail",
							"payment_term": "_Test Payment Term 2 for Purchase Invoice",
							"invoice_portion": 50.00,
							"credit_days_based_on": "Day(s) after invoice date",
							"credit_days": 30,
						},
					],
				}
			).insert()

		template = frappe.db.get_value(
			"Payment Terms Template", "_Test Payment Terms Template For Purchase Invoice"
		)
		frappe.db.set_value("Supplier", "_Test Supplier", "payment_terms", template)

		pr = make_purchase_receipt(do_not_save=True)
		self.assertRaises(frappe.ValidationError, make_purchase_invoice, pr.name)
		pr.submit()

		pi = make_purchase_invoice(pr.name)

		self.assertEqual(pi.doctype, "Purchase Invoice")
		self.assertEqual(len(pi.get("items")), len(pr.get("items")))

		# test maintaining same rate throughout purchade cycle
		pi.get("items")[0].rate = 200
		self.assertRaises(frappe.ValidationError, frappe.get_doc(pi).submit)

		# test if payment terms are fetched and set in PI
		self.assertEqual(pi.payment_terms_template, template)
		self.assertEqual(pi.payment_schedule[0].payment_amount, flt(pi.grand_total) / 2)
		self.assertEqual(pi.payment_schedule[0].invoice_portion, 50)
		self.assertEqual(pi.payment_schedule[1].payment_amount, flt(pi.grand_total) / 2)
		self.assertEqual(pi.payment_schedule[1].invoice_portion, 50)

	def test_purchase_receipt_no_gl_entry(self):
		from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry

		existing_bin_qty, existing_bin_stock_value = frappe.db.get_value(
			"Bin",
			{"item_code": "_Test Item", "warehouse": "_Test Warehouse - _TC"},
			["actual_qty", "stock_value"],
		)

		if existing_bin_qty < 0:
			make_stock_entry(
				item_code="_Test Item", target="_Test Warehouse - _TC", qty=abs(existing_bin_qty)
			)

		existing_bin_qty, existing_bin_stock_value = frappe.db.get_value(
			"Bin",
			{"item_code": "_Test Item", "warehouse": "_Test Warehouse - _TC"},
			["actual_qty", "stock_value"],
		)

		pr = make_purchase_receipt()

		stock_value_difference = frappe.db.get_value(
			"Stock Ledger Entry",
			{
				"voucher_type": "Purchase Receipt",
				"voucher_no": pr.name,
				"item_code": "_Test Item",
				"warehouse": "_Test Warehouse - _TC",
			},
			"stock_value_difference",
		)

		self.assertEqual(stock_value_difference, 250)

		current_bin_stock_value = frappe.db.get_value(
			"Bin", {"item_code": "_Test Item", "warehouse": "_Test Warehouse - _TC"}, "stock_value"
		)
		self.assertEqual(current_bin_stock_value, existing_bin_stock_value + 250)

		self.assertFalse(get_gl_entries("Purchase Receipt", pr.name))

		pr.cancel()

	def test_batched_serial_no_purchase(self):
		item = frappe.db.exists("Item", {"item_name": "Batched Serialized Item"})
		if not item:
			item = create_item("Batched Serialized Item")
			item.has_batch_no = 1
			item.create_new_batch = 1
			item.has_serial_no = 1
			item.batch_number_series = "BS-BATCH-.##"
			item.serial_no_series = "BS-.####"
			item.save()
		else:
			item = frappe.get_doc("Item", {"item_name": "Batched Serialized Item"})

		pr = make_purchase_receipt(item_code=item.name, qty=5, rate=500)

		self.assertTrue(frappe.db.get_value("Batch", {"item": item.name, "reference_name": pr.name}))

		pr.load_from_db()
		pr.cancel()

	def test_duplicate_serial_nos(self):
		from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note

		item = frappe.db.exists("Item", {"item_name": "Test Serialized Item 123"})
		if not item:
			item = create_item("Test Serialized Item 123")
			item.has_serial_no = 1
			item.serial_no_series = "TSI123-.####"
			item.save()
		else:
			item = frappe.get_doc("Item", {"item_name": "Test Serialized Item 123"})

		# First make purchase receipt
		pr = make_purchase_receipt(item_code=item.name, qty=2, rate=500)
		pr.load_from_db()

		bundle_id = frappe.db.get_value(
			"Stock Ledger Entry",
			{"voucher_type": "Purchase Receipt", "voucher_no": pr.name, "item_code": item.name},
			"serial_and_batch_bundle",
		)

		serial_nos = get_serial_nos_from_bundle(bundle_id)

		self.assertEqual(get_serial_nos_from_bundle(pr.items[0].serial_and_batch_bundle), serial_nos)

		bundle_id = make_serial_batch_bundle(
			frappe._dict(
				{
					"item_code": item.item_code,
					"warehouse": "_Test Warehouse 2 - _TC1",
					"company": "_Test Company 1",
					"qty": 2,
					"voucher_type": "Purchase Receipt",
					"serial_nos": serial_nos,
					"posting_date": today(),
					"posting_time": nowtime(),
					"do_not_save": True,
				}
			)
		)

		self.assertRaises(SerialNoDuplicateError, bundle_id.make_serial_and_batch_bundle)

		# Then made delivery note to remove the serial nos from stock
		dn = create_delivery_note(item_code=item.name, qty=2, rate=1500, serial_no=serial_nos)
		dn.load_from_db()
		self.assertEqual(get_serial_nos_from_bundle(dn.items[0].serial_and_batch_bundle), serial_nos)

		posting_date = add_days(today(), -3)

		# Try to receive same serial nos again in the same company with backdated.
		bundle_id = make_serial_batch_bundle(
			frappe._dict(
				{
					"item_code": item.item_code,
					"warehouse": "_Test Warehouse - _TC",
					"company": "_Test Company",
					"qty": 2,
					"rate": 500,
					"voucher_type": "Purchase Receipt",
					"serial_nos": serial_nos,
					"posting_date": posting_date,
					"posting_time": nowtime(),
					"do_not_save": True,
				}
			)
		)

		self.assertRaises(SerialNoExistsInFutureTransactionError, bundle_id.make_serial_and_batch_bundle)

		# Try to receive same serial nos with different company with backdated.
		bundle_id = make_serial_batch_bundle(
			frappe._dict(
				{
					"item_code": item.item_code,
					"warehouse": "_Test Warehouse 2 - _TC1",
					"company": "_Test Company 1",
					"qty": 2,
					"rate": 500,
					"voucher_type": "Purchase Receipt",
					"serial_nos": serial_nos,
					"posting_date": posting_date,
					"posting_time": nowtime(),
					"do_not_save": True,
				}
			)
		)

		self.assertRaises(SerialNoExistsInFutureTransactionError, bundle_id.make_serial_and_batch_bundle)

		# Receive the same serial nos after the delivery note posting date and time
		make_purchase_receipt(item_code=item.name, qty=2, rate=500, serial_no=serial_nos)

		# Raise the error for backdated deliver note entry cancel
		# self.assertRaises(SerialNoExistsInFutureTransactionError, dn.cancel)

	def test_purchase_receipt_gl_entry(self):
		pr = make_purchase_receipt(
			company="_Test Company with perpetual inventory",
			warehouse="Stores - TCP1",
			supplier_warehouse="Work In Progress - TCP1",
			get_multiple_items=True,
			get_taxes_and_charges=True,
		)

		self.assertEqual(cint(erpnext.is_perpetual_inventory_enabled(pr.company)), 1)

		gl_entries = get_gl_entries("Purchase Receipt", pr.name)

		self.assertTrue(gl_entries)

		stock_in_hand_account = get_inventory_account(pr.company, pr.items[0].warehouse)
		fixed_asset_account = get_inventory_account(pr.company, pr.items[1].warehouse)

		if stock_in_hand_account == fixed_asset_account:
			expected_values = {
				stock_in_hand_account: [750.0, 0.0],
				"Stock Received But Not Billed - TCP1": [0.0, 500.0],
				"_Test Account Shipping Charges - TCP1": [0.0, 100.0],
				"_Test Account Customs Duty - TCP1": [0.0, 150.0],
			}
		else:
			expected_values = {
				stock_in_hand_account: [375.0, 0.0],
				fixed_asset_account: [375.0, 0.0],
				"Stock Received But Not Billed - TCP1": [0.0, 500.0],
				"_Test Account Shipping Charges - TCP1": [0.0, 250.0],
			}
		for gle in gl_entries:
			self.assertEqual(expected_values[gle.account][0], gle.debit)
			self.assertEqual(expected_values[gle.account][1], gle.credit)

		pr.cancel()
		self.assertTrue(get_gl_entries("Purchase Receipt", pr.name))

	def test_serial_no_warehouse(self):
		pr = make_purchase_receipt(item_code="_Test Serialized Item With Series", qty=1)
		pr_row_1_serial_no = get_serial_nos_from_bundle(pr.get("items")[0].serial_and_batch_bundle)[0]

		self.assertEqual(
			frappe.db.get_value("Serial No", pr_row_1_serial_no, "warehouse"), pr.get("items")[0].warehouse
		)

		pr.cancel()
		self.assertFalse(frappe.db.get_value("Serial No", pr_row_1_serial_no, "warehouse"))

	def test_rejected_warehouse_filter(self):
		pr = frappe.copy_doc(self.globalTestRecords["Purchase Receipt"][0])
		pr.get("items")[0].item_code = "_Test Serialized Item With Series"
		pr.get("items")[0].qty = 3
		pr.get("items")[0].rejected_qty = 2
		pr.get("items")[0].received_qty = 5
		pr.get("items")[0].rejected_warehouse = pr.get("items")[0].warehouse
		self.assertRaises(frappe.ValidationError, pr.save)

	def test_rejected_serial_no(self):
		pr = frappe.copy_doc(self.globalTestRecords["Purchase Receipt"][0])
		pr.get("items")[0].item_code = "_Test Serialized Item With Series"
		pr.get("items")[0].qty = 3
		pr.get("items")[0].rejected_qty = 2
		pr.get("items")[0].received_qty = 5
		pr.get("items")[0].rejected_warehouse = "_Test Rejected Warehouse - _TC"
		pr.insert()
		pr.submit()
		pr.load_from_db()

		accepted_serial_nos = get_serial_nos_from_bundle(pr.get("items")[0].serial_and_batch_bundle)
		self.assertEqual(len(accepted_serial_nos), 3)
		for serial_no in accepted_serial_nos:
			self.assertEqual(
				frappe.db.get_value("Serial No", serial_no, "warehouse"), pr.get("items")[0].warehouse
			)

		rejected_serial_nos = get_serial_nos_from_bundle(pr.get("items")[0].rejected_serial_and_batch_bundle)
		self.assertEqual(len(rejected_serial_nos), 2)
		for serial_no in rejected_serial_nos:
			self.assertEqual(
				frappe.db.get_value("Serial No", serial_no, "warehouse"),
				pr.get("items")[0].rejected_warehouse,
			)

		pr.cancel()

	def test_purchase_return_partial(self):
		pr = make_purchase_receipt(
			company="_Test Company with perpetual inventory",
			warehouse="Stores - TCP1",
			supplier_warehouse="Work In Progress - TCP1",
		)

		return_pr = make_purchase_receipt(
			company="_Test Company with perpetual inventory",
			warehouse="Stores - TCP1",
			supplier_warehouse="Work In Progress - TCP1",
			is_return=1,
			return_against=pr.name,
			qty=-2,
			do_not_submit=1,
		)
		return_pr.items[0].purchase_receipt_item = pr.items[0].name
		return_pr.submit()

		# check sle
		outgoing_rate = frappe.db.get_value(
			"Stock Ledger Entry",
			{"voucher_type": "Purchase Receipt", "voucher_no": return_pr.name},
			"outgoing_rate",
		)

		self.assertEqual(outgoing_rate, 50)

		# check gl entries for return
		gl_entries = get_gl_entries("Purchase Receipt", return_pr.name)

		self.assertTrue(gl_entries)
		stock_in_hand_account = get_inventory_account(return_pr.company)

		expected_values = {
			stock_in_hand_account: [0.0, 100.0],
			"Stock Received But Not Billed - TCP1": [100.0, 0.0],
		}

		for gle in gl_entries:
			self.assertEqual(expected_values[gle.account][0], gle.debit)
			self.assertEqual(expected_values[gle.account][1], gle.credit)

		# hack because new_doc isn't considering is_return portion of status_updater
		returned = frappe.get_doc("Purchase Receipt", return_pr.name)
		returned.update_prevdoc_status()
		pr.load_from_db()

		# Check if Original PR updated
		self.assertEqual(pr.items[0].returned_qty, 2)
		self.assertEqual(pr.per_returned, 40)
		self.assertEqual(returned.status, "Return")

		from erpnext.controllers.sales_and_purchase_return import make_return_doc

		return_pr_2 = make_return_doc("Purchase Receipt", pr.name)

		# Check if unreturned amount is mapped in 2nd return
		self.assertEqual(return_pr_2.items[0].qty, -3)

		# Make PI against unreturned amount
		buying_settings = frappe.get_single("Buying Settings")
		buying_settings.bill_for_rejected_quantity_in_purchase_invoice = 0
		buying_settings.save()

		pi = make_purchase_invoice(pr.name)
		pi.submit()

		self.assertEqual(pi.items[0].qty, 3)

		buying_settings.bill_for_rejected_quantity_in_purchase_invoice = 1
		buying_settings.save()

		pr.load_from_db()
		# PR should be completed on billing all unreturned amount
		self.assertEqual(pr.items[0].billed_amt, 150)
		self.assertEqual(pr.per_billed, 100)
		self.assertEqual(pr.status, "Completed")

		pi.load_from_db()
		pi.cancel()

		pr.load_from_db()
		self.assertEqual(pr.per_billed, 0)

		return_pr.cancel()
		pr.cancel()

	def test_purchase_return_full(self):
		pr = make_purchase_receipt(
			company="_Test Company with perpetual inventory",
			warehouse="Stores - TCP1",
			supplier_warehouse="Work In Progress - TCP1",
		)

		return_pr = make_purchase_receipt(
			company="_Test Company with perpetual inventory",
			warehouse="Stores - TCP1",
			supplier_warehouse="Work In Progress - TCP1",
			is_return=1,
			return_against=pr.name,
			qty=-5,
			do_not_submit=1,
		)
		return_pr.items[0].purchase_receipt_item = pr.items[0].name
		return_pr.submit()

		# hack because new_doc isn't considering is_return portion of status_updater
		returned = frappe.get_doc("Purchase Receipt", return_pr.name)
		returned.update_prevdoc_status()
		pr.load_from_db()

		# Check if Original PR updated
		self.assertEqual(pr.items[0].returned_qty, 5)
		self.assertEqual(pr.per_returned, 100)
		self.assertEqual(pr.status, "Return Issued")

		return_pr.cancel()
		pr.cancel()

	def test_purchase_return_for_rejected_qty(self):
		from erpnext.stock.doctype.warehouse.test_warehouse import get_warehouse

		rejected_warehouse = "_Test Rejected Warehouse - TCP1"
		if not frappe.db.exists("Warehouse", rejected_warehouse):
			get_warehouse(
				company="_Test Company with perpetual inventory",
				abbr=" - TCP1",
				warehouse_name="_Test Rejected Warehouse",
			).name

		pr = make_purchase_receipt(
			company="_Test Company with perpetual inventory",
			warehouse="Stores - TCP1",
			supplier_warehouse="Work In Progress - TCP1",
			qty=2,
			rejected_qty=2,
			rejected_warehouse=rejected_warehouse,
		)

		return_pr = make_purchase_receipt(
			company="_Test Company with perpetual inventory",
			warehouse="Stores - TCP1",
			supplier_warehouse="Work In Progress - TCP1",
			is_return=1,
			return_against=pr.name,
			qty=-2,
			rejected_qty=-2,
			rejected_warehouse=rejected_warehouse,
		)

		actual_qty = frappe.db.get_value(
			"Stock Ledger Entry",
			{
				"voucher_type": "Purchase Receipt",
				"voucher_no": return_pr.name,
				"warehouse": return_pr.items[0].rejected_warehouse,
			},
			"actual_qty",
		)

		self.assertEqual(actual_qty, -2)

		return_pr.cancel()
		pr.cancel()

	def test_purchase_receipt_for_rejected_gle_without_accepted_warehouse(self):
		from erpnext.stock.doctype.warehouse.test_warehouse import get_warehouse

		rejected_warehouse = "_Test Rejected Warehouse - TCP1"
		if not frappe.db.exists("Warehouse", rejected_warehouse):
			get_warehouse(
				company="_Test Company with perpetual inventory",
				abbr=" - TCP1",
				warehouse_name="_Test Rejected Warehouse",
			).name

		pr = make_purchase_receipt(
			company="_Test Company with perpetual inventory",
			warehouse="Stores - TCP1",
			received_qty=2,
			rejected_qty=2,
			rejected_warehouse=rejected_warehouse,
			do_not_save=True,
		)

		pr.items[0].qty = 0.0
		pr.items[0].warehouse = ""
		pr.submit()

		actual_qty = frappe.db.get_value(
			"Stock Ledger Entry",
			{
				"voucher_type": "Purchase Receipt",
				"voucher_no": pr.name,
				"warehouse": pr.items[0].rejected_warehouse,
				"is_cancelled": 0,
			},
			"actual_qty",
		)

		self.assertEqual(actual_qty, 2)
		self.assertFalse(pr.items[0].warehouse)
		pr.cancel()

	def test_purchase_return_for_serialized_items(self):
		def _check_serial_no_values(serial_no, field_values):
			serial_no = frappe.get_doc("Serial No", serial_no)
			for field, value in field_values.items():
				self.assertEqual(cstr(serial_no.get(field)), value)

		pr = make_purchase_receipt(item_code="_Test Serialized Item With Series", qty=1)

		serial_no = get_serial_nos_from_bundle(pr.get("items")[0].serial_and_batch_bundle)[0]

		_check_serial_no_values(serial_no, {"warehouse": "_Test Warehouse - _TC"})

		return_pr = make_purchase_receipt(
			item_code="_Test Serialized Item With Series",
			qty=-1,
			is_return=1,
			return_against=pr.name,
			serial_no=[serial_no],
		)

		_check_serial_no_values(
			serial_no,
			{"warehouse": ""},
		)

		return_pr.cancel()
		pr.reload()
		pr.cancel()

	def test_purchase_return_for_multi_uom(self):
		item_code = "_Test Purchase Return For Multi-UOM"
		if not frappe.db.exists("Item", item_code):
			item = make_item(item_code, {"stock_uom": "Box"})
			row = item.append("uoms", {"uom": "Unit", "conversion_factor": 0.1})
			row.db_update()

		pr = make_purchase_receipt(item_code=item_code, qty=1, uom="Box", conversion_factor=1.0)
		return_pr = make_purchase_receipt(
			item_code=item_code,
			qty=-10,
			uom="Unit",
			stock_uom="Box",
			conversion_factor=0.1,
			is_return=1,
			return_against=pr.name,
		)

		self.assertEqual(abs(return_pr.items[0].stock_qty), 1.0)

		return_pr.cancel()
		pr.cancel()

	def test_closed_purchase_receipt(self):
		from erpnext.stock.doctype.purchase_receipt.purchase_receipt import (
			update_purchase_receipt_status,
		)

		item = make_item()

		pr = make_purchase_receipt(item_code=item.name)

		update_purchase_receipt_status(pr.name, "Closed")
		self.assertEqual(frappe.db.get_value("Purchase Receipt", pr.name, "status"), "Closed")

	def test_purchase_return_against_closed_purchase_order(self):
		from erpnext.buying.doctype.purchase_order.purchase_order import (
			make_purchase_receipt as make_pr_from_po,
		)
		from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order
		from erpnext.controllers.sales_and_purchase_return import make_return_doc

		po = create_purchase_order(qty=2, rate=100)

		receipts = []
		for _ in range(2):
			pr = make_pr_from_po(po.name)
			pr.items[0].qty = pr.items[0].received_qty = 1
			pr.submit()
			receipts.append(pr)

		first_return = make_return_doc("Purchase Receipt", receipts[0].name)
		first_return.submit()

		po.reload()
		po.update_status("Closed")

		# a return against a closed Purchase Order should still go through,
		# the same way a Delivery Note return does against a closed Sales Order
		second_return = make_return_doc("Purchase Receipt", receipts[1].name)
		second_return.submit()

		self.assertEqual(second_return.docstatus, 1)
		self.assertEqual(frappe.db.get_value("Purchase Order", po.name, "status"), "Closed")

		# cancelling the return runs the same check on the closed order
		second_return.cancel()

		# a regular receipt against the closed order must still be blocked
		blocked_pr = make_pr_from_po(po.name)
		self.assertRaisesRegex(frappe.InvalidStatusError, "Closed", blocked_pr.save)

	def test_pr_billing_status(self):
		"""Flow:
		1. PO -> PR1 -> PI
		2. PO -> PI
		3. PO -> PR2.
		"""
		from erpnext.buying.doctype.purchase_order.purchase_order import (
			make_purchase_invoice as make_purchase_invoice_from_po,
		)
		from erpnext.buying.doctype.purchase_order.purchase_order import make_purchase_receipt
		from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order

		frappe.flags.print_test_messages = False
		# Qty: 10, Rate: 500
		po = create_purchase_order()

		pr1 = make_purchase_receipt(po.name)
		pr1.posting_date = today()
		pr1.posting_time = "10:00"
		pr1.get("items")[0].received_qty = 2
		pr1.get("items")[0].qty = 2
		pr1.submit()

		pi1 = make_purchase_invoice(pr1.name)
		pi1.submit()

		pr1.load_from_db()
		self.assertEqual(pr1.per_billed, 100)

		pi2 = make_purchase_invoice_from_po(po.name)
		pi2.get("items")[0].qty = 4
		pi2.submit()

		frappe.flags.print_test_messages = True
		pr2 = make_purchase_receipt(po.name)
		pr2.posting_date = today()
		pr2.posting_time = "08:00"
		pr2.get("items")[0].received_qty = 5
		pr2.get("items")[0].qty = 5
		pr2.submit()

		pr1.load_from_db()
		self.assertEqual(pr1.get("items")[0].billed_amt, 1000)
		self.assertEqual(pr1.per_billed, 100)
		self.assertEqual(pr1.status, "Completed")

		pr2.load_from_db()
		self.assertEqual(pr2.get("items")[0].billed_amt, 2000)
		self.assertEqual(pr2.per_billed, 80)
		self.assertEqual(pr2.status, "Partly Billed")

		pr2.cancel()
		pi2.reload()
		pi2.cancel()
		pi1.reload()
		pi1.cancel()
		pr1.reload()
		pr1.cancel()
		po.reload()
		po.cancel()

	def test_pr_billing_status_for_po_invoice_across_multiple_receipts(self):
		"""When a Purchase Invoice is raised directly from a PO and the invoiced qty
		spans more than one Purchase Receipt, the billed amount must be split between
		the receipts (FIFO), not duplicated. A receipt with no amount left to consume
		must not show as fully billed / Completed.

		Flow:
		1. PO (Qty: 10, Rate: 500) -> PI for Qty 5 (Amount 2500)
		2. PO -> PR1 (Qty 3) -> gets 1500 billed (fully billed)
		3. PO -> PR2 (Qty 3) -> gets the remaining 1000 billed (partly billed)
		"""
		from erpnext.buying.doctype.purchase_order.purchase_order import (
			make_purchase_invoice as make_purchase_invoice_from_po,
		)
		from erpnext.buying.doctype.purchase_order.purchase_order import make_purchase_receipt
		from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order

		# Qty: 10, Rate: 500
		po = create_purchase_order()

		pi = make_purchase_invoice_from_po(po.name)
		pi.get("items")[0].qty = 5
		pi.submit()

		pr1 = make_purchase_receipt(po.name)
		pr1.posting_date = today()
		pr1.posting_time = "08:00"
		pr1.get("items")[0].received_qty = 3
		pr1.get("items")[0].qty = 3
		pr1.submit()

		pr2 = make_purchase_receipt(po.name)
		pr2.posting_date = today()
		pr2.posting_time = "10:00"
		pr2.get("items")[0].received_qty = 3
		pr2.get("items")[0].qty = 3
		pr2.submit()

		# PR1 consumes 3 * 500 = 1500 out of the 2500 invoiced -> fully billed.
		pr1.load_from_db()
		self.assertEqual(pr1.get("items")[0].billed_amt, 1500)
		self.assertEqual(pr1.per_billed, 100)
		self.assertEqual(pr1.status, "Completed")

		# PR2 must only get the remaining 1000 (not 1500 again) -> partly billed.
		pr2.load_from_db()
		self.assertEqual(pr2.get("items")[0].billed_amt, 1000)
		self.assertEqual(flt(pr2.per_billed, 2), 66.67)
		self.assertEqual(pr2.status, "Partly Billed")

		from erpnext.patches.v16_0 import recalculate_purchase_receipt_billing_status

		purchase_order_item = po.items[0].name
		with patch.object(
			recalculate_purchase_receipt_billing_status,
			"get_candidate_purchase_order_items",
			return_value=[purchase_order_item],
		):
			self.assertEqual(
				recalculate_purchase_receipt_billing_status.get_affected_purchase_order_items(), []
			)

			frappe.db.set_value(
				"Purchase Receipt Item",
				pr2.items[0].name,
				"billed_amt",
				1500,
				update_modified=False,
			)
			frappe.db.set_value(
				"Purchase Receipt",
				pr2.name,
				{"per_billed": 100, "status": "Completed"},
				update_modified=False,
			)

			self.assertEqual(
				recalculate_purchase_receipt_billing_status.get_affected_purchase_order_items(),
				[purchase_order_item],
			)
			recalculate_purchase_receipt_billing_status.execute()
			self.assertEqual(
				recalculate_purchase_receipt_billing_status.get_affected_purchase_order_items(), []
			)

		pr2.load_from_db()
		self.assertEqual(pr2.get("items")[0].billed_amt, 1000)
		self.assertEqual(flt(pr2.per_billed, 2), 66.67)
		self.assertEqual(pr2.status, "Partly Billed")

		pr2.cancel()
		pr1.reload()
		pr1.cancel()
		pi.reload()
		pi.cancel()
		po.reload()
		po.cancel()

	def test_billing_repair_patch_skips_invoice_created_receipts(self):
		"""A Purchase Receipt created from a Purchase Invoice keeps billed_amt = amount
		by definition. When such a receipt coexists with a receipt made directly from
		the PO, the stored total can exceed the PO-invoiced amount, but the repair
		patch must leave those PO Items alone instead of stripping the invoice-created
		receipt.

		Flow:
		1. PO (Qty: 10, Rate: 500) -> PI for Qty 5 (Amount 2500)
		2. PO -> PR1 (Qty 5, direct) -> absorbs the full 2500 (fully billed)
		3. PI -> PR2 (Qty 5, created from the invoice) -> billed 2500 via invoice link
		"""
		from erpnext.accounts.doctype.purchase_invoice.purchase_invoice import (
			make_purchase_receipt as make_purchase_receipt_from_pi,
		)
		from erpnext.buying.doctype.purchase_order.purchase_order import (
			make_purchase_invoice as make_purchase_invoice_from_po,
		)
		from erpnext.buying.doctype.purchase_order.purchase_order import make_purchase_receipt
		from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order

		po = create_purchase_order()

		pi = make_purchase_invoice_from_po(po.name)
		pi.get("items")[0].qty = 5
		pi.submit()

		pr_direct = make_purchase_receipt(po.name)
		pr_direct.get("items")[0].received_qty = 5
		pr_direct.get("items")[0].qty = 5
		pr_direct.submit()

		pr_direct.load_from_db()
		self.assertEqual(pr_direct.get("items")[0].billed_amt, 2500)
		self.assertEqual(pr_direct.per_billed, 100)

		pr_from_invoice = make_purchase_receipt_from_pi(pi.name)
		pr_from_invoice.submit()

		pr_from_invoice.load_from_db()
		self.assertEqual(pr_from_invoice.get("items")[0].billed_amt, 2500)
		self.assertEqual(pr_from_invoice.per_billed, 100)

		from erpnext.patches.v16_0 import recalculate_purchase_receipt_billing_status

		purchase_order_item = po.items[0].name
		with patch.object(
			recalculate_purchase_receipt_billing_status,
			"get_candidate_purchase_order_items",
			return_value=[purchase_order_item],
		):
			self.assertEqual(
				recalculate_purchase_receipt_billing_status.get_affected_purchase_order_items(), []
			)
			recalculate_purchase_receipt_billing_status.execute()

		pr_direct.load_from_db()
		self.assertEqual(pr_direct.get("items")[0].billed_amt, 2500)
		pr_from_invoice.load_from_db()
		self.assertEqual(pr_from_invoice.get("items")[0].billed_amt, 2500)
		self.assertEqual(pr_from_invoice.status, "Completed")

		pr_from_invoice.cancel()
		pr_direct.reload()
		pr_direct.cancel()
		pi.reload()
		pi.cancel()
		po.reload()
		po.cancel()

	def test_serial_no_against_purchase_receipt(self):
		item_code = "Test Manual Created Serial No"
		if not frappe.db.exists("Item", item_code):
			make_item(item_code, dict(has_serial_no=1))

		serial_no = ["12903812901"]
		if not frappe.db.exists("Serial No", serial_no[0]):
			frappe.get_doc(
				{
					"doctype": "Serial No",
					"item_code": item_code,
					"serial_no": serial_no[0],
					"company": "_Test Company",
				}
			).insert()

		pr_doc = make_purchase_receipt(item_code=item_code, qty=1, serial_no=serial_no)
		pr_doc.load_from_db()

		bundle_id = pr_doc.items[0].serial_and_batch_bundle
		self.assertEqual(serial_no[0], get_serial_nos_from_bundle(bundle_id)[0])

		voucher_no = frappe.db.get_value("Serial and Batch Bundle", bundle_id, "voucher_no")

		self.assertEqual(voucher_no, pr_doc.name)
		pr_doc.cancel()

		# check for the auto created serial nos
		item_code = "Test Auto Created Serial No"
		if not frappe.db.exists("Item", item_code):
			make_item(item_code, dict(has_serial_no=1, serial_no_series="KLJL.###"))

		new_pr_doc = make_purchase_receipt(item_code=item_code, qty=1)
		new_pr_doc.load_from_db()

		bundle_id = new_pr_doc.items[0].serial_and_batch_bundle
		serial_no = get_serial_nos_from_bundle(bundle_id)[0]
		self.assertTrue(serial_no)

		voucher_no = frappe.db.get_value("Serial and Batch Bundle", bundle_id, "voucher_no")

		self.assertEqual(voucher_no, new_pr_doc.name)

		new_pr_doc.cancel()

	def test_auto_asset_creation(self):
		asset_item = "Test Asset Item"

		if not frappe.db.exists("Item", asset_item):
			asset_category = frappe.get_all("Asset Category")

			if asset_category:
				asset_category = asset_category[0].name

			if not asset_category:
				doc = frappe.get_doc(
					{
						"doctype": "Asset Category",
						"asset_category_name": "Test Asset Category",
						"depreciation_method": "Straight Line",
						"total_number_of_depreciations": 12,
						"frequency_of_depreciation": 1,
						"accounts": [
							{
								"company_name": "_Test Company",
								"fixed_asset_account": "_Test Fixed Asset - _TC",
								"accumulated_depreciation_account": "_Test Accumulated Depreciations - _TC",
								"depreciation_expense_account": "_Test Depreciations - _TC",
							}
						],
					}
				).insert()

				asset_category = doc.name

			item_data = make_item(
				asset_item,
				{
					"is_stock_item": 0,
					"stock_uom": "Box",
					"is_fixed_asset": 1,
					"auto_create_assets": 1,
					"asset_category": asset_category,
					"asset_naming_series": "ABC.###",
				},
			)
			asset_item = item_data.item_code

		pr = make_purchase_receipt(item_code=asset_item, qty=3)
		assets = frappe.db.get_all("Asset", filters={"purchase_receipt": pr.name})

		self.assertEqual(len(assets), 3)

		location = frappe.db.get_value("Asset", assets[0].name, "location")
		self.assertEqual(location, "Test Location")

		pr.cancel()

	def test_purchase_return_with_submitted_asset(self):
		from erpnext.stock.doctype.purchase_receipt.purchase_receipt import make_purchase_return

		pr = make_purchase_receipt(item_code="Test Asset Item", qty=1)

		asset = frappe.get_doc("Asset", {"purchase_receipt": pr.name})
		asset.available_for_use_date = frappe.utils.nowdate()
		asset.net_purchase_amount = 50.0
		asset.append(
			"finance_books",
			{
				"expected_value_after_useful_life": 10,
				"depreciation_method": "Straight Line",
				"total_number_of_depreciations": 3,
				"frequency_of_depreciation": 1,
			},
		)
		asset.submit()

		pr_return = make_purchase_return(pr.name)
		self.assertRaises(frappe.exceptions.ValidationError, pr_return.submit)

		asset.load_from_db()
		asset.cancel()

		pr_return.submit()

		pr_return.cancel()
		pr.cancel()

	def test_purchase_receipt_cost_center(self):
		from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center

		cost_center = "_Test Cost Center for BS Account - TCP1"
		create_cost_center(
			cost_center_name="_Test Cost Center for BS Account",
			company="_Test Company with perpetual inventory",
		)

		if not frappe.db.exists("Location", "Test Location"):
			frappe.get_doc({"doctype": "Location", "location_name": "Test Location"}).insert()

		pr = make_purchase_receipt(
			cost_center=cost_center,
			company="_Test Company with perpetual inventory",
			warehouse="Stores - TCP1",
			supplier_warehouse="Work In Progress - TCP1",
		)

		stock_in_hand_account = get_inventory_account(pr.company, pr.get("items")[0].warehouse)
		gl_entries = get_gl_entries("Purchase Receipt", pr.name)

		self.assertTrue(gl_entries)

		expected_values = {
			"Stock Received But Not Billed - TCP1": {"cost_center": cost_center},
			stock_in_hand_account: {"cost_center": cost_center},
		}
		for _i, gle in enumerate(gl_entries):
			self.assertEqual(expected_values[gle.account]["cost_center"], gle.cost_center)

		pr.cancel()

	def test_purchase_receipt_cost_center_with_balance_sheet_account(self):
		if not frappe.db.exists("Location", "Test Location"):
			frappe.get_doc({"doctype": "Location", "location_name": "Test Location"}).insert()

		pr = make_purchase_receipt(
			company="_Test Company with perpetual inventory",
			warehouse="Stores - TCP1",
			supplier_warehouse="Work In Progress - TCP1",
		)

		stock_in_hand_account = get_inventory_account(pr.company, pr.get("items")[0].warehouse)
		gl_entries = get_gl_entries("Purchase Receipt", pr.name)

		self.assertTrue(gl_entries)
		cost_center = pr.get("items")[0].cost_center

		expected_values = {
			"Stock Received But Not Billed - TCP1": {"cost_center": cost_center},
			stock_in_hand_account: {"cost_center": cost_center},
		}
		for _i, gle in enumerate(gl_entries):
			self.assertEqual(expected_values[gle.account]["cost_center"], gle.cost_center)

		pr.cancel()

	def test_make_purchase_invoice_from_pr_for_returned_qty(self):
		from erpnext.buying.doctype.purchase_order.test_purchase_order import (
			create_pr_against_po,
			create_purchase_order,
		)

		frappe.db.set_single_value("Buying Settings", "bill_for_rejected_quantity_in_purchase_invoice", 0)

		po = create_purchase_order()
		pr = create_pr_against_po(po.name)

		pr1 = make_purchase_receipt(qty=-1, is_return=1, return_against=pr.name, do_not_submit=True)
		pr1.items[0].purchase_order = po.name
		pr1.items[0].purchase_order_item = po.items[0].name
		pr1.items[0].purchase_receipt_item = pr.items[0].name
		pr1.submit()

		pi1 = make_purchase_invoice(pr.name)
		self.assertEqual(pi1.items[0].qty, 3)

		pr1.cancel()
		pr.reload()
		pr.cancel()
		po.reload()
		po.cancel()

	def test_make_purchase_invoice_from_pr_with_returned_qty_duplicate_items(self):
		frappe.db.set_single_value("Buying Settings", "bill_for_rejected_quantity_in_purchase_invoice", 0)
		pr1 = make_purchase_receipt(qty=8, do_not_submit=True)
		pr1.append(
			"items",
			{
				"item_code": "_Test Item",
				"warehouse": "_Test Warehouse - _TC",
				"qty": 1,
				"received_qty": 1,
				"rate": 100,
				"conversion_factor": 1.0,
			},
		)
		pr1.submit()

		pi1 = make_purchase_invoice(pr1.name)
		pi1.items[0].qty = 4
		pi1.items.pop(1)
		pi1.save()
		pi1.submit()

		pr2 = make_purchase_receipt(qty=-2, is_return=1, return_against=pr1.name, do_not_submit=True)
		pr2.items[0].purchase_receipt_item = pr1.items[0].name
		pr2.submit()

		pi2 = make_purchase_invoice(pr1.name)
		self.assertEqual(pi2.items[0].qty, 2)
		self.assertEqual(pi2.items[1].qty, 1)

		pr2.cancel()
		pi1.cancel()
		pr1.reload()
		pr1.cancel()

	def test_stock_transfer_from_purchase_receipt(self):
		from erpnext.stock.doctype.delivery_note.delivery_note import make_inter_company_purchase_receipt
		from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note

		prepare_data_for_internal_transfer()

		customer = "_Test Internal Customer 2"
		company = "_Test Company with perpetual inventory"

		pr1 = make_purchase_receipt(
			warehouse="Stores - TCP1", company="_Test Company with perpetual inventory"
		)

		dn1 = create_delivery_note(
			item_code=pr1.items[0].item_code,
			company=company,
			customer=customer,
			cost_center="Main - TCP1",
			expense_account="Cost of Goods Sold - TCP1",
			qty=5,
			rate=500,
			warehouse="Stores - TCP1",
			target_warehouse="Work In Progress - TCP1",
		)

		pr = make_inter_company_purchase_receipt(dn1.name)
		pr.items[0].from_warehouse = "Work In Progress - TCP1"
		pr.items[0].warehouse = "Stores - TCP1"
		pr.submit()

		gl_entries = get_gl_entries("Purchase Receipt", pr.name)
		sl_entries = get_sl_entries("Purchase Receipt", pr.name)

		self.assertFalse(gl_entries)

		expected_sle = {"Work In Progress - TCP1": -5, "Stores - TCP1": 5}

		for sle in sl_entries:
			self.assertEqual(expected_sle[sle.warehouse], sle.actual_qty)

		pr.cancel()

	def test_inter_company_purchase_receipt_does_not_inherit_party_fields(self):
		"""
		Party-derived fields on DN (from Customer) must not leak into the mapped PR.
		"""
		from erpnext.stock.doctype.delivery_note.delivery_note import make_inter_company_purchase_receipt
		from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note

		prepare_data_for_internal_transfer()

		customer = "_Test Internal Customer 2"
		company = "_Test Company with perpetual inventory"

		dn = create_delivery_note(
			company=company,
			customer=customer,
			cost_center="Main - TCP1",
			expense_account="Cost of Goods Sold - TCP1",
			qty=1,
			rate=100,
			warehouse="Stores - TCP1",
			target_warehouse="Work In Progress - TCP1",
			do_not_submit=True,
		)
		# Stamp customer-side party fields onto the DN
		dn.tax_category = "_Test Tax Category 2"
		dn.language = "ar"
		dn.submit()

		pr = make_inter_company_purchase_receipt(dn.name)

		supplier = frappe.get_doc("Supplier", "_Test Internal Supplier 2")
		self.assertEqual(pr.tax_category or None, supplier.tax_category or None)
		self.assertEqual(pr.language or None, supplier.language or None)

	def test_lcv_for_internal_transfer(self):
		from erpnext.stock.doctype.delivery_note.delivery_note import make_inter_company_purchase_receipt
		from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note
		from erpnext.stock.doctype.landed_cost_voucher.test_landed_cost_voucher import (
			make_landed_cost_voucher,
		)

		prepare_data_for_internal_transfer()

		customer = "_Test Internal Customer 2"
		company = "_Test Company with perpetual inventory"

		item_code = make_item(
			"Test Item For LCV in Internal Transfer",
			{"has_batch_no": 1, "create_new_batch": 1, "batch_naming_series": "TEST-SBATCH.###"},
		).name

		pr1 = make_purchase_receipt(
			item_code=item_code,
			qty=10,
			rate=100,
			warehouse="Stores - TCP1",
			company="_Test Company with perpetual inventory",
		)

		dn1 = create_delivery_note(
			item_code=pr1.items[0].item_code,
			company=company,
			customer=customer,
			cost_center="Main - TCP1",
			expense_account="Cost of Goods Sold - TCP1",
			qty=10,
			rate=500,
			warehouse="Stores - TCP1",
			target_warehouse="Work In Progress - TCP1",
		)

		pr = make_inter_company_purchase_receipt(dn1.name)
		pr.items[0].from_warehouse = "Work In Progress - TCP1"
		pr.items[0].warehouse = "Stores - TCP1"
		pr.submit()

		sle_entries = frappe.get_all(
			"Stock Ledger Entry",
			filters={"voucher_type": "Purchase Receipt", "voucher_no": pr.name},
			fields=["serial_and_batch_bundle", "actual_qty"],
		)
		self.assertEqual(len(sle_entries), 2)

		inward_sabb = frappe.get_all(
			"Serial and Batch Bundle",
			filters={
				"voucher_type": "Purchase Receipt",
				"voucher_no": pr.name,
				"total_qty": (">", 0),
				"docstatus": 1,
			},
			pluck="name",
		)
		self.assertEqual(len(inward_sabb), 1)

		original_cost = frappe.db.get_value("Serial and Batch Bundle", inward_sabb[0], "total_amount")

		make_landed_cost_voucher(
			company=pr.company,
			receipt_document_type="Purchase Receipt",
			receipt_document=pr.name,
			charges=100,
			distribute_charges_based_on="Qty",
			expense_account="Expenses Included In Valuation - TCP1",
		)

		sle_entries = frappe.get_all(
			"Stock Ledger Entry",
			filters={"voucher_type": "Purchase Receipt", "voucher_no": pr.name, "is_cancelled": 0},
			fields=["serial_and_batch_bundle", "actual_qty"],
		)
		self.assertEqual(len(sle_entries), 2)

		new_inward_sabb = frappe.get_all(
			"Serial and Batch Bundle",
			filters={
				"voucher_type": "Purchase Receipt",
				"voucher_no": pr.name,
				"total_qty": (">", 0),
				"docstatus": 1,
			},
			pluck="name",
		)
		self.assertEqual(len(new_inward_sabb), 1)

		new_cost = frappe.db.get_value("Serial and Batch Bundle", new_inward_sabb[0], "total_amount")
		self.assertEqual(new_cost, original_cost + 100)

		self.assertTrue(new_inward_sabb[0] == inward_sabb[0])

	def test_stock_transfer_from_purchase_receipt_with_valuation(self):
		from erpnext.stock.doctype.delivery_note.delivery_note import make_inter_company_purchase_receipt
		from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note
		from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import (
			create_stock_reconciliation,
		)
		from erpnext.stock.get_item_details import get_valuation_rate
		from erpnext.stock.utils import get_stock_balance

		prepare_data_for_internal_transfer()

		create_warehouse(
			"_Test Warehouse for Valuation",
			company="_Test Company with perpetual inventory",
			properties={"account": "_Test Account Stock In Hand - TCP1"},
		)

		pr1 = make_purchase_receipt(
			warehouse="Stores - TCP1",
			company="_Test Company with perpetual inventory",
		)

		if (
			get_valuation_rate(
				pr1.items[0].item_code, "_Test Company with perpetual inventory", warehouse="Stores - TCP1"
			)
			!= 50
		):
			balance = get_stock_balance(item_code=pr1.items[0].item_code, warehouse="Stores - TCP1")
			create_stock_reconciliation(
				item_code=pr1.items[0].item_code,
				company="_Test Company with perpetual inventory",
				warehouse="Stores - TCP1",
				qty=balance,
				rate=50,
				do_not_save=True,
			)

		customer = "_Test Internal Customer 2"
		company = "_Test Company with perpetual inventory"

		dn1 = create_delivery_note(
			item_code=pr1.items[0].item_code,
			company=company,
			customer=customer,
			cost_center="Main - TCP1",
			expense_account="Cost of Goods Sold - TCP1",
			qty=5,
			rate=50,
			warehouse="Stores - TCP1",
			target_warehouse="_Test Warehouse for Valuation - TCP1",
		)

		pr = make_inter_company_purchase_receipt(dn1.name)
		pr.items[0].from_warehouse = "_Test Warehouse for Valuation - TCP1"
		pr.items[0].warehouse = "Stores - TCP1"

		pr.append(
			"taxes",
			{
				"charge_type": "On Net Total",
				"account_head": "_Test Account Shipping Charges - TCP1",
				"category": "Valuation and Total",
				"cost_center": "Main - TCP1",
				"description": "Test",
				"rate": 9,
			},
		)

		pr.submit()

		gl_entries = get_gl_entries("Purchase Receipt", pr.name)
		sl_entries = get_sl_entries("Purchase Receipt", pr.name)

		expected_gle = [
			["Stock In Hand - TCP1", 250.0, 0.0],
			["Cost of Goods Sold - TCP1", 22.5, 0.0],
			["_Test Account Stock In Hand - TCP1", 0.0, 250.0],
			["_Test Account Shipping Charges - TCP1", 0.0, 22.5],
		]

		expected_sle = {"_Test Warehouse for Valuation - TCP1": -5, "Stores - TCP1": 5}

		for sle in sl_entries:
			self.assertEqual(expected_sle[sle.warehouse], sle.actual_qty)

		for i, gle in enumerate(gl_entries):
			self.assertEqual(gle.account, expected_gle[i][0])
			self.assertEqual(gle.debit, expected_gle[i][1])
			self.assertEqual(gle.credit, expected_gle[i][2])

		pr.cancel()

	def test_item_valuation_with_deduct_valuation_and_total_tax(self):
		pr = make_purchase_receipt(
			company="_Test Company with perpetual inventory",
			warehouse="Stores - TCP1",
			supplier_warehouse="Work In Progress - TCP1",
			qty=5,
			rate=100,
			do_not_save=1,
		)

		pr.append(
			"taxes",
			{
				"charge_type": "Actual",
				"add_deduct_tax": "Deduct",
				"account_head": "_Test Account Shipping Charges - TCP1",
				"category": "Valuation and Total",
				"cost_center": "Main - TCP1",
				"description": "Valuation Discount",
				"tax_amount": 20,
			},
		)

		pr.insert()

		self.assertAlmostEqual(pr.items[0].item_tax_amount, -20.0, places=2)
		self.assertAlmostEqual(pr.items[0].valuation_rate, 96.0, places=2)

		pr.delete()

		pr = make_purchase_receipt(
			company="_Test Company with perpetual inventory",
			warehouse="Stores - TCP1",
			supplier_warehouse="Work In Progress - TCP1",
			qty=5,
			rate=100,
			do_not_save=1,
		)

		pr.append(
			"taxes",
			{
				"charge_type": "On Net Total",
				"add_deduct_tax": "Deduct",
				"account_head": "_Test Account Shipping Charges - TCP1",
				"category": "Valuation and Total",
				"cost_center": "Main - TCP1",
				"description": "Valuation Discount",
				"rate": 10,
			},
		)

		pr.insert()

		self.assertAlmostEqual(pr.items[0].item_tax_amount, -50.0, places=2)
		self.assertAlmostEqual(pr.items[0].valuation_rate, 90.0, places=2)

		pr.delete()

	def test_valuation_tax_distribution_with_non_stock_item(self):
		"""When "Allocate Full Amount to Stock Items" is unchecked, a "Valuation and Total"
		actual charge is distributed across all items by net amount, but only stock/asset items
		can carry valuation. For a document with 2 stock items + 1 service item (each net 100)
		and a 30 valuation charge, each item's share is 10; only the two stock items capitalize
		their share (20 total), so the non-stock item's 10 share must not be capitalized onto the
		stock items."""
		company = "_Test Company with perpetual inventory"
		warehouse = "Stores - TCP1"

		stock_item1 = make_item(properties={"is_stock_item": 1}).name
		stock_item2 = make_item(properties={"is_stock_item": 1}).name
		service_item = make_item(properties={"is_stock_item": 0}).name

		pr = frappe.new_doc("Purchase Receipt")
		pr.company = company
		pr.supplier = "_Test Supplier"
		pr.currency = "INR"
		# Order matters: stock, service, stock (service item in the middle)
		for code in (stock_item1, service_item, stock_item2):
			pr.append(
				"items",
				{
					"item_code": code,
					"qty": 1,
					"rate": 100,
					"warehouse": warehouse,
					"cost_center": "Main - TCP1",
					"expense_account": "Cost of Goods Sold - TCP1",
				},
			)

		pr.append(
			"taxes",
			{
				"charge_type": "Actual",
				"account_head": "_Test Account Shipping Charges - TCP1",
				"category": "Valuation and Total",
				"cost_center": "Main - TCP1",
				"description": "Valuation Tax",
				"tax_amount": 30,
				# Spread across all items (incl. non-stock); do not allocate full amount to stock items
				"allocate_full_amount_to_stock_items": 0,
			},
		)

		pr.insert()

		# 30 tax / 300 net = 10 per item. The two stock items capitalize 10 each; the service
		# item's 10 share is excluded from valuation (not dumped onto the stock items).
		self.assertAlmostEqual(pr.items[0].item_tax_amount, 10.0, places=2)
		self.assertAlmostEqual(pr.items[1].item_tax_amount, 0.0, places=2)
		self.assertAlmostEqual(pr.items[2].item_tax_amount, 10.0, places=2)
		self.assertAlmostEqual(pr.items[0].valuation_rate, 110.0, places=2)
		self.assertAlmostEqual(pr.items[2].valuation_rate, 110.0, places=2)

		pr.submit()

		gl_entries = get_gl_entries("Purchase Receipt", pr.name, skip_cancelled=True, as_dict=True)
		gl_map = {row.account: row for row in gl_entries}

		warehouse_account = get_warehouse_account_map(company)
		stock_account = warehouse_account[warehouse]["account"]

		# Stock asset = 200 (goods) + 20 (stock items' share of the valuation tax)
		self.assertAlmostEqual(gl_map[stock_account].debit, 220.0, places=2)
		self.assertAlmostEqual(gl_map["Stock Received But Not Billed - TCP1"].credit, 200.0, places=2)
		# Only the stock items' share (20) is capitalized; the service item's 10 is excluded
		self.assertAlmostEqual(gl_map["_Test Account Shipping Charges - TCP1"].credit, 20.0, places=2)

	def test_full_actual_charge_capitalized_on_stock_items_only(self):
		"""When "Allocate Full Amount to Stock Items" is checked (the default), an actual
		valuation charge such as Freight is fully capitalized onto stock/asset items only. For a
		document with 2 stock items + 1 service item (each net 100) and a 30 freight charge, the
		charge is distributed over the 200 stock net only: 15 per stock item, and the entire 30
		is capitalized (nothing is lost to the non-stock item)."""
		company = "_Test Company with perpetual inventory"
		warehouse = "Stores - TCP1"

		stock_item1 = make_item(properties={"is_stock_item": 1}).name
		stock_item2 = make_item(properties={"is_stock_item": 1}).name
		service_item = make_item(properties={"is_stock_item": 0}).name

		pr = frappe.new_doc("Purchase Receipt")
		pr.company = company
		pr.supplier = "_Test Supplier"
		pr.currency = "INR"
		# Order matters: stock, service, stock (service item in the middle)
		for code in (stock_item1, service_item, stock_item2):
			pr.append(
				"items",
				{
					"item_code": code,
					"qty": 1,
					"rate": 100,
					"warehouse": warehouse,
					"cost_center": "Main - TCP1",
					"expense_account": "Cost of Goods Sold - TCP1",
				},
			)

		pr.append(
			"taxes",
			{
				"charge_type": "Actual",
				"account_head": "_Test Account Shipping Charges - TCP1",
				"category": "Valuation and Total",
				"cost_center": "Main - TCP1",
				"description": "Freight",
				"tax_amount": 30,
				# Default behavior: allocate the full amount to stock/asset items only
				"allocate_full_amount_to_stock_items": 1,
			},
		)

		pr.insert()

		# 30 freight / 200 stock net = 15 per stock item. The service item carries nothing.
		self.assertAlmostEqual(pr.items[0].item_tax_amount, 15.0, places=2)
		self.assertAlmostEqual(pr.items[1].item_tax_amount, 0.0, places=2)
		self.assertAlmostEqual(pr.items[2].item_tax_amount, 15.0, places=2)
		self.assertAlmostEqual(pr.items[0].valuation_rate, 115.0, places=2)
		self.assertAlmostEqual(pr.items[2].valuation_rate, 115.0, places=2)

		pr.submit()

		gl_entries = get_gl_entries("Purchase Receipt", pr.name, skip_cancelled=True, as_dict=True)
		gl_map = {row.account: row for row in gl_entries}

		warehouse_account = get_warehouse_account_map(company)
		stock_account = warehouse_account[warehouse]["account"]

		# Stock asset = 200 (goods) + 30 (the entire freight charge)
		self.assertAlmostEqual(gl_map[stock_account].debit, 230.0, places=2)
		self.assertAlmostEqual(gl_map["Stock Received But Not Billed - TCP1"].credit, 200.0, places=2)
		# The whole freight charge (30) is capitalized
		self.assertAlmostEqual(gl_map["_Test Account Shipping Charges - TCP1"].credit, 30.0, places=2)

	def test_actual_charge_distribution_with_both_allocation_modes(self):
		"""Both allocation modes can coexist on the same document, and each item's share from
		each charge adds up. For 2 stock items + 1 service item (each net 100):
		- a 30 charge with the flag unchecked spreads over all 3 items (10 each); the service
		  item's 10 is not capitalized, so each stock item keeps 10.
		- a 20 charge with the flag checked spreads over the 2 stock items only (10 each).
		So each stock item carries 10 + 10 = 20, and the service item carries nothing."""
		company = "_Test Company with perpetual inventory"
		warehouse = "Stores - TCP1"

		stock_item1 = make_item(properties={"is_stock_item": 1}).name
		stock_item2 = make_item(properties={"is_stock_item": 1}).name
		service_item = make_item(properties={"is_stock_item": 0}).name

		pr = frappe.new_doc("Purchase Receipt")
		pr.company = company
		pr.supplier = "_Test Supplier"
		pr.currency = "INR"
		# Order matters: stock, service, stock (service item in the middle)
		for code in (stock_item1, service_item, stock_item2):
			pr.append(
				"items",
				{
					"item_code": code,
					"qty": 1,
					"rate": 100,
					"warehouse": warehouse,
					"cost_center": "Main - TCP1",
					"expense_account": "Cost of Goods Sold - TCP1",
				},
			)

		# Spread across all items (service share dropped)
		pr.append(
			"taxes",
			{
				"charge_type": "Actual",
				"account_head": "_Test Account Shipping Charges - TCP1",
				"category": "Valuation and Total",
				"cost_center": "Main - TCP1",
				"description": "Valuation Tax",
				"tax_amount": 30,
				"allocate_full_amount_to_stock_items": 0,
			},
		)
		# Allocate the full amount to stock items only
		pr.append(
			"taxes",
			{
				"charge_type": "Actual",
				"account_head": "_Test Account Customs Duty - TCP1",
				"category": "Valuation and Total",
				"cost_center": "Main - TCP1",
				"description": "Freight",
				"tax_amount": 20,
				"allocate_full_amount_to_stock_items": 1,
			},
		)

		pr.insert()

		# Each stock item: 10 (all-items charge) + 10 (stock-only charge) = 20
		self.assertAlmostEqual(pr.items[0].item_tax_amount, 20.0, places=2)
		self.assertAlmostEqual(pr.items[1].item_tax_amount, 0.0, places=2)
		self.assertAlmostEqual(pr.items[2].item_tax_amount, 20.0, places=2)
		self.assertAlmostEqual(pr.items[0].valuation_rate, 120.0, places=2)
		self.assertAlmostEqual(pr.items[2].valuation_rate, 120.0, places=2)

		pr.submit()

		gl_entries = get_gl_entries("Purchase Receipt", pr.name, skip_cancelled=True, as_dict=True)
		gl_map = {row.account: row for row in gl_entries}

		warehouse_account = get_warehouse_account_map(company)
		stock_account = warehouse_account[warehouse]["account"]

		# Stock asset = 200 (goods) + 20 (stock share of the spread charge) + 20 (the full freight)
		self.assertAlmostEqual(gl_map[stock_account].debit, 240.0, places=2)
		self.assertAlmostEqual(gl_map["Stock Received But Not Billed - TCP1"].credit, 200.0, places=2)
		# Only the stock items' 20 share of the spread charge is capitalized (service 10 excluded)
		self.assertAlmostEqual(gl_map["_Test Account Shipping Charges - TCP1"].credit, 20.0, places=2)
		# The whole freight charge (20) is capitalized
		self.assertAlmostEqual(gl_map["_Test Account Customs Duty - TCP1"].credit, 20.0, places=2)

	def test_multiple_actual_charges_per_item_matches_gl_per_account(self):
		"""With multiple "Actual" valuation charges over unevenly valued stock items, each charge
		is distributed individually so the per-item item_tax_amount decomposes exactly into the
		per-tax-row amount capitalized in the GL (no rounding drift between the two paths).

		2 stock items with net 100 and 200 (total 300), and two freight charges of 10 each, both
		flagged to capitalize fully onto stock items. Distributing each charge separately gives
		item1 = round(100/300*10) * 2 = 3.33 * 2 = 6.66 and item2 = 6.67 * 2 = 13.34. Pooling the
		two charges into 20 first and spreading the aggregate would instead put 6.67 on item1,
		which no longer matches the 3.33 + 3.33 implied by the two per-account GL credits."""
		company = "_Test Company with perpetual inventory"
		warehouse = "Stores - TCP1"

		stock_item1 = make_item(properties={"is_stock_item": 1}).name
		stock_item2 = make_item(properties={"is_stock_item": 1}).name

		pr = frappe.new_doc("Purchase Receipt")
		pr.company = company
		pr.supplier = "_Test Supplier"
		pr.currency = "INR"
		for code, rate in ((stock_item1, 100), (stock_item2, 200)):
			pr.append(
				"items",
				{
					"item_code": code,
					"qty": 1,
					"rate": rate,
					"warehouse": warehouse,
					"cost_center": "Main - TCP1",
					"expense_account": "Cost of Goods Sold - TCP1",
				},
			)

		for account, amount in (
			("_Test Account Shipping Charges - TCP1", 10),
			("_Test Account Customs Duty - TCP1", 10),
		):
			pr.append(
				"taxes",
				{
					"charge_type": "Actual",
					"account_head": account,
					"category": "Valuation and Total",
					"cost_center": "Main - TCP1",
					"description": account,
					"tax_amount": amount,
					"allocate_full_amount_to_stock_items": 1,
				},
			)

		pr.insert()

		# Each charge spread on its own: 3.33 + 3.33 = 6.66 and 6.67 + 6.67 = 13.34 (total 20)
		self.assertAlmostEqual(pr.items[0].item_tax_amount, 6.66, places=2)
		self.assertAlmostEqual(pr.items[1].item_tax_amount, 13.34, places=2)
		self.assertAlmostEqual(pr.items[0].valuation_rate, 106.66, places=2)
		self.assertAlmostEqual(pr.items[1].valuation_rate, 213.34, places=2)

		pr.submit()

		gl_entries = get_gl_entries("Purchase Receipt", pr.name, skip_cancelled=True, as_dict=True)
		gl_map = {row.account: row for row in gl_entries}

		warehouse_account = get_warehouse_account_map(company)
		stock_account = warehouse_account[warehouse]["account"]

		# Stock asset = 300 (goods) + 10 + 10 (both freight charges fully capitalized)
		self.assertAlmostEqual(gl_map[stock_account].debit, 320.0, places=2)
		self.assertAlmostEqual(gl_map["Stock Received But Not Billed - TCP1"].credit, 300.0, places=2)
		# Each charge is credited in full to its own account
		self.assertAlmostEqual(gl_map["_Test Account Shipping Charges - TCP1"].credit, 10.0, places=2)
		self.assertAlmostEqual(gl_map["_Test Account Customs Duty - TCP1"].credit, 10.0, places=2)

	def test_po_to_pi_and_po_to_pr_worflow_full(self):
		"""Test following behaviour:
		- Create PO
		- Create PI from PO and submit
		- Create PR from PO and submit
		"""
		from erpnext.buying.doctype.purchase_order import purchase_order, test_purchase_order

		po = test_purchase_order.create_purchase_order()

		pi = purchase_order.make_purchase_invoice(po.name)
		pi.submit()

		pr = purchase_order.make_purchase_receipt(po.name)
		pr.submit()

		pr.load_from_db()

		self.assertEqual(pr.status, "Completed")
		self.assertEqual(pr.per_billed, 100)

	def test_po_to_pi_and_po_to_pr_worflow_partial(self):
		"""Test following behaviour:
		- Create PO
		- Create partial PI from PO and submit
		- Create PR from PO and submit
		"""
		from erpnext.buying.doctype.purchase_order import purchase_order, test_purchase_order

		po = test_purchase_order.create_purchase_order()

		pi = purchase_order.make_purchase_invoice(po.name)
		pi.items[0].qty /= 2  # roughly 50%, ^ this function only creates PI with 1 item.
		pi.submit()

		pr = purchase_order.make_purchase_receipt(po.name)
		pr.save()
		# per_billed is only updated after submission.
		self.assertEqual(flt(pr.per_billed), 0)

		pr.submit()

		pi.load_from_db()
		pr.load_from_db()

		self.assertEqual(pr.status, "Partly Billed")
		self.assertAlmostEqual(pr.per_billed, 50.0, places=2)

	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,
		)
		from erpnext.accounts.party import add_party_account

		add_party_account(
			"Supplier",
			"_Test Supplier USD",
			"_Test Company with perpetual inventory",
			"_Test Payable USD - TCP1",
		)

		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,
			supplier="_Test Supplier USD",
		)

		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

		pr.save()
		pr.submit()

		# 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)

	@ERPNextTestSuite.change_settings("Accounts Settings", {"automatically_fetch_payment_terms": 1})
	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,
		)
		from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
		from erpnext.buying.doctype.purchase_order.test_purchase_order import (
			create_purchase_order,
			make_pr_against_po,
		)
		from erpnext.selling.doctype.sales_order.test_sales_order import (
			compare_payment_schedules,
		)

		po = create_purchase_order(qty=10, rate=100, do_not_save=1)
		create_payment_terms_template()
		po.payment_terms_template = "Test Receivable Template"
		po.submit()

		pr = make_pr_against_po(po.name, received_qty=10)

		pi = make_purchase_invoice(qty=10, rate=100, do_not_save=1)
		pi.items[0].purchase_receipt = pr.name
		pi.items[0].pr_detail = pr.items[0].name
		pi.items[0].purchase_order = po.name
		pi.items[0].po_detail = po.items[0].name
		pi.insert()

		# self.assertEqual(po.payment_terms_template, pi.payment_terms_template)
		compare_payment_schedules(self, po, pi)

	@ERPNextTestSuite.change_settings("Stock Settings", {"allow_negative_stock": 1})
	def test_neg_to_positive(self):
		from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry

		item_code = "_TestNegToPosItem"
		warehouse = "Stores - TCP1"
		company = "_Test Company with perpetual inventory"
		account = "Stock Received But Not Billed - TCP1"

		make_item(item_code)
		se = make_stock_entry(item_code=item_code, from_warehouse=warehouse, qty=50, do_not_save=True, rate=0)
		se.items[0].allow_zero_valuation_rate = 1
		se.save()
		se.submit()

		pr = make_purchase_receipt(
			qty=50,
			rate=1,
			item_code=item_code,
			warehouse=warehouse,
			get_taxes_and_charges=True,
			company=company,
		)
		gles = get_gl_entries(pr.doctype, pr.name)

		for gle in gles:
			if gle.account == account:
				self.assertEqual(gle.credit, 50)

	def test_backdated_transaction_for_internal_transfer(self):
		from erpnext.stock.doctype.delivery_note.delivery_note import make_inter_company_purchase_receipt
		from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note

		prepare_data_for_internal_transfer()
		customer = "_Test Internal Customer 2"
		company = "_Test Company with perpetual inventory"

		from_warehouse = create_warehouse("_Test Internal From Warehouse New", company=company)
		to_warehouse = create_warehouse("_Test Internal To Warehouse New", company=company)
		item_doc = create_item("Test Internal Transfer Item")

		target_warehouse = create_warehouse("_Test Internal GIT Warehouse New", company=company)

		make_purchase_receipt(
			item_code=item_doc.name,
			company=company,
			posting_date=add_days(today(), -1),
			warehouse=from_warehouse,
			qty=1,
			rate=100,
		)

		dn1 = create_delivery_note(
			item_code=item_doc.name,
			company=company,
			customer=customer,
			cost_center="Main - TCP1",
			expense_account="Cost of Goods Sold - TCP1",
			qty=1,
			rate=500,
			warehouse=from_warehouse,
			target_warehouse=target_warehouse,
		)

		self.assertEqual(dn1.items[0].rate, 100)

		pr1 = make_inter_company_purchase_receipt(dn1.name)
		pr1.items[0].warehouse = to_warehouse
		self.assertEqual(pr1.items[0].rate, 100)
		pr1.submit()

		self.assertEqual(pr1.is_internal_supplier, 1)

		# Backdated purchase receipt entry, the valuation rate should be updated for DN1 and PR1
		make_purchase_receipt(
			item_code=item_doc.name,
			company=company,
			posting_date=add_days(today(), -2),
			warehouse=from_warehouse,
			qty=1,
			rate=200,
		)

		dn_value = frappe.db.get_value(
			"Stock Ledger Entry",
			{"voucher_type": "Delivery Note", "voucher_no": dn1.name, "warehouse": target_warehouse},
			"stock_value_difference",
		)

		self.assertEqual(abs(dn_value), 200.00)

		pr_value = frappe.db.get_value(
			"Stock Ledger Entry",
			{"voucher_type": "Purchase Receipt", "voucher_no": pr1.name, "warehouse": to_warehouse},
			"stock_value_difference",
		)

		self.assertEqual(abs(pr_value), 200.00)
		pr1.load_from_db()

		self.assertEqual(pr1.items[0].valuation_rate, 200)
		self.assertEqual(pr1.items[0].rate, 100)

		Gl = frappe.qb.DocType("GL Entry")

		query = (
			frappe.qb.from_(Gl)
			.select(
				(fn.Sum(Gl.debit) - fn.Sum(Gl.credit)).as_("value"),
			)
			.where((Gl.voucher_type == pr1.doctype) & (Gl.voucher_no == pr1.name))
		).run(as_dict=True)

		self.assertEqual(query[0].value, 0)

	def test_backdated_transaction_for_internal_transfer_in_trasit_warehouse_for_purchase_receipt(
		self,
	):
		from erpnext.stock.doctype.delivery_note.delivery_note import make_inter_company_purchase_receipt
		from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note

		prepare_data_for_internal_transfer()
		customer = "_Test Internal Customer 2"
		company = "_Test Company with perpetual inventory"

		from_warehouse = create_warehouse("_Test Internal From Warehouse New", company=company)
		to_warehouse = create_warehouse("_Test Internal To Warehouse New", company=company)
		item_doc = create_item("Test Internal Transfer Item")

		target_warehouse = create_warehouse("_Test Internal GIT Warehouse New", company=company)

		make_purchase_receipt(
			item_code=item_doc.name,
			company=company,
			posting_date=add_days(today(), -1),
			warehouse=from_warehouse,
			qty=1,
			rate=100,
		)

		# Keep stock in advance and make sure that systen won't pick this stock while reposting backdated transaction
		for i in range(1, 4):
			make_purchase_receipt(
				item_code=item_doc.name,
				company=company,
				posting_date=add_days(today(), -1 * i),
				warehouse=target_warehouse,
				qty=1,
				rate=320 * i,
			)

		dn1 = create_delivery_note(
			item_code=item_doc.name,
			company=company,
			customer=customer,
			cost_center="Main - TCP1",
			expense_account="Cost of Goods Sold - TCP1",
			qty=1,
			rate=500,
			warehouse=from_warehouse,
			target_warehouse=target_warehouse,
		)

		self.assertEqual(dn1.items[0].rate, 100)

		pr1 = make_inter_company_purchase_receipt(dn1.name)
		pr1.items[0].warehouse = to_warehouse
		self.assertEqual(pr1.items[0].rate, 100)
		pr1.submit()

		stk_ledger = frappe.db.get_value(
			"Stock Ledger Entry",
			{"voucher_type": "Purchase Receipt", "voucher_no": pr1.name, "warehouse": target_warehouse},
			["stock_value_difference", "outgoing_rate"],
			as_dict=True,
		)

		self.assertEqual(abs(stk_ledger.stock_value_difference), 100)
		self.assertEqual(stk_ledger.outgoing_rate, 100)

		# Backdated purchase receipt entry, the valuation rate should be updated for DN1 and PR1
		make_purchase_receipt(
			item_code=item_doc.name,
			company=company,
			posting_date=add_days(today(), -2),
			warehouse=from_warehouse,
			qty=1,
			rate=200,
		)

		dn_value = frappe.db.get_value(
			"Stock Ledger Entry",
			{"voucher_type": "Delivery Note", "voucher_no": dn1.name, "warehouse": target_warehouse},
			"stock_value_difference",
		)

		self.assertEqual(abs(dn_value), 200.00)

		pr_value = frappe.db.get_value(
			"Stock Ledger Entry",
			{"voucher_type": "Purchase Receipt", "voucher_no": pr1.name, "warehouse": to_warehouse},
			"stock_value_difference",
		)

		self.assertEqual(abs(pr_value), 200.00)
		pr1.load_from_db()

		self.assertEqual(pr1.items[0].valuation_rate, 200)
		self.assertEqual(pr1.items[0].rate, 100)

		Gl = frappe.qb.DocType("GL Entry")

		query = (
			frappe.qb.from_(Gl)
			.select(
				(fn.Sum(Gl.debit) - fn.Sum(Gl.credit)).as_("value"),
			)
			.where((Gl.voucher_type == pr1.doctype) & (Gl.voucher_no == pr1.name))
		).run(as_dict=True)

		self.assertEqual(query[0].value, 0)

	def test_internal_transfer_pr_incoming_sle_anchored_to_dn_rate(self):
		"""Internal-transfer PR's inward SLE must use DN.incoming_rate even when
		PR.item.valuation_rate was wrong at submit, so divisional_loss does not
		leak to COGS."""
		from erpnext.stock.doctype.delivery_note.delivery_note import make_inter_company_purchase_receipt
		from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note
		from erpnext.stock.stock_ledger import update_entries_after

		prepare_data_for_internal_transfer()
		customer = "_Test Internal Customer 2"
		company = "_Test Company with perpetual inventory"

		from_warehouse = create_warehouse("_Test Drift From", company=company)
		transit_warehouse = create_warehouse("_Test Drift Transit", company=company)
		to_warehouse = create_warehouse("_Test Drift Receiver", company=company)
		item_doc = create_item("Test Internal Drift Item")

		make_purchase_receipt(
			item_code=item_doc.name,
			company=company,
			posting_date=add_days(today(), -1),
			warehouse=from_warehouse,
			qty=10,
			rate=100,
		)

		dn = create_delivery_note(
			item_code=item_doc.name,
			company=company,
			customer=customer,
			cost_center="Main - TCP1",
			expense_account="Cost of Goods Sold - TCP1",
			qty=1,
			rate=100,
			warehouse=from_warehouse,
			target_warehouse=transit_warehouse,
		)
		self.assertEqual(flt(dn.items[0].incoming_rate), 100.0)

		pr = make_inter_company_purchase_receipt(dn.name)
		pr.items[0].warehouse = to_warehouse
		pr.submit()

		# Simulate the failure path
		frappe.db.set_value(
			"Purchase Receipt Item",
			pr.items[0].name,
			{"sales_incoming_rate": 0, "valuation_rate": 80},
		)
		inward_sle = frappe.db.get_value(
			"Stock Ledger Entry",
			{
				"voucher_type": "Purchase Receipt",
				"voucher_no": pr.name,
				"warehouse": to_warehouse,
				"is_cancelled": 0,
			},
			["name", "item_code", "warehouse", "posting_date", "posting_time", "creation"],
			as_dict=True,
		)
		frappe.db.set_value(
			"Stock Ledger Entry",
			inward_sle.name,
			{"incoming_rate": 80, "stock_value_difference": 80},
		)

		update_entries_after(
			{
				"item_code": inward_sle.item_code,
				"warehouse": inward_sle.warehouse,
				"posting_date": inward_sle.posting_date,
				"posting_time": inward_sle.posting_time,
				"sle_id": inward_sle.name,
				"creation": inward_sle.creation,
			}
		)

		refreshed = frappe.db.get_value(
			"Stock Ledger Entry",
			inward_sle.name,
			["incoming_rate", "stock_value_difference"],
			as_dict=True,
		)
		self.assertEqual(flt(refreshed.incoming_rate), 100.0)
		self.assertEqual(flt(refreshed.stock_value_difference), 100.0)

	def test_backdated_transaction_for_internal_transfer_in_trasit_warehouse_for_purchase_invoice(
		self,
	):
		from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import (
			make_purchase_invoice as make_purchase_invoice_for_si,
		)
		from erpnext.accounts.doctype.sales_invoice.sales_invoice import (
			make_inter_company_purchase_invoice,
		)
		from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice

		prepare_data_for_internal_transfer()
		customer = "_Test Internal Customer 2"
		company = "_Test Company with perpetual inventory"

		from_warehouse = create_warehouse("_Test Internal From Warehouse New", company=company)
		to_warehouse = create_warehouse("_Test Internal To Warehouse New", company=company)
		item_doc = create_item("Test Internal Transfer Item")

		target_warehouse = create_warehouse("_Test Internal GIT Warehouse New", company=company)

		make_purchase_invoice_for_si(
			item_code=item_doc.name,
			company=company,
			posting_date=add_days(today(), -1),
			warehouse=from_warehouse,
			qty=1,
			update_stock=1,
			expense_account="Cost of Goods Sold - TCP1",
			cost_center="Main - TCP1",
			rate=100,
		)

		# Keep stock in advance and make sure that systen won't pick this stock while reposting backdated transaction
		for i in range(1, 4):
			make_purchase_invoice_for_si(
				item_code=item_doc.name,
				company=company,
				posting_date=add_days(today(), -1 * i),
				warehouse=target_warehouse,
				update_stock=1,
				qty=1,
				expense_account="Cost of Goods Sold - TCP1",
				cost_center="Main - TCP1",
				rate=320 * i,
			)

		si1 = create_sales_invoice(
			item_code=item_doc.name,
			company=company,
			customer=customer,
			cost_center="Main - TCP1",
			income_account="Sales - TCP1",
			qty=1,
			rate=500,
			update_stock=1,
			warehouse=from_warehouse,
			target_warehouse=target_warehouse,
		)

		self.assertEqual(si1.items[0].rate, 100)

		pi1 = make_inter_company_purchase_invoice(si1.name)
		pi1.items[0].warehouse = to_warehouse
		self.assertEqual(pi1.items[0].rate, 100)
		pi1.update_stock = 1
		pi1.save()
		pi1.submit()

		stk_ledger = frappe.db.get_value(
			"Stock Ledger Entry",
			{"voucher_type": pi1.doctype, "voucher_no": pi1.name, "warehouse": target_warehouse},
			["stock_value_difference", "outgoing_rate"],
			as_dict=True,
		)

		self.assertEqual(abs(stk_ledger.stock_value_difference), 100)
		self.assertEqual(stk_ledger.outgoing_rate, 100)

		# Backdated purchase receipt entry, the valuation rate should be updated for si1 and pi1
		make_purchase_receipt(
			item_code=item_doc.name,
			company=company,
			posting_date=add_days(today(), -2),
			warehouse=from_warehouse,
			qty=1,
			rate=200,
		)

		si_value = frappe.db.get_value(
			"Stock Ledger Entry",
			{"voucher_type": si1.doctype, "voucher_no": si1.name, "warehouse": target_warehouse},
			"stock_value_difference",
		)

		self.assertEqual(abs(si_value), 200.00)

		pi_value = frappe.db.get_value(
			"Stock Ledger Entry",
			{"voucher_type": pi1.doctype, "voucher_no": pi1.name, "warehouse": to_warehouse},
			"stock_value_difference",
		)

		self.assertEqual(abs(pi_value), 200.00)
		pi1.load_from_db()

		self.assertEqual(pi1.items[0].valuation_rate, 200)
		self.assertEqual(pi1.items[0].rate, 100)

		Gl = frappe.qb.DocType("GL Entry")

		query = (
			frappe.qb.from_(Gl)
			.select(
				(fn.Sum(Gl.debit) - fn.Sum(Gl.credit)).as_("value"),
			)
			.where((Gl.voucher_type == pi1.doctype) & (Gl.voucher_no == pi1.name))
		).run(as_dict=True)

		self.assertEqual(query[0].value, 0)

	def test_batch_expiry_for_purchase_receipt(self):
		from erpnext.controllers.sales_and_purchase_return import make_return_doc

		item = make_item(
			"_Test Batch Item For Return Check",
			{
				"is_purchase_item": 1,
				"is_stock_item": 1,
				"has_batch_no": 1,
				"create_new_batch": 1,
				"batch_number_series": "TBIRC.#####",
			},
		)

		pi = make_purchase_receipt(
			qty=1,
			item_code=item.name,
			update_stock=True,
		)

		pi.load_from_db()
		batch_no = get_batch_from_bundle(pi.items[0].serial_and_batch_bundle)
		self.assertTrue(batch_no)

		frappe.db.set_value("Batch", batch_no, "expiry_date", add_days(today(), -1))

		return_pi = make_return_doc(pi.doctype, pi.name)
		return_pi.save().submit()

		self.assertTrue(return_pi.docstatus == 1)

	def test_disable_last_purchase_rate(self):
		from erpnext.stock.get_item_details import ItemDetailsCtx, get_item_details

		item = make_item(
			"_Test Disable Last Purchase Rate",
			{"is_purchase_item": 1, "is_stock_item": 1},
		)

		frappe.db.set_single_value("Buying Settings", "disable_last_purchase_rate", 1)

		pr = make_purchase_receipt(
			qty=1,
			rate=100,
			item_code=item.name,
		)

		ctx = ItemDetailsCtx(pr.items[0].as_dict())
		ctx.update(
			{
				"supplier": pr.supplier,
				"doctype": pr.doctype,
				"conversion_rate": pr.conversion_rate,
				"currency": pr.currency,
				"company": pr.company,
				"posting_date": pr.posting_date,
				"posting_time": pr.posting_time,
			}
		)

		res = get_item_details(ctx)
		self.assertEqual(res.get("last_purchase_rate"), 0)

		frappe.db.set_single_value("Buying Settings", "disable_last_purchase_rate", 0)

		pr = make_purchase_receipt(
			qty=1,
			rate=100,
			item_code=item.name,
		)

		res = get_item_details(ctx)
		self.assertEqual(res.get("last_purchase_rate"), 100)

	def test_validate_received_qty_for_internal_pr(self):
		prepare_data_for_internal_transfer()
		customer = "_Test Internal Customer 2"
		company = "_Test Company with perpetual inventory"
		from_warehouse = create_warehouse("_Test Internal From Warehouse New", company=company)
		target_warehouse = create_warehouse("_Test Internal GIT Warehouse New", company=company)
		to_warehouse = create_warehouse("_Test Internal To Warehouse New", company=company)

		# Step 1: Create Item
		item = make_item(properties={"is_stock_item": 1, "valuation_rate": 100})

		# Step 2: Create Stock Entry (Material Receipt)
		from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry

		make_stock_entry(
			purpose="Material Receipt",
			item_code=item.name,
			qty=20,
			company=company,
			to_warehouse=from_warehouse,
			posting_date=add_days(today(), -3),
		)

		# Step 3: Create Delivery Note with Internal Customer
		from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note

		dn = create_delivery_note(
			item_code=item.name,
			company=company,
			customer=customer,
			cost_center="Main - TCP1",
			expense_account="Cost of Goods Sold - TCP1",
			qty=10,
			rate=100,
			warehouse=from_warehouse,
			target_warehouse=target_warehouse,
		)

		# Step 4: Create Internal Purchase Receipt
		from erpnext.stock.doctype.delivery_note.delivery_note import make_inter_company_purchase_receipt

		pr = make_inter_company_purchase_receipt(dn.name)
		pr.set_posting_time = 1
		pr.posting_date = today()
		pr.items[0].qty = 15
		pr.items[0].from_warehouse = target_warehouse
		pr.items[0].warehouse = to_warehouse
		pr.items[0].rejected_warehouse = from_warehouse
		pr.save()

		self.assertRaises(frappe.ValidationError, pr.submit)

		# Step 5: Test Over Receipt Allowance
		frappe.db.set_single_value("Stock Settings", "over_delivery_receipt_allowance", 50)

		make_stock_entry(
			purpose="Material Transfer",
			item_code=item.name,
			qty=5,
			company=company,
			from_warehouse=from_warehouse,
			to_warehouse=target_warehouse,
			posting_date=add_days(pr.posting_date, -1),
		)

		pr.reload()
		pr.submit()

		frappe.db.set_single_value("Stock Settings", "over_delivery_receipt_allowance", 0)

	def test_internal_pr_gl_entries(self):
		from erpnext.stock.doctype.delivery_note.delivery_note import make_inter_company_purchase_receipt
		from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note
		from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
		from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import (
			create_stock_reconciliation,
		)

		prepare_data_for_internal_transfer()
		customer = "_Test Internal Customer 2"
		company = "_Test Company with perpetual inventory"
		from_warehouse = create_warehouse("_Test Internal From Warehouse New", company=company)
		target_warehouse = create_warehouse("_Test Internal GIT Warehouse New", company=company)
		to_warehouse = create_warehouse("_Test Internal To Warehouse New", company=company)

		item = make_item(properties={"is_stock_item": 1, "valuation_rate": 100})
		make_stock_entry(
			purpose="Material Receipt",
			item_code=item.name,
			qty=10,
			company=company,
			to_warehouse=from_warehouse,
			posting_date=add_days(today(), -3),
		)

		# Step - 1: Create Delivery Note with Internal Customer
		dn = create_delivery_note(
			item_code=item.name,
			company=company,
			customer=customer,
			cost_center="Main - TCP1",
			expense_account="Cost of Goods Sold - TCP1",
			qty=10,
			rate=100,
			warehouse=from_warehouse,
			target_warehouse=target_warehouse,
			posting_date=add_days(today(), -2),
		)

		# Step - 2: Create Internal Purchase Receipt
		pr = make_inter_company_purchase_receipt(dn.name)
		pr.items[0].qty = 10
		pr.items[0].from_warehouse = target_warehouse
		pr.items[0].warehouse = to_warehouse
		pr.items[0].rejected_warehouse = from_warehouse
		pr.save()
		pr.submit()

		# Step - 3: Create back-date Stock Reconciliation [After DN and Before PR]
		create_stock_reconciliation(
			item_code=item.name,
			warehouse=target_warehouse,
			qty=10,
			rate=50,
			company=company,
			posting_date=add_days(today(), -1),
		)

		warehouse_account = get_warehouse_account_map(company)
		stock_account_value = frappe.db.get_value(
			"GL Entry",
			{
				"account": warehouse_account[target_warehouse]["account"],
				"voucher_type": "Purchase Receipt",
				"voucher_no": pr.name,
				"is_cancelled": 0,
			},
			fieldname=["credit"],
		)
		stock_diff = frappe.db.get_value(
			"Stock Ledger Entry",
			{
				"voucher_type": "Purchase Receipt",
				"voucher_no": pr.name,
				"is_cancelled": 0,
			},
			fieldname=[{"SUM": "stock_value_difference"}],
		)

		# Value of Stock Account should be equal to the sum of Stock Value Difference
		self.assertEqual(stock_account_value, stock_diff)

	def test_internal_pr_reference(self):
		item = make_item(properties={"is_stock_item": 1, "valuation_rate": 100})
		customer = "_Test Internal Customer 2"
		company = "_Test Company with perpetual inventory"
		from_warehouse = create_warehouse("_Test Internal From Warehouse New 1", company=company)
		target_warehouse = create_warehouse("_Test Internal GIT Warehouse New 1", company=company)
		to_warehouse = create_warehouse("_Test Internal To Warehouse New 1", company=company)

		# Step 2: Create Stock Entry (Material Receipt)
		from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry

		make_stock_entry(
			purpose="Material Receipt",
			item_code=item.name,
			qty=15,
			company=company,
			to_warehouse=from_warehouse,
		)

		# Step 3: Create Delivery Note with Internal Customer
		from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note

		dn = create_delivery_note(
			item_code=item.name,
			company=company,
			customer=customer,
			cost_center="Main - TCP1",
			expense_account="Cost of Goods Sold - TCP1",
			qty=10,
			rate=100,
			warehouse=from_warehouse,
			target_warehouse=target_warehouse,
		)

		# Step 4: Create Internal Purchase Receipt
		from erpnext.stock.doctype.delivery_note.delivery_note import make_inter_company_purchase_receipt

		pr = make_inter_company_purchase_receipt(dn.name)
		pr.inter_company_reference = ""
		self.assertRaises(frappe.ValidationError, pr.save)

		pr.inter_company_reference = dn.name
		pr.items[0].qty = 10
		pr.items[0].from_warehouse = target_warehouse
		pr.items[0].warehouse = to_warehouse
		pr.items[0].rejected_warehouse = from_warehouse
		pr.save()

		delivery_note_item = pr.items[0].delivery_note_item
		pr.items[0].delivery_note_item = ""

		self.assertRaises(frappe.ValidationError, pr.save)

		pr.load_from_db()
		pr.items[0].delivery_note_item = delivery_note_item
		pr.save()

	def test_purchase_return_valuation_with_rejected_qty(self):
		item_code = "_Test Item Return Valuation"
		create_item(item_code)

		warehouse = create_warehouse("_Test Warehouse Return Valuation")
		rejected_warehouse = create_warehouse("_Test Rejected Warehouse Return Valuation")

		# Step 1: Create Purchase Receipt with valuation rate 100
		make_purchase_receipt(
			item_code=item_code,
			warehouse=warehouse,
			qty=10,
			rate=100,
			rejected_qty=2,
			rejected_warehouse=rejected_warehouse,
		)

		# Step 2: Create One more Purchase Receipt with valuation rate 200
		pr = make_purchase_receipt(
			item_code=item_code,
			warehouse=warehouse,
			qty=10,
			rate=200,
			rejected_qty=2,
			rejected_warehouse=rejected_warehouse,
		)

		# Step 3: Create Purchase Return for 2 qty
		from erpnext.stock.doctype.purchase_receipt.purchase_receipt import make_purchase_return

		pr_return = make_purchase_return(pr.name)
		pr_return.items[0].qty = 2 * -1
		pr_return.items[0].received_qty = 2 * -1
		pr_return.items[0].rejected_qty = 0
		pr_return.items[0].rejected_warehouse = ""
		pr_return.save()
		pr_return.submit()

		data = frappe.get_all(
			"Stock Ledger Entry",
			filters={"voucher_no": pr_return.name, "docstatus": 1},
			fields=[{"SUM": "stock_value_difference", "as": "stock_value_difference"}],
		)[0]

		self.assertEqual(abs(data["stock_value_difference"]), 400.00)

	def test_return_from_rejected_warehouse(self):
		from erpnext.stock.doctype.purchase_receipt.purchase_receipt import (
			make_purchase_return_against_rejected_warehouse,
		)

		item_code = "_Test Item Return from Rejected Warehouse"
		create_item(item_code)

		warehouse = create_warehouse("_Test Warehouse Return Qty Warehouse")
		rejected_warehouse = create_warehouse("_Test Rejected Warehouse Return Qty Warehouse")

		# Step 1: Create Purchase Receipt with valuation rate 100
		pr = make_purchase_receipt(
			item_code=item_code,
			warehouse=warehouse,
			qty=10,
			rate=100,
			rejected_qty=2,
			rejected_warehouse=rejected_warehouse,
			do_not_save=1,
		)

		pr.append(
			"items",
			{"item_code": item_code, "qty": 2, "rate": 100, "warehouse": warehouse, "rejected_qty": 0},
		)
		pr.save()
		pr.submit()
		self.assertEqual(len(pr.items), 2)

		pr_return = make_purchase_return_against_rejected_warehouse(pr.name)
		self.assertEqual(len(pr_return.items), 1)
		self.assertEqual(pr_return.items[0].warehouse, rejected_warehouse)
		self.assertEqual(pr_return.items[0].qty, 2.0 * -1)
		self.assertEqual(pr_return.items[0].rejected_qty, 0.0)
		self.assertEqual(pr_return.items[0].rejected_warehouse, "")

	def test_purchase_receipt_with_backdated_landed_cost_voucher(self):
		from erpnext.controllers.sales_and_purchase_return import make_return_doc
		from erpnext.stock.doctype.landed_cost_voucher.test_landed_cost_voucher import (
			create_landed_cost_voucher,
		)
		from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry

		item_code = "_Test Purchase Item With Landed Cost"
		create_item(item_code)

		warehouse = create_warehouse("_Test Purchase Warehouse With Landed Cost")
		warehouse1 = create_warehouse("_Test Purchase Warehouse With Landed Cost 1")
		warehouse2 = create_warehouse("_Test Purchase Warehouse With Landed Cost 2")
		warehouse3 = create_warehouse("_Test Purchase Warehouse With Landed Cost 3")

		pr = make_purchase_receipt(
			item_code=item_code,
			warehouse=warehouse,
			posting_date=add_days(today(), -10),
			posting_time="10:59:59",
			qty=100,
			rate=275.00,
		)

		pr_return = make_return_doc("Purchase Receipt", pr.name)
		pr_return.posting_date = add_days(today(), -9)
		pr_return.items[0].qty = 2 * -1
		pr_return.items[0].received_qty = 2 * -1
		pr_return.submit()

		ste1 = make_stock_entry(
			purpose="Material Transfer",
			posting_date=add_days(today(), -8),
			source=warehouse,
			target=warehouse1,
			item_code=item_code,
			qty=20,
			company=pr.company,
		)

		ste1.reload()
		self.assertEqual(ste1.items[0].valuation_rate, 275.00)

		ste2 = make_stock_entry(
			purpose="Material Transfer",
			posting_date=add_days(today(), -7),
			source=warehouse,
			target=warehouse2,
			item_code=item_code,
			qty=20,
			company=pr.company,
		)

		ste2.reload()
		self.assertEqual(ste2.items[0].valuation_rate, 275.00)

		ste3 = make_stock_entry(
			purpose="Material Transfer",
			posting_date=add_days(today(), -6),
			source=warehouse,
			target=warehouse3,
			item_code=item_code,
			qty=20,
			company=pr.company,
		)

		ste3.reload()
		self.assertEqual(ste3.items[0].valuation_rate, 275.00)

		ste4 = make_stock_entry(
			purpose="Material Transfer",
			posting_date=add_days(today(), -5),
			source=warehouse1,
			target=warehouse,
			item_code=item_code,
			qty=20,
			company=pr.company,
		)

		ste4.reload()
		self.assertEqual(ste4.items[0].valuation_rate, 275.00)

		ste5 = make_stock_entry(
			purpose="Material Transfer",
			posting_date=add_days(today(), -4),
			source=warehouse,
			target=warehouse1,
			item_code=item_code,
			qty=20,
			company=pr.company,
		)

		ste5.reload()
		self.assertEqual(ste5.items[0].valuation_rate, 275.00)

		ste6 = make_stock_entry(
			purpose="Material Transfer",
			posting_date=add_days(today(), -3),
			source=warehouse1,
			target=warehouse,
			item_code=item_code,
			qty=20,
			company=pr.company,
		)

		ste6.reload()
		self.assertEqual(ste6.items[0].valuation_rate, 275.00)

		ste7 = make_stock_entry(
			purpose="Material Transfer",
			posting_date=add_days(today(), -3),
			source=warehouse,
			target=warehouse1,
			item_code=item_code,
			qty=20,
			company=pr.company,
		)

		ste7.reload()
		self.assertEqual(ste7.items[0].valuation_rate, 275.00)

		available_qty = frappe.db.get_value(
			"Bin",
			{"item_code": item_code, "warehouse": warehouse},
			"actual_qty",
		)

		new_pr = make_purchase_receipt(
			item_code=item_code,
			warehouse=warehouse,
			qty=100,
			rate=500,
		)

		create_landed_cost_voucher("Purchase Receipt", pr.name, pr.company, charges=2500 * -1)

		pr.reload()
		valuation_rate = pr.items[0].valuation_rate

		ste1.reload()
		self.assertEqual(ste1.items[0].valuation_rate, valuation_rate)

		ste2.reload()
		self.assertEqual(ste2.items[0].valuation_rate, valuation_rate)

		ste3.reload()
		self.assertEqual(ste3.items[0].valuation_rate, valuation_rate)

		ste4.reload()
		self.assertEqual(ste4.items[0].valuation_rate, valuation_rate)

		ste5.reload()
		self.assertEqual(ste5.items[0].valuation_rate, valuation_rate)

		ste6.reload()
		self.assertEqual(ste6.items[0].valuation_rate, valuation_rate)

		ste7.reload()
		self.assertEqual(ste7.items[0].valuation_rate, valuation_rate)

		sle = frappe.db.get_value(
			"Stock Ledger Entry",
			{"voucher_type": "Purchase Receipt", "voucher_no": new_pr.name, "is_cancelled": 0},
			["stock_value", "qty_after_transaction"],
			as_dict=1,
		)

		stock_value = flt(available_qty * valuation_rate) + 50000
		total_stock_qty = available_qty + 100

		self.assertEqual(sle.stock_value, stock_value)
		self.assertEqual(sle.qty_after_transaction, total_stock_qty)

		make_purchase_receipt(
			item_code=item_code,
			warehouse=warehouse,
			posting_date=add_days(today(), -12),
			qty=100,
			rate=500,
		)

		total_stock_qty += 100

		qty_after_transaction = frappe.db.get_value(
			"Stock Ledger Entry",
			{"voucher_type": "Purchase Receipt", "voucher_no": new_pr.name, "is_cancelled": 0},
			["qty_after_transaction"],
		)

		self.assertEqual(qty_after_transaction, total_stock_qty)

	def test_purchase_receipt_provisional_accounting(self):
		# Step - 1: Create Supplier with Default Currency as USD
		from erpnext.buying.doctype.supplier.test_supplier import create_supplier

		supplier = create_supplier(default_currency="USD")

		# Step - 2: Setup Company for Provisional Accounting
		from erpnext.accounts.doctype.account.test_account import create_account

		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()

		# Step - 3: Create Non-Stock Item
		item = make_item(properties={"is_stock_item": 0})

		# Step - 4: Create Purchase Receipt
		pr = make_purchase_receipt(
			qty=2,
			item_code=item.name,
			company=company.name,
			supplier=supplier.name,
			currency=supplier.default_currency,
		)

		# Test - 1: Total and Base Total should not be the same as the currency is different
		self.assertNotEqual(flt(pr.total, 2), flt(pr.base_total, 2))
		self.assertEqual(flt(pr.total * pr.conversion_rate, 2), flt(pr.base_total, 2))

		# Test - 2: Sum of Debit or Credit should be equal to Purchase Receipt Base Total
		amount = frappe.db.get_value("GL Entry", {"docstatus": 1, "voucher_no": pr.name}, [{"SUM": "debit"}])
		expected_amount = pr.base_total
		self.assertEqual(amount, expected_amount)

		company.enable_provisional_accounting_for_non_stock_items = 0
		company.save()

	def test_purchase_return_status_with_debit_note(self):
		pr = make_purchase_receipt(rejected_qty=10, received_qty=10, rate=100, do_not_save=1)
		pr.items[0].qty = 0
		pr.items[0].stock_qty = 0
		pr.submit()

		return_pr = make_purchase_receipt(
			is_return=1,
			return_against=pr.name,
			qty=0,
			rejected_qty=10 * -1,
			received_qty=10 * -1,
			do_not_save=1,
		)
		return_pr.items[0].qty = 0.0
		return_pr.items[0].stock_qty = 0.0
		return_pr.submit()

		self.assertEqual(return_pr.status, "Return")

		pi = make_purchase_invoice(return_pr.name)
		pi.submit()

		return_pr.reload()
		self.assertEqual(return_pr.status, "Completed")

	def test_purchase_return_with_zero_rate(self):
		company = "_Test Company with perpetual inventory"

		# Step - 1: Create Item
		item, warehouse = (
			make_item(properties={"is_stock_item": 1, "valuation_method": "Moving Average"}).name,
			"Stores - TCP1",
		)

		# Step - 2: Create Stock Entry (Material Receipt)
		from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry

		se = make_stock_entry(
			purpose="Material Receipt",
			item_code=item,
			qty=100,
			basic_rate=100,
			to_warehouse=warehouse,
			company=company,
		)

		# Step - 3: Create Purchase Receipt
		pr = make_purchase_receipt(
			item_code=item,
			qty=5,
			rate=0,
			warehouse=warehouse,
			company=company,
		)

		# Step - 4: Create Purchase Return
		from erpnext.controllers.sales_and_purchase_return import make_return_doc

		pr_return = make_return_doc("Purchase Receipt", pr.name)
		pr_return.save()
		pr_return.submit()

		sl_entries = get_sl_entries(pr_return.doctype, pr_return.name)
		gl_entries = get_gl_entries(pr_return.doctype, pr_return.name)

		# Test - 1: SLE Stock Value Difference should be equal to Qty * Average Rate
		average_rate = ((se.items[0].qty * se.items[0].basic_rate) + (pr.items[0].qty * pr.items[0].rate)) / (
			se.items[0].qty + pr.items[0].qty
		)
		expected_stock_value_difference = pr_return.items[0].qty * average_rate
		self.assertEqual(
			flt(sl_entries[0].stock_value_difference, 2), flt(expected_stock_value_difference, 2)
		)

		# Test - 2: GL Entries should be created for Stock Value Difference
		self.assertEqual(len(gl_entries), 2)

		# Test - 3: SLE Stock Value Difference should be equal to Debit or Credit of GL Entries.
		for entry in gl_entries:
			self.assertEqual(abs(entry.debit + entry.credit), abs(sl_entries[0].stock_value_difference))

	def non_internal_transfer_purchase_receipt(self):
		from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse

		pr_doc = make_purchase_receipt(do_not_submit=True)
		warehouse = create_warehouse("Internal Transfer Warehouse", pr_doc.company)
		pr_doc.items[0].db_set("target_warehouse", "warehouse")

		pr_doc.reload()

		self.assertEqual(pr_doc.items[0].from_warehouse, warehouse.name)

		pr_doc.save()
		pr_doc.reload()
		self.assertFalse(pr_doc.items[0].from_warehouse)

	def test_use_serial_batch_fields_for_serial_nos(self):
		from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note
		from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
		from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import (
			create_stock_reconciliation,
		)

		frappe.db.set_single_value(
			"Stock Settings", "do_not_update_serial_batch_on_creation_of_auto_bundle", 0
		)

		item_code = make_item(
			"_Test Use Serial Fields Item Serial Item",
			properties={"has_serial_no": 1, "serial_no_series": "SNU-TSFISI-.#####"},
		).name

		serial_nos = [
			"SNU-TSFISI-000011",
			"SNU-TSFISI-000012",
			"SNU-TSFISI-000013",
			"SNU-TSFISI-000014",
			"SNU-TSFISI-000015",
		]

		pr = make_purchase_receipt(
			item_code=item_code,
			qty=5,
			serial_no="\n".join(serial_nos),
			use_serial_batch_fields=1,
			rate=100,
		)

		self.assertEqual(pr.items[0].use_serial_batch_fields, 1)
		self.assertTrue(pr.items[0].serial_no)
		self.assertTrue(pr.items[0].serial_and_batch_bundle)

		sbb_doc = frappe.get_doc("Serial and Batch Bundle", pr.items[0].serial_and_batch_bundle)

		for row in sbb_doc.entries:
			self.assertTrue(row.serial_no in serial_nos)

		serial_nos.remove("SNU-TSFISI-000015")

		sr = create_stock_reconciliation(
			item_code=item_code,
			serial_no="\n".join(serial_nos),
			qty=4,
			warehouse=pr.items[0].warehouse,
			use_serial_batch_fields=1,
			do_not_submit=True,
		)
		sr.reload()

		serial_nos = get_serial_nos(sr.items[0].current_serial_no)
		self.assertEqual(len(serial_nos), 5)
		self.assertEqual(sr.items[0].current_qty, 5)

		new_serial_nos = get_serial_nos(sr.items[0].serial_no)
		self.assertEqual(len(new_serial_nos), 4)
		self.assertEqual(sr.items[0].qty, 4)
		self.assertEqual(sr.items[0].use_serial_batch_fields, 1)
		self.assertFalse(sr.items[0].current_serial_and_batch_bundle)
		self.assertFalse(sr.items[0].serial_and_batch_bundle)
		self.assertTrue(sr.items[0].current_serial_no)
		sr.submit()

		sr.reload()
		self.assertTrue(sr.items[0].current_serial_and_batch_bundle)
		self.assertTrue(sr.items[0].serial_and_batch_bundle)

		serial_no_status = frappe.db.get_value("Serial No", "SNU-TSFISI-000015", "status")

		self.assertTrue(serial_no_status != "Active")

		dn = create_delivery_note(
			item_code=item_code,
			qty=4,
			serial_no="\n".join(new_serial_nos),
			use_serial_batch_fields=1,
		)

		self.assertTrue(dn.items[0].serial_and_batch_bundle)
		self.assertEqual(dn.items[0].qty, 4)
		doc = frappe.get_doc("Serial and Batch Bundle", dn.items[0].serial_and_batch_bundle)
		for row in doc.entries:
			self.assertTrue(row.serial_no in new_serial_nos)

		for sn in new_serial_nos:
			serial_no_status = frappe.db.get_value("Serial No", sn, "status")
			self.assertTrue(serial_no_status != "Active")

		frappe.db.set_single_value(
			"Stock Settings", "do_not_update_serial_batch_on_creation_of_auto_bundle", 1
		)

	def test_sle_qty_after_transaction(self):
		item = make_item(
			"_Test Item Qty After Transaction",
			properties={"is_stock_item": 1, "valuation_method": "FIFO"},
		).name

		posting_date = today()
		posting_time = nowtime()

		# Step 1: Create Purchase Receipt
		pr = make_purchase_receipt(
			item_code=item,
			qty=1,
			rate=100,
			posting_date=posting_date,
			posting_time=posting_time,
			do_not_save=1,
		)

		for _i in range(9):
			pr.append(
				"items",
				{
					"item_code": item,
					"qty": 1,
					"rate": 100,
					"warehouse": pr.items[0].warehouse,
					"cost_center": pr.items[0].cost_center,
					"expense_account": pr.items[0].expense_account,
					"uom": pr.items[0].uom,
					"stock_uom": pr.items[0].stock_uom,
					"conversion_factor": pr.items[0].conversion_factor,
				},
			)

		self.assertEqual(len(pr.items), 10)
		pr.save()
		pr.submit()

		data = frappe.get_all(
			"Stock Ledger Entry",
			fields=["qty_after_transaction", "creation", "posting_datetime"],
			filters={"voucher_no": pr.name, "is_cancelled": 0},
			order_by="creation",
		)

		for index, d in enumerate(data):
			self.assertEqual(d.qty_after_transaction, 1 + index)

		# Step 2: Create Purchase Receipt
		pr = make_purchase_receipt(
			item_code=item,
			qty=1,
			rate=100,
			posting_date=posting_date,
			posting_time=posting_time,
			do_not_save=1,
		)

		for _i in range(9):
			pr.append(
				"items",
				{
					"item_code": item,
					"qty": 1,
					"rate": 100,
					"warehouse": pr.items[0].warehouse,
					"cost_center": pr.items[0].cost_center,
					"expense_account": pr.items[0].expense_account,
					"uom": pr.items[0].uom,
					"stock_uom": pr.items[0].stock_uom,
					"conversion_factor": pr.items[0].conversion_factor,
				},
			)

		self.assertEqual(len(pr.items), 10)
		pr.save()
		pr.submit()

		data = frappe.get_all(
			"Stock Ledger Entry",
			fields=["qty_after_transaction", "creation", "posting_datetime"],
			filters={"voucher_no": pr.name, "is_cancelled": 0},
			order_by="creation",
		)

		for index, d in enumerate(data):
			self.assertEqual(d.qty_after_transaction, 11 + index)

	def test_auto_set_batch_based_on_bundle(self):
		item_code = make_item(
			"_Test Auto Set Batch Based on Bundle",
			properties={
				"has_batch_no": 1,
				"batch_number_series": "BATCH-BNU-TASBBB-.#####",
				"create_new_batch": 1,
			},
		).name

		frappe.db.set_single_value(
			"Stock Settings", "do_not_update_serial_batch_on_creation_of_auto_bundle", 0
		)

		pr = make_purchase_receipt(
			item_code=item_code,
			qty=5,
			rate=100,
		)

		self.assertTrue(pr.items[0].batch_no)
		batch_no = get_batch_from_bundle(pr.items[0].serial_and_batch_bundle)
		self.assertEqual(pr.items[0].batch_no, batch_no)

		frappe.db.set_single_value(
			"Stock Settings", "do_not_update_serial_batch_on_creation_of_auto_bundle", 1
		)

	def test_pr_billed_amount_against_return_entry(self):
		from erpnext.accounts.doctype.purchase_invoice.purchase_invoice import make_debit_note
		from erpnext.stock.doctype.purchase_receipt.purchase_receipt import (
			make_purchase_invoice as make_pi_from_pr,
		)

		# Create a Purchase Receipt and Fully Bill it
		pr = make_purchase_receipt(qty=10)
		pi = make_pi_from_pr(pr.name)
		pi.insert()
		pi.submit()

		# Debit Note - 50% Qty & enable updating PR billed amount
		pi_return = make_debit_note(pi.name)
		pi_return.items[0].qty = -5
		pi_return.update_billed_amount_in_purchase_receipt = 1
		pi_return.submit()

		# Check if the billed amount reduced
		pr.reload()
		self.assertEqual(pr.per_billed, 50)

		pi_return.reload()
		pi_return.cancel()

		# Debit Note - 50% Qty & disable updating PR billed amount
		pi_return = make_debit_note(pi.name)
		pi_return.items[0].qty = -5
		pi_return.update_billed_amount_in_purchase_receipt = 0
		pi_return.submit()

		# Check if the billed amount stayed the same
		pr.reload()
		self.assertEqual(pr.per_billed, 100)

	def test_valuation_taxes_lcv_repost_after_billing(self):
		from erpnext.stock.doctype.landed_cost_voucher.test_landed_cost_voucher import (
			make_landed_cost_voucher,
		)

		old_perpetual_inventory = erpnext.is_perpetual_inventory_enabled("_Test Company")
		frappe.local.enable_perpetual_inventory["_Test Company"] = 1
		old_inventory_account = frappe.db.get_value("Company", "_Test Company", "default_inventory_account")
		frappe.db.set_value(
			"Company",
			"_Test Company",
			{
				"stock_received_but_not_billed": "Stock Received But Not Billed - _TC",
				"default_inventory_account": "Stock In Hand - _TC",
			},
		)

		pr = make_purchase_receipt(qty=10, rate=1000, do_not_submit=1)
		pr.append(
			"taxes",
			{
				"category": "Valuation and Total",
				"charge_type": "Actual",
				"account_head": "Freight and Forwarding Charges - _TC",
				"tax_amount": 2000,
				"description": "Test",
			},
		)
		pr.submit()
		pi = make_purchase_invoice(pr.name)
		pi.submit()
		make_landed_cost_voucher(
			company=pr.company,
			receipt_document_type="Purchase Receipt",
			receipt_document=pr.name,
			charges=2000,
			distribute_charges_based_on="Qty",
			expense_account="Expenses Included In Valuation - _TC",
		)

		gl_entries = get_gl_entries("Purchase Receipt", pr.name, skip_cancelled=True, as_dict=False)
		warehouse_account = get_warehouse_account_map("_Test Company")
		expected_gle = (
			("Stock Received But Not Billed - _TC", 0.0, 10000.0, "Main - _TC"),
			("Freight and Forwarding Charges - _TC", 0.0, 2000.0, "Main - _TC"),
			("Expenses Included In Valuation - _TC", 0.0, 2000.0, "Main - _TC"),
			(warehouse_account[pr.items[0].warehouse]["account"], 14000.0, 0.0, "Main - _TC"),
		)
		self.assertCountEqual(expected_gle, gl_entries)
		frappe.local.enable_perpetual_inventory["_Test Company"] = old_perpetual_inventory
		frappe.db.set_value("Company", "_Test Company", "default_inventory_account", old_inventory_account)

	def test_purchase_receipt_with_use_serial_batch_field_for_rejected_qty(self):
		batch_item = make_item(
			"_Test Purchase Receipt 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 Receipt Serial Item for Rejected Qty",
			properties={"has_serial_no": 1, "is_stock_item": 1},
		).name

		rej_warehouse = create_warehouse("_Test Purchase Warehouse For Rejected Qty")

		batch_no = "BATCH-BNU-TPRBI-0001"
		serial_nos = ["SNU-TPRSI-0001", "SNU-TPRSI-0002", "SNU-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,
						"company": "_Test Company",
					}
				).insert()

		pr = make_purchase_receipt(
			item_code=batch_item,
			received_qty=10,
			qty=8,
			rejected_qty=2,
			rejected_warehouse=rej_warehouse,
			use_serial_batch_fields=1,
			batch_no=batch_no,
			rate=100,
			do_not_submit=1,
		)

		pr.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": pr.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],
			},
		)

		pr.save()
		pr.submit()

		pr.reload()

		for row in pr.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_internal_transfer_with_serial_batch_items_and_their_valuation(self):
		from erpnext.controllers.sales_and_purchase_return import make_return_doc
		from erpnext.stock.doctype.delivery_note.delivery_note import make_inter_company_purchase_receipt
		from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note

		prepare_data_for_internal_transfer()

		customer = "_Test Internal Customer 2"
		company = "_Test Company with perpetual inventory"

		batch_item_doc = make_item(
			"_Test Batch Item For Stock Transfer",
			{"has_batch_no": 1, "create_new_batch": 1, "batch_number_series": "BT-BIFST-.####"},
		)

		serial_item_doc = make_item(
			"_Test Serial No Item For Stock Transfer",
			{"has_serial_no": 1, "serial_no_series": "BT-BIFST-.####"},
		)

		inward_entry = make_purchase_receipt(
			item_code=batch_item_doc.name,
			qty=10,
			rate=150,
			warehouse="Stores - TCP1",
			company="_Test Company with perpetual inventory",
			use_serial_batch_fields=1,
			do_not_submit=1,
		)

		inward_entry.append(
			"items",
			{
				"item_code": serial_item_doc.name,
				"qty": 15,
				"rate": 250,
				"item_name": serial_item_doc.item_name,
				"conversion_factor": 1.0,
				"uom": serial_item_doc.stock_uom,
				"stock_uom": serial_item_doc.stock_uom,
				"warehouse": "Stores - TCP1",
				"use_serial_batch_fields": 1,
			},
		)

		inward_entry.submit()
		inward_entry.reload()

		for row in inward_entry.items:
			self.assertTrue(row.serial_and_batch_bundle)

		inter_transfer_dn = create_delivery_note(
			item_code=inward_entry.items[0].item_code,
			company=company,
			customer=customer,
			cost_center="Main - TCP1",
			expense_account="Cost of Goods Sold - TCP1",
			qty=10,
			rate=500,
			warehouse="Stores - TCP1",
			target_warehouse="Work In Progress - TCP1",
			batch_no=get_batch_from_bundle(inward_entry.items[0].serial_and_batch_bundle),
			use_serial_batch_fields=1,
			do_not_submit=1,
		)

		inter_transfer_dn.append(
			"items",
			{
				"item_code": serial_item_doc.name,
				"qty": 15,
				"rate": 350,
				"item_name": serial_item_doc.item_name,
				"conversion_factor": 1.0,
				"uom": serial_item_doc.stock_uom,
				"stock_uom": serial_item_doc.stock_uom,
				"warehouse": "Stores - TCP1",
				"target_warehouse": "Work In Progress - TCP1",
				"serial_no": "\n".join(
					get_serial_nos_from_bundle(inward_entry.items[1].serial_and_batch_bundle)
				),
				"use_serial_batch_fields": 1,
			},
		)

		inter_transfer_dn.submit()
		inter_transfer_dn.reload()
		for row in inter_transfer_dn.items:
			if row.item_code == batch_item_doc.name:
				self.assertEqual(row.rate, 150.0)
			else:
				self.assertEqual(row.rate, 250.0)

			self.assertTrue(row.serial_and_batch_bundle)

		inter_transfer_pr = make_inter_company_purchase_receipt(inter_transfer_dn.name)
		for row in inter_transfer_pr.items:
			row.from_warehouse = "Work In Progress - TCP1"
			row.warehouse = "Stores - TCP1"
		inter_transfer_pr.submit()

		for row in inter_transfer_pr.items:
			if row.item_code == batch_item_doc.name:
				self.assertEqual(row.rate, 150.0)
			else:
				self.assertEqual(row.rate, 250.0)

			self.assertTrue(row.serial_and_batch_bundle)

		inter_transfer_pr_return = make_return_doc("Purchase Receipt", inter_transfer_pr.name)

		inter_transfer_pr_return.submit()
		inter_transfer_pr_return.reload()
		for row in inter_transfer_pr_return.items:
			self.assertTrue(row.serial_and_batch_bundle)
			if row.item_code == serial_item_doc.name:
				self.assertEqual(row.rate, 250.0)
				serial_nos = get_serial_nos_from_bundle(row.serial_and_batch_bundle)
				for sn in serial_nos:
					serial_no_details = frappe.db.get_value(
						"Serial No", sn, ["status", "warehouse"], as_dict=1
					)
					self.assertTrue(serial_no_details.status == "Active")
					self.assertEqual(serial_no_details.warehouse, "Work In Progress - TCP1")

		inter_transfer_dn_return = make_return_doc("Delivery Note", inter_transfer_dn.name)
		inter_transfer_dn_return.posting_date = today()
		inter_transfer_dn_return.posting_time = nowtime()
		for row in inter_transfer_dn_return.items:
			row.target_warehouse = "Work In Progress - TCP1"

		inter_transfer_dn_return.submit()
		inter_transfer_dn_return.reload()

		for row in inter_transfer_dn_return.items:
			self.assertTrue(row.serial_and_batch_bundle)

	def test_internal_transfer_with_serial_batch_items_without_use_serial_batch_fields(self):
		from erpnext.controllers.sales_and_purchase_return import make_return_doc
		from erpnext.stock.doctype.delivery_note.delivery_note import make_inter_company_purchase_receipt
		from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note

		frappe.db.set_single_value("Stock Settings", "use_serial_batch_fields", 0)

		prepare_data_for_internal_transfer()

		customer = "_Test Internal Customer 2"
		company = "_Test Company with perpetual inventory"

		batch_item_doc = make_item(
			"_Test Batch Item For Stock Transfer USE SERIAL BATCH FIELDS",
			{"has_batch_no": 1, "create_new_batch": 1, "batch_number_series": "USBF-BT-BIFST-.####"},
		)

		serial_item_doc = make_item(
			"_Test Serial No Item For Stock Transfer USE SERIAL BATCH FIELDS",
			{"has_serial_no": 1, "serial_no_series": "USBF-BT-BIFST-.####"},
		)

		inward_entry = make_purchase_receipt(
			item_code=batch_item_doc.name,
			qty=10,
			rate=150,
			warehouse="Stores - TCP1",
			company="_Test Company with perpetual inventory",
			use_serial_batch_fields=0,
			do_not_submit=1,
		)

		inward_entry.append(
			"items",
			{
				"item_code": serial_item_doc.name,
				"qty": 15,
				"rate": 250,
				"item_name": serial_item_doc.item_name,
				"conversion_factor": 1.0,
				"uom": serial_item_doc.stock_uom,
				"stock_uom": serial_item_doc.stock_uom,
				"warehouse": "Stores - TCP1",
				"use_serial_batch_fields": 0,
			},
		)

		inward_entry.submit()
		inward_entry.reload()

		for row in inward_entry.items:
			self.assertTrue(row.serial_and_batch_bundle)

		inter_transfer_dn = create_delivery_note(
			item_code=inward_entry.items[0].item_code,
			company=company,
			customer=customer,
			cost_center="Main - TCP1",
			expense_account="Cost of Goods Sold - TCP1",
			qty=10,
			rate=500,
			warehouse="Stores - TCP1",
			target_warehouse="Work In Progress - TCP1",
			batch_no=get_batch_from_bundle(inward_entry.items[0].serial_and_batch_bundle),
			use_serial_batch_fields=0,
			do_not_submit=1,
		)

		inter_transfer_dn.append(
			"items",
			{
				"item_code": serial_item_doc.name,
				"qty": 15,
				"rate": 350,
				"item_name": serial_item_doc.item_name,
				"conversion_factor": 1.0,
				"uom": serial_item_doc.stock_uom,
				"stock_uom": serial_item_doc.stock_uom,
				"warehouse": "Stores - TCP1",
				"target_warehouse": "Work In Progress - TCP1",
				"serial_no": "\n".join(
					get_serial_nos_from_bundle(inward_entry.items[1].serial_and_batch_bundle)
				),
				"use_serial_batch_fields": 0,
			},
		)

		inter_transfer_dn.submit()
		inter_transfer_dn.reload()
		for row in inter_transfer_dn.items:
			if row.item_code == batch_item_doc.name:
				self.assertEqual(row.rate, 150.0)
			else:
				self.assertEqual(row.rate, 250.0)

			self.assertTrue(row.serial_and_batch_bundle)

		inter_transfer_pr = make_inter_company_purchase_receipt(inter_transfer_dn.name)
		for row in inter_transfer_pr.items:
			row.from_warehouse = "Work In Progress - TCP1"
			row.warehouse = "Stores - TCP1"
		inter_transfer_pr.submit()

		for row in inter_transfer_pr.items:
			if row.item_code == batch_item_doc.name:
				self.assertEqual(row.rate, 150.0)
			else:
				self.assertEqual(row.rate, 250.0)

			self.assertTrue(row.serial_and_batch_bundle)

		inter_transfer_pr_return = make_return_doc("Purchase Receipt", inter_transfer_pr.name)

		inter_transfer_pr_return.submit()
		inter_transfer_pr_return.reload()
		for row in inter_transfer_pr_return.items:
			self.assertTrue(row.serial_and_batch_bundle)
			if row.item_code == serial_item_doc.name:
				self.assertEqual(row.rate, 250.0)
				serial_nos = get_serial_nos_from_bundle(row.serial_and_batch_bundle)
				for sn in serial_nos:
					serial_no_details = frappe.db.get_value(
						"Serial No", sn, ["status", "warehouse"], as_dict=1
					)
					self.assertTrue(serial_no_details.status == "Active")
					self.assertEqual(serial_no_details.warehouse, "Work In Progress - TCP1")

		inter_transfer_dn_return = make_return_doc("Delivery Note", inter_transfer_dn.name)
		inter_transfer_dn_return.posting_date = today()
		inter_transfer_dn_return.posting_time = nowtime()
		for row in inter_transfer_dn_return.items:
			row.target_warehouse = "Work In Progress - TCP1"

		inter_transfer_dn_return.submit()
		inter_transfer_dn_return.reload()

		for row in inter_transfer_dn_return.items:
			self.assertTrue(row.serial_and_batch_bundle)

		frappe.db.set_single_value("Stock Settings", "use_serial_batch_fields", 1)

	def test_purchase_receipt_bill_for_rejected_quantity_in_purchase_invoice(self):
		item_code = make_item(
			"_Test Purchase Receipt Bill For Rejected Quantity",
			properties={"is_stock_item": 1},
		).name

		pr = make_purchase_receipt(item_code=item_code, qty=5, rate=100)

		return_pr = make_purchase_receipt(
			item_code=item_code,
			is_return=1,
			return_against=pr.name,
			qty=-2,
			do_not_submit=1,
		)
		return_pr.items[0].purchase_receipt_item = pr.items[0].name
		return_pr.submit()
		old_value = frappe.db.get_single_value(
			"Buying Settings", "bill_for_rejected_quantity_in_purchase_invoice"
		)

		frappe.db.set_single_value("Buying Settings", "bill_for_rejected_quantity_in_purchase_invoice", 0)
		pi = make_purchase_invoice(pr.name)
		self.assertEqual(pi.items[0].qty, 3)

		frappe.db.set_single_value("Buying Settings", "bill_for_rejected_quantity_in_purchase_invoice", 1)
		pi = make_purchase_invoice(pr.name)
		pi.submit()
		self.assertEqual(pi.items[0].qty, 5)

		frappe.db.set_single_value(
			"Buying Settings", "bill_for_rejected_quantity_in_purchase_invoice", old_value
		)

	def test_zero_valuation_rate_for_batched_item(self):
		from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry

		item = make_item(
			"_Test Zero Valuation Rate For the Batch Item",
			{
				"is_purchase_item": 1,
				"is_stock_item": 1,
				"has_batch_no": 1,
				"create_new_batch": 1,
				"batch_number_series": "TZVRFORBATCH.#####",
				"valuation_rate": 200,
			},
		)

		pi = make_purchase_receipt(
			qty=10,
			rate=0,
			item_code=item.name,
		)

		pi.reload()
		batch_no = get_batch_from_bundle(pi.items[0].serial_and_batch_bundle)

		se = make_stock_entry(
			purpose="Material Issue",
			item_code=item.name,
			source=pi.items[0].warehouse,
			qty=10,
			batch_no=batch_no,
			use_serial_batch_fields=0,
		)

		se.submit()

		se.reload()

		self.assertEqual(se.items[0].valuation_rate, 0)
		self.assertEqual(se.items[0].basic_rate, 0)

		sabb_doc = frappe.get_doc("Serial and Batch Bundle", se.items[0].serial_and_batch_bundle)
		for row in sabb_doc.entries:
			self.assertEqual(row.incoming_rate, 0)

	def test_purchase_return_from_accepted_and_rejected_warehouse(self):
		from erpnext.stock.doctype.purchase_receipt.purchase_receipt import (
			make_purchase_return,
		)

		item = make_item(
			"_Test PR Item With Return From Accepted and Rejected WH",
			{
				"is_purchase_item": 1,
				"is_stock_item": 1,
				"has_batch_no": 1,
				"create_new_batch": 1,
				"batch_number_series": "SD-TZVRFORBATCH.#####",
				"valuation_rate": 200,
			},
		)

		pr = make_purchase_receipt(
			qty=10,
			rejected_qty=5,
			rate=100,
			item_code=item.name,
		)

		pr.reload()
		self.assertTrue(pr.items[0].serial_and_batch_bundle)
		self.assertTrue(pr.items[0].rejected_serial_and_batch_bundle)

		return_pr = make_purchase_return(pr.name)
		return_pr.submit()

		return_pr.reload()
		self.assertTrue(return_pr.items[0].serial_and_batch_bundle)
		self.assertTrue(return_pr.items[0].rejected_serial_and_batch_bundle)

		self.assertEqual(
			return_pr.items[0].qty,
			frappe.db.get_value(
				"Serial and Batch Bundle", return_pr.items[0].serial_and_batch_bundle, "total_qty"
			),
		)

		self.assertEqual(
			return_pr.items[0].rejected_qty,
			frappe.db.get_value(
				"Serial and Batch Bundle", return_pr.items[0].rejected_serial_and_batch_bundle, "total_qty"
			),
		)

	def test_manufacturing_and_expiry_date_for_batch(self):
		item = make_item(
			"_Test Manufacturing and Expiry Date For Batch",
			{
				"is_purchase_item": 1,
				"is_stock_item": 1,
				"has_batch_no": 1,
				"create_new_batch": 1,
				"batch_number_series": "B-MEBATCH.#####",
				"has_expiry_date": 1,
				"shelf_life_in_days": 5,
			},
		)

		pr = make_purchase_receipt(
			qty=10,
			rate=100,
			item_code=item.name,
			posting_date=today(),
		)

		pr.reload()
		self.assertTrue(pr.items[0].serial_and_batch_bundle)

		batch_no = get_batch_from_bundle(pr.items[0].serial_and_batch_bundle)
		batch = frappe.get_doc("Batch", batch_no)
		self.assertEqual(batch.manufacturing_date, getdate(today()))
		self.assertEqual(batch.expiry_date, getdate(add_days(today(), 5)))

	def test_purchase_return_from_rejected_warehouse(self):
		from erpnext.stock.doctype.purchase_receipt.purchase_receipt import (
			make_purchase_return_against_rejected_warehouse,
		)

		item_code = "_Test Item Return from Rejected Warehouse 11"
		create_item(item_code)

		warehouse = create_warehouse("_Test Warehouse Return Qty Warehouse 11")
		rejected_warehouse = create_warehouse("_Test Rejected Warehouse Return Qty Warehouse 11")

		# Step 1: Create Purchase Receipt with valuation rate 100
		pr = make_purchase_receipt(
			item_code=item_code,
			warehouse=warehouse,
			qty=24,
			rate=100,
			rejected_qty=31,
			rejected_warehouse=rejected_warehouse,
		)

		pr_return = make_purchase_return_against_rejected_warehouse(pr.name)
		pr_return.save()
		pr_return.submit()

		self.assertEqual(pr_return.items[0].warehouse, rejected_warehouse)
		self.assertEqual(pr_return.items[0].qty, 31 * -1)
		self.assertEqual(pr_return.items[0].rejected_qty, 0.0)
		self.assertEqual(pr_return.items[0].rejected_warehouse, "")

	def test_tax_account_heads_on_lcv_and_item_repost(self):
		"""
		PO -> PR -> PI
		PR -> LCV
		Backdated `Repost Item valuation` should not merge tax account heads into stock_rbnb
		"""
		from erpnext.accounts.doctype.account.test_account import create_account
		from erpnext.buying.doctype.purchase_order.test_purchase_order import (
			create_purchase_order,
			make_pr_against_po,
		)
		from erpnext.stock.doctype.purchase_receipt.purchase_receipt import make_purchase_invoice

		stock_rbnb = "Stock Received But Not Billed - _TC"
		stock_in_hand = "Stock In Hand - _TC"
		test_cc = "_Test Cost Center - _TC"
		test_company = "_Test Company"
		creditors = "Creditors - _TC"
		lcv_expense_account = "Expenses Included In Valuation - _TC"

		company_doc = frappe.get_doc("Company", test_company)
		company_doc.enable_perpetual_inventory = True
		company_doc.stock_received_but_not_billed = stock_rbnb
		company_doc.default_inventory_account = stock_in_hand
		company_doc.save()

		packaging_charges_account = create_account(
			account_name="Packaging Charges",
			parent_account="Indirect Expenses - _TC",
			company=test_company,
			account_type="Tax",
		)

		po = create_purchase_order(qty=10, rate=100, do_not_save=1)
		po.taxes = []
		po.append(
			"taxes",
			{
				"category": "Valuation and Total",
				"account_head": packaging_charges_account,
				"cost_center": test_cc,
				"description": "Test",
				"add_deduct_tax": "Add",
				"charge_type": "Actual",
				"tax_amount": 250,
			},
		)
		po.save().submit()

		pr = make_pr_against_po(po.name, received_qty=10)
		pr_gl_entries = get_gl_entries(pr.doctype, pr.name, skip_cancelled=True)
		expected_pr_gles = [
			{"account": stock_rbnb, "debit": 0.0, "credit": 1000.0, "cost_center": test_cc},
			{"account": stock_in_hand, "debit": 1250.0, "credit": 0.0, "cost_center": test_cc},
			{"account": packaging_charges_account, "debit": 0.0, "credit": 250.0, "cost_center": test_cc},
		]
		self.assertEqual(expected_pr_gles, pr_gl_entries)

		# Make PI against Purchase Receipt
		pi = make_purchase_invoice(pr.name).save().submit()
		pi_gl_entries = get_gl_entries(pi.doctype, pi.name, skip_cancelled=True)
		expected_pi_gles = [
			{"account": stock_rbnb, "debit": 1000.0, "credit": 0.0, "cost_center": test_cc},
			{"account": packaging_charges_account, "debit": 250.0, "credit": 0.0, "cost_center": test_cc},
			{"account": creditors, "debit": 0.0, "credit": 1250.0, "cost_center": None},
		]
		self.assertEqual(expected_pi_gles, pi_gl_entries)

		lcv = self.create_lcv(pr.doctype, pr.name, test_company, lcv_expense_account)
		pr_gles_after_lcv = get_gl_entries(pr.doctype, pr.name, skip_cancelled=True)
		expected_pr_gles_after_lcv = [
			{"account": stock_rbnb, "debit": 0.0, "credit": 1000.0, "cost_center": test_cc},
			{"account": stock_in_hand, "debit": 1300.0, "credit": 0.0, "cost_center": test_cc},
			{"account": packaging_charges_account, "debit": 0.0, "credit": 250.0, "cost_center": test_cc},
			{"account": lcv_expense_account, "debit": 0.0, "credit": 50.0, "cost_center": test_cc},
		]
		self.assertEqual(expected_pr_gles_after_lcv, pr_gles_after_lcv)

		# Trigger Repost Item Valudation on a older date
		repost_doc = frappe.get_doc(
			{
				"doctype": "Repost Item Valuation",
				"based_on": "Item and Warehouse",
				"item_code": pr.items[0].item_code,
				"warehouse": pr.items[0].warehouse,
				"posting_date": add_days(pr.posting_date, -1),
				"posting_time": "00:00:00",
				"company": pr.company,
				"allow_negative_stock": 1,
				"via_landed_cost_voucher": 0,
				"allow_zero_rate": 0,
			}
		)
		repost_doc.save().submit()

		pr_gles_after_repost = get_gl_entries(pr.doctype, pr.name, skip_cancelled=True)
		expected_pr_gles_after_repost = [
			{"account": stock_rbnb, "debit": 0.0, "credit": 1000.0, "cost_center": test_cc},
			{"account": stock_in_hand, "debit": 1300.0, "credit": 0.0, "cost_center": test_cc},
			{"account": packaging_charges_account, "debit": 0.0, "credit": 250.0, "cost_center": test_cc},
			{"account": lcv_expense_account, "debit": 0.0, "credit": 50.0, "cost_center": test_cc},
		]
		self.assertEqual(len(pr_gles_after_repost), len(expected_pr_gles_after_repost))
		self.assertEqual(expected_pr_gles_after_repost, pr_gles_after_repost)

		# teardown
		lcv.reload()
		lcv.cancel()
		pi.reload()
		pi.cancel()
		pr.reload()
		pr.cancel()

		company_doc.enable_perpetual_inventory = False
		company_doc.stock_received_but_not_billed = None
		company_doc.default_inventory_account = None
		company_doc.save()

	def create_lcv(self, receipt_document_type, receipt_document, company, expense_account, charges=50):
		ref_doc = frappe.get_doc(receipt_document_type, receipt_document)

		lcv = frappe.new_doc("Landed Cost Voucher")
		lcv.company = company
		lcv.distribute_charges_based_on = "Qty"
		lcv.set(
			"purchase_receipts",
			[
				{
					"receipt_document_type": receipt_document_type,
					"receipt_document": receipt_document,
					"supplier": ref_doc.supplier,
					"posting_date": ref_doc.posting_date,
					"grand_total": ref_doc.base_grand_total,
				}
			],
		)

		lcv.set(
			"taxes",
			[
				{
					"description": "Testing",
					"expense_account": expense_account,
					"amount": charges,
				}
			],
		)
		lcv.save().submit()
		return lcv

	def test_tax_account_heads_on_item_repost_without_lcv(self):
		"""
		PO -> PR -> PI
		Backdated `Repost Item valuation` should not merge tax account heads into stock_rbnb if Purchase Receipt was created first
		This scenario is without LCV
		"""
		from erpnext.accounts.doctype.account.test_account import create_account
		from erpnext.buying.doctype.purchase_order.test_purchase_order import (
			create_purchase_order,
			make_pr_against_po,
		)
		from erpnext.stock.doctype.purchase_receipt.purchase_receipt import make_purchase_invoice

		stock_rbnb = "Stock Received But Not Billed - _TC"
		stock_in_hand = "Stock In Hand - _TC"
		test_cc = "_Test Cost Center - _TC"
		test_company = "_Test Company"
		creditors = "Creditors - _TC"

		company_doc = frappe.get_doc("Company", test_company)
		company_doc.enable_perpetual_inventory = True
		company_doc.stock_received_but_not_billed = stock_rbnb
		company_doc.default_inventory_account = stock_in_hand
		company_doc.save()

		packaging_charges_account = create_account(
			account_name="Packaging Charges",
			parent_account="Indirect Expenses - _TC",
			company=test_company,
			account_type="Tax",
		)

		po = create_purchase_order(qty=10, rate=100, do_not_save=1)
		po.taxes = []
		po.append(
			"taxes",
			{
				"category": "Valuation and Total",
				"account_head": packaging_charges_account,
				"cost_center": test_cc,
				"description": "Test",
				"add_deduct_tax": "Add",
				"charge_type": "Actual",
				"tax_amount": 250,
			},
		)
		po.save().submit()

		pr = make_pr_against_po(po.name, received_qty=10)
		pr_gl_entries = get_gl_entries(pr.doctype, pr.name, skip_cancelled=True)
		expected_pr_gles = [
			{"account": stock_rbnb, "debit": 0.0, "credit": 1000.0, "cost_center": test_cc},
			{"account": stock_in_hand, "debit": 1250.0, "credit": 0.0, "cost_center": test_cc},
			{"account": packaging_charges_account, "debit": 0.0, "credit": 250.0, "cost_center": test_cc},
		]
		self.assertEqual(expected_pr_gles, pr_gl_entries)

		# Make PI against Purchase Receipt
		pi = make_purchase_invoice(pr.name).save().submit()
		pi_gl_entries = get_gl_entries(pi.doctype, pi.name, skip_cancelled=True)
		expected_pi_gles = [
			{"account": stock_rbnb, "debit": 1000.0, "credit": 0.0, "cost_center": test_cc},
			{"account": packaging_charges_account, "debit": 250.0, "credit": 0.0, "cost_center": test_cc},
			{"account": creditors, "debit": 0.0, "credit": 1250.0, "cost_center": None},
		]
		self.assertEqual(expected_pi_gles, pi_gl_entries)

		# Trigger Repost Item Valudation on a older date
		repost_doc = frappe.get_doc(
			{
				"doctype": "Repost Item Valuation",
				"based_on": "Item and Warehouse",
				"item_code": pr.items[0].item_code,
				"warehouse": pr.items[0].warehouse,
				"posting_date": add_days(pr.posting_date, -1),
				"posting_time": "00:00:00",
				"company": pr.company,
				"allow_negative_stock": 1,
				"via_landed_cost_voucher": 0,
				"allow_zero_rate": 0,
			}
		)
		repost_doc.save().submit()

		pr_gles_after_repost = get_gl_entries(pr.doctype, pr.name, skip_cancelled=True)
		expected_pr_gles_after_repost = [
			{"account": stock_rbnb, "debit": 0.0, "credit": 1000.0, "cost_center": test_cc},
			{"account": stock_in_hand, "debit": 1250.0, "credit": 0.0, "cost_center": test_cc},
			{"account": packaging_charges_account, "debit": 0.0, "credit": 250.0, "cost_center": test_cc},
		]
		self.assertEqual(len(pr_gles_after_repost), len(expected_pr_gles_after_repost))
		self.assertEqual(expected_pr_gles_after_repost, pr_gles_after_repost)

		# teardown
		pi.reload()
		pi.cancel()
		pr.reload()
		pr.cancel()

		company_doc.enable_perpetual_inventory = False
		company_doc.stock_received_but_not_billed = None
		company_doc.default_inventory_account = None
		company_doc.save()

	def test_do_not_use_batchwise_valuation_rate(self):
		from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note

		item_code = "Test Item for Do Not Use Batchwise Valuation"
		make_item(
			item_code,
			properties={
				"is_stock_item": 1,
				"has_batch_no": 1,
				"create_new_batch": 1,
				"batch_number_series": "TIDNBV-.#####",
				"valuation_method": "Moving Average",
			},
		)

		# 1st pr for 100 rate
		pr = make_purchase_receipt(
			item_code=item_code,
			qty=1,
			rate=100,
			posting_date=add_days(today(), -2),
		)

		make_purchase_receipt(
			item_code=item_code,
			qty=1,
			rate=200,
			posting_date=add_days(today(), -1),
		)

		dn = create_delivery_note(
			item_code=item_code,
			qty=1,
			rate=300,
			posting_date=today(),
			use_serial_batch_fields=1,
			batch_no=get_batch_from_bundle(pr.items[0].serial_and_batch_bundle),
		)
		dn.reload()
		bundle = dn.items[0].serial_and_batch_bundle

		valuation_rate = frappe.db.get_value("Serial and Batch Bundle", bundle, "avg_rate")
		self.assertEqual(valuation_rate, 100.0)

		doc = frappe.get_doc("Stock Settings")
		doc.do_not_use_batchwise_valuation = 1
		doc.flags.ignore_validate = True
		doc.save()

		pr.repost_future_sle_and_gle(force=True)

		valuation_rate = frappe.db.get_value("Serial and Batch Bundle", bundle, "avg_rate")
		self.assertEqual(valuation_rate, 150)

		doc = frappe.get_doc("Stock Settings")
		doc.do_not_use_batchwise_valuation = 0
		doc.flags.ignore_validate = True
		doc.save()

	def test_status_mapping(self):
		item_code = "item_for_status"
		create_item(item_code)
		create_item("item_for_status")
		warehouse = create_warehouse("Stores")
		supplier = "Test Supplier"
		create_supplier(supplier_name=supplier)
		pr = make_purchase_receipt(
			item_code=item_code,
			warehouse=warehouse,
			qty=1,
			rate=0,
		)
		self.assertEqual(pr.grand_total, 0.0)
		self.assertEqual(pr.status, "Completed")

	def test_internal_transfer_for_batch_items_with_cancel(self):
		from erpnext.controllers.sales_and_purchase_return import make_return_doc
		from erpnext.stock.doctype.delivery_note.delivery_note import make_inter_company_purchase_receipt
		from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note

		frappe.db.set_single_value("Stock Settings", "use_serial_batch_fields", 0)

		prepare_data_for_internal_transfer()

		customer = "_Test Internal Customer 2"
		company = "_Test Company with perpetual inventory"

		batch_item_doc = make_item(
			"_Test Batch Item For Stock Transfer Cancel Case",
			{"has_batch_no": 1, "create_new_batch": 1, "batch_number_series": "USBF-BT-CANBIFST-.####"},
		)

		serial_item_doc = make_item(
			"_Test Serial No Item For Stock Transfer Cancel Case",
			{"has_serial_no": 1, "serial_no_series": "USBF-BT-CANBIFST-.####"},
		)

		inward_entry = make_purchase_receipt(
			item_code=batch_item_doc.name,
			qty=10,
			rate=150,
			warehouse="Stores - TCP1",
			company="_Test Company with perpetual inventory",
			use_serial_batch_fields=0,
			do_not_submit=1,
		)

		inward_entry.append(
			"items",
			{
				"item_code": serial_item_doc.name,
				"qty": 15,
				"rate": 250,
				"item_name": serial_item_doc.item_name,
				"conversion_factor": 1.0,
				"uom": serial_item_doc.stock_uom,
				"stock_uom": serial_item_doc.stock_uom,
				"warehouse": "Stores - TCP1",
				"use_serial_batch_fields": 0,
			},
		)

		inward_entry.submit()
		inward_entry.reload()

		for row in inward_entry.items:
			self.assertTrue(row.serial_and_batch_bundle)

		inter_transfer_dn = create_delivery_note(
			item_code=inward_entry.items[0].item_code,
			company=company,
			customer=customer,
			cost_center="Main - TCP1",
			expense_account="Cost of Goods Sold - TCP1",
			qty=10,
			rate=500,
			warehouse="Stores - TCP1",
			target_warehouse="Work In Progress - TCP1",
			batch_no=get_batch_from_bundle(inward_entry.items[0].serial_and_batch_bundle),
			use_serial_batch_fields=0,
			do_not_submit=1,
		)

		inter_transfer_dn.append(
			"items",
			{
				"item_code": serial_item_doc.name,
				"qty": 15,
				"rate": 350,
				"item_name": serial_item_doc.item_name,
				"conversion_factor": 1.0,
				"uom": serial_item_doc.stock_uom,
				"stock_uom": serial_item_doc.stock_uom,
				"warehouse": "Stores - TCP1",
				"target_warehouse": "Work In Progress - TCP1",
				"serial_no": "\n".join(
					get_serial_nos_from_bundle(inward_entry.items[1].serial_and_batch_bundle)
				),
				"use_serial_batch_fields": 0,
			},
		)

		inter_transfer_dn.submit()
		inter_transfer_dn.reload()
		for row in inter_transfer_dn.items:
			if row.item_code == batch_item_doc.name:
				self.assertEqual(row.rate, 150.0)
			else:
				self.assertEqual(row.rate, 250.0)

			self.assertTrue(row.serial_and_batch_bundle)

		inter_transfer_pr = make_inter_company_purchase_receipt(inter_transfer_dn.name)
		for row in inter_transfer_pr.items:
			row.from_warehouse = "Work In Progress - TCP1"
			row.warehouse = "Stores - TCP1"
		inter_transfer_pr.submit()

		for row in inter_transfer_pr.items:
			if row.item_code == batch_item_doc.name:
				self.assertEqual(row.rate, 150.0)
			else:
				self.assertEqual(row.rate, 250.0)

			self.assertTrue(row.serial_and_batch_bundle)

		inter_transfer_pr.cancel()
		inter_transfer_dn.cancel()

		frappe.db.set_single_value("Stock Settings", "use_serial_batch_fields", 1)

	def test_internal_transfer_for_batch_items_with_cancel_use_serial_batch_fields(self):
		from erpnext.controllers.sales_and_purchase_return import make_return_doc
		from erpnext.stock.doctype.delivery_note.delivery_note import make_inter_company_purchase_receipt
		from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note

		frappe.db.set_single_value("Stock Settings", "use_serial_batch_fields", 1)
		frappe.db.set_single_value("Stock Settings", "auto_create_serial_and_batch_bundle_for_outward", 0)

		prepare_data_for_internal_transfer()

		customer = "_Test Internal Customer 2"
		company = "_Test Company with perpetual inventory"

		batch_item_doc = make_item(
			"_Test Batch Item For Stock Transfer Cancel Case 11",
			{"has_batch_no": 1, "create_new_batch": 1, "batch_number_series": "USBF11-BT-CANBIFST-.####"},
		)

		serial_item_doc = make_item(
			"_Test Serial No Item For Stock Transfer Cancel Case 11",
			{"has_serial_no": 1, "serial_no_series": "USBF11-BT-CANBIFST-.####"},
		)

		inward_entry = make_purchase_receipt(
			item_code=batch_item_doc.name,
			qty=10,
			rate=150,
			warehouse="Stores - TCP1",
			company="_Test Company with perpetual inventory",
			use_serial_batch_fields=1,
			do_not_submit=1,
		)

		inward_entry.append(
			"items",
			{
				"item_code": serial_item_doc.name,
				"qty": 15,
				"rate": 250,
				"item_name": serial_item_doc.item_name,
				"conversion_factor": 1.0,
				"uom": serial_item_doc.stock_uom,
				"stock_uom": serial_item_doc.stock_uom,
				"warehouse": "Stores - TCP1",
				"use_serial_batch_fields": 1,
			},
		)

		inward_entry.submit()
		inward_entry.reload()

		for row in inward_entry.items:
			self.assertTrue(row.serial_and_batch_bundle)

		inter_transfer_dn = create_delivery_note(
			item_code=inward_entry.items[0].item_code,
			company=company,
			customer=customer,
			cost_center="Main - TCP1",
			expense_account="Cost of Goods Sold - TCP1",
			qty=10,
			rate=500,
			warehouse="Stores - TCP1",
			target_warehouse="Work In Progress - TCP1",
			batch_no=get_batch_from_bundle(inward_entry.items[0].serial_and_batch_bundle),
			use_serial_batch_fields=1,
			do_not_submit=1,
		)

		inter_transfer_dn.append(
			"items",
			{
				"item_code": serial_item_doc.name,
				"qty": 15,
				"rate": 350,
				"item_name": serial_item_doc.item_name,
				"conversion_factor": 1.0,
				"uom": serial_item_doc.stock_uom,
				"stock_uom": serial_item_doc.stock_uom,
				"warehouse": "Stores - TCP1",
				"target_warehouse": "Work In Progress - TCP1",
				"serial_no": "\n".join(
					get_serial_nos_from_bundle(inward_entry.items[1].serial_and_batch_bundle)
				),
				"use_serial_batch_fields": 1,
			},
		)

		inter_transfer_dn.submit()
		inter_transfer_dn.reload()
		for row in inter_transfer_dn.items:
			if row.item_code == batch_item_doc.name:
				self.assertEqual(row.rate, 150.0)
			else:
				self.assertEqual(row.rate, 250.0)

			self.assertTrue(row.serial_and_batch_bundle)

		inter_transfer_pr = make_inter_company_purchase_receipt(inter_transfer_dn.name)
		for row in inter_transfer_pr.items:
			row.from_warehouse = "Work In Progress - TCP1"
			row.warehouse = "Stores - TCP1"
		inter_transfer_pr.submit()

		for row in inter_transfer_pr.items:
			if row.item_code == batch_item_doc.name:
				self.assertEqual(row.rate, 150.0)
			else:
				self.assertEqual(row.rate, 250.0)

			self.assertTrue(row.serial_and_batch_bundle)

		inter_transfer_pr.cancel()
		inter_transfer_dn.cancel()
		frappe.db.set_single_value("Stock Settings", "auto_create_serial_and_batch_bundle_for_outward", 1)

	def test_sles_with_same_posting_datetime_and_creation(self):
		from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
		from erpnext.stock.report.stock_balance.stock_balance import execute

		item_code = "Test Item for SLE with same posting datetime and creation"
		create_item(item_code)

		pr = make_purchase_receipt(
			item_code=item_code,
			qty=10,
			rate=100,
			posting_date="2023-11-06",
			posting_time="00:00:00",
		)

		sr = make_stock_entry(
			item_code=item_code,
			source=pr.items[0].warehouse,
			qty=10,
			posting_date="2023-11-07",
			posting_time="14:28:0.330404",
		)

		sle = frappe.db.get_value(
			"Stock Ledger Entry",
			{"voucher_type": sr.doctype, "voucher_no": sr.name, "item_code": sr.items[0].item_code},
			"name",
		)

		sle_doc = frappe.get_doc("Stock Ledger Entry", sle)
		sle_doc.db_set("creation", "2023-11-07 14:28:01.208930")

		sle_doc.reload()
		self.assertEqual(get_datetime(sle_doc.creation), get_datetime("2023-11-07 14:28:01.208930"))

		sr = make_stock_entry(
			item_code=item_code,
			target=pr.items[0].warehouse,
			qty=50,
			posting_date="2023-11-07",
			posting_time="14:28:0.920825",
		)

		sle = frappe.db.get_value(
			"Stock Ledger Entry",
			{"voucher_type": sr.doctype, "voucher_no": sr.name, "item_code": sr.items[0].item_code},
			"name",
		)

		sle_doc = frappe.get_doc("Stock Ledger Entry", sle)
		sle_doc.db_set("creation", "2023-11-07 14:28:01.044561")

		sle_doc.reload()
		self.assertEqual(get_datetime(sle_doc.creation), get_datetime("2023-11-07 14:28:01.044561"))

		pr.repost_future_sle_and_gle(force=True)

		columns, data = execute(
			filters=frappe._dict(
				{"item_code": [item_code], "warehouse": [pr.items[0].warehouse], "company": pr.company}
			)
		)

		self.assertEqual(data[0].get("bal_qty"), 50.0)

	def test_same_stock_and_transaction_uom_conversion_factor(self):
		item_code = "Test Item for Same Stock and Transaction UOM Conversion Factor"
		create_item(item_code)

		pr = make_purchase_receipt(
			item_code=item_code,
			qty=10,
			rate=100,
			stock_uom="Nos",
			transaction_uom="Nos",
			conversion_factor=10,
		)

		self.assertEqual(pr.items[0].conversion_factor, 1.0)

	def test_purchase_receipt_return_valuation_without_use_serial_batch_field(self):
		from erpnext.stock.doctype.purchase_receipt.purchase_receipt import make_purchase_return

		batch_item = make_item(
			"_Test Purchase Receipt Return Valuation Batch Item",
			properties={
				"has_batch_no": 1,
				"create_new_batch": 1,
				"is_stock_item": 1,
				"batch_number_series": "BRTN-TPRBI-.#####",
			},
		).name

		serial_item = make_item(
			"_Test Purchase Receipt Return Valuation Serial Item",
			properties={"has_serial_no": 1, "is_stock_item": 1, "serial_no_series": "SRTN-TPRSI-.#####"},
		).name

		rej_warehouse = create_warehouse("_Test Purchase Warehouse For Rejected Qty")

		pr = make_purchase_receipt(
			item_code=batch_item,
			received_qty=10,
			qty=8,
			rejected_qty=2,
			rejected_warehouse=rej_warehouse,
			rate=300,
			do_not_submit=1,
			use_serial_batch_fields=0,
		)

		pr.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": pr.items[0].warehouse,
				"use_serial_batch_fields": 0,
				"rejected_warehouse": rej_warehouse,
			},
		)

		pr.save()
		pr.submit()
		pr.reload()

		batch_no = get_batch_from_bundle(pr.items[0].serial_and_batch_bundle)
		rejected_batch_no = get_batch_from_bundle(pr.items[0].rejected_serial_and_batch_bundle)

		self.assertEqual(batch_no, rejected_batch_no)

		return_entry = make_purchase_return(pr.name)

		return_entry.save()
		return_entry.submit()
		return_entry.reload()

		for row in return_entry.items:
			if row.item_code == batch_item:
				bundle_data = frappe.get_all(
					"Serial and Batch Entry",
					filters={"parent": row.serial_and_batch_bundle},
					pluck="incoming_rate",
				)

				for incoming_rate in bundle_data:
					self.assertEqual(incoming_rate, 300.00)
			else:
				bundle_data = frappe.get_all(
					"Serial and Batch Entry",
					filters={"parent": row.serial_and_batch_bundle},
					pluck="incoming_rate",
				)

				for incoming_rate in bundle_data:
					self.assertEqual(incoming_rate, 100.00)

		for row in return_entry.items:
			if row.item_code == batch_item:
				bundle_data = frappe.get_all(
					"Serial and Batch Entry",
					filters={"parent": row.rejected_serial_and_batch_bundle},
					pluck="incoming_rate",
				)

				for incoming_rate in bundle_data:
					self.assertEqual(incoming_rate, 0)
			else:
				bundle_data = frappe.get_all(
					"Serial and Batch Entry",
					filters={"parent": row.rejected_serial_and_batch_bundle},
					pluck="incoming_rate",
				)

				for incoming_rate in bundle_data:
					self.assertEqual(incoming_rate, 0)

	def test_purchase_receipt_return_valuation_with_use_serial_batch_field(self):
		from erpnext.stock.doctype.purchase_receipt.purchase_receipt import make_purchase_return

		batch_item = make_item(
			"_Test Purchase Receipt Return Valuation With Batch Item",
			properties={"has_batch_no": 1, "create_new_batch": 1, "is_stock_item": 1},
		).name

		serial_item = make_item(
			"_Test Purchase Receipt Return Valuation With Serial Item",
			properties={"has_serial_no": 1, "is_stock_item": 1},
		).name

		rej_warehouse = create_warehouse("_Test Purchase Warehouse For Rejected Qty")

		batch_no = "BATCH-RTN-BNU-TPRBI-0001"
		serial_nos = ["SNU-RTN-TPRSI-0001", "SNU-RTN-TPRSI-0002", "SNU-RTN-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,
						"company": "_Test Company",
					}
				).insert()

		pr = make_purchase_receipt(
			item_code=batch_item,
			received_qty=10,
			qty=8,
			rejected_qty=2,
			rejected_warehouse=rej_warehouse,
			batch_no=batch_no,
			use_serial_batch_fields=1,
			rate=300,
			do_not_submit=1,
		)

		pr.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": pr.items[0].warehouse,
				"use_serial_batch_fields": 1,
				"rejected_warehouse": rej_warehouse,
				"serial_no": "\n".join(serial_nos[:2]),
				"rejected_serial_no": serial_nos[2],
			},
		)

		pr.save()
		pr.submit()
		pr.reload()

		batch_no = get_batch_from_bundle(pr.items[0].serial_and_batch_bundle)
		rejected_batch_no = get_batch_from_bundle(pr.items[0].rejected_serial_and_batch_bundle)

		self.assertEqual(batch_no, rejected_batch_no)

		return_entry = make_purchase_return(pr.name)

		return_entry.save()
		return_entry.submit()
		return_entry.reload()

		for row in return_entry.items:
			if row.item_code == batch_item:
				bundle_data = frappe.get_all(
					"Serial and Batch Entry",
					filters={"parent": row.serial_and_batch_bundle},
					pluck="incoming_rate",
				)

				for incoming_rate in bundle_data:
					self.assertEqual(incoming_rate, 300.00)
			else:
				bundle_data = frappe.get_all(
					"Serial and Batch Entry",
					filters={"parent": row.serial_and_batch_bundle},
					pluck="incoming_rate",
				)

				for incoming_rate in bundle_data:
					self.assertEqual(incoming_rate, 100.00)

		for row in return_entry.items:
			if row.item_code == batch_item:
				bundle_data = frappe.get_all(
					"Serial and Batch Entry",
					filters={"parent": row.rejected_serial_and_batch_bundle},
					pluck="incoming_rate",
				)

				for incoming_rate in bundle_data:
					self.assertEqual(incoming_rate, 0)
			else:
				bundle_data = frappe.get_all(
					"Serial and Batch Entry",
					filters={"parent": row.rejected_serial_and_batch_bundle},
					pluck="incoming_rate",
				)

				for incoming_rate in bundle_data:
					self.assertEqual(incoming_rate, 0)

	def test_purchase_return_partial_debit_note(self):
		pr = make_purchase_receipt(
			company="_Test Company with perpetual inventory",
			warehouse="Stores - TCP1",
			supplier_warehouse="Work In Progress - TCP1",
		)

		return_pr = make_purchase_receipt(
			company="_Test Company with perpetual inventory",
			warehouse="Stores - TCP1",
			supplier_warehouse="Work In Progress - TCP1",
			is_return=1,
			return_against=pr.name,
			qty=-2,
			do_not_submit=1,
		)
		return_pr.items[0].purchase_receipt_item = pr.items[0].name
		return_pr.submit()

		# because new_doc isn't considering is_return portion of status_updater
		returned = frappe.get_doc("Purchase Receipt", return_pr.name)
		returned.update_prevdoc_status()
		pr.load_from_db()

		# Check if Original PR updated
		self.assertEqual(pr.items[0].returned_qty, 2)
		self.assertEqual(pr.per_returned, 40)

		# Create first partial debit_note
		pi_1 = make_purchase_invoice(return_pr.name)
		pi_1.items[0].qty = -1
		pi_1.submit()

		# Check if the first partial debit billing percentage got updated
		return_pr.reload()
		self.assertEqual(return_pr.per_billed, 50)
		self.assertEqual(return_pr.status, "Partly Billed")

		# Create second partial debit_note to complete the debit note
		pi_2 = make_purchase_invoice(return_pr.name)
		pi_2.items[0].qty = -1
		pi_2.submit()

		# Check if the second partial debit note billing percentage got updated
		return_pr.reload()
		self.assertEqual(return_pr.per_billed, 100)
		self.assertEqual(return_pr.status, "Completed")

	def test_do_not_allow_to_inward_same_serial_no_multiple_times(self):
		from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry

		frappe.db.set_single_value("Stock Settings", "allow_existing_serial_no", 0)

		item_code = make_item(
			"Test Do Not Allow INWD Item 123", {"has_serial_no": 1, "serial_no_series": "SN-TDAISN-.#####"}
		).name

		pr = make_purchase_receipt(item_code=item_code, qty=1, rate=100, use_serial_batch_fields=1)
		serial_no = get_serial_nos_from_bundle(pr.items[0].serial_and_batch_bundle)[0]

		status = frappe.db.get_value("Serial No", serial_no, "status")
		self.assertTrue(status == "Active")

		make_stock_entry(
			item_code=item_code,
			source=pr.items[0].warehouse,
			qty=1,
			serial_no=serial_no,
			use_serial_batch_fields=1,
		)

		status = frappe.db.get_value("Serial No", serial_no, "status")
		self.assertFalse(status == "Active")

		pr = make_purchase_receipt(
			item_code=item_code, qty=1, rate=100, use_serial_batch_fields=1, do_not_submit=1
		)
		pr.items[0].serial_no = serial_no
		pr.save()

		self.assertRaises(frappe.exceptions.ValidationError, pr.submit)

		frappe.db.set_single_value("Stock Settings", "allow_existing_serial_no", 1)

	def test_seral_no_return_validation(self):
		from erpnext.stock.doctype.purchase_receipt.purchase_receipt import (
			make_purchase_return,
		)

		sn_item_code = make_item(
			"Test Serial No for Validation", {"has_serial_no": 1, "serial_no_series": "SN-TSNFVAL-.#####"}
		).name

		pr1 = make_purchase_receipt(item_code=sn_item_code, qty=5, rate=100, use_serial_batch_fields=1)
		pr1_serial_nos = get_serial_nos_from_bundle(pr1.items[0].serial_and_batch_bundle)

		serial_no_pr = make_purchase_receipt(
			item_code=sn_item_code, qty=5, rate=100, use_serial_batch_fields=1
		)
		serial_no_pr_serial_nos = get_serial_nos_from_bundle(serial_no_pr.items[0].serial_and_batch_bundle)

		sn_return = make_purchase_return(serial_no_pr.name)
		sn_return.items[0].qty = -1
		sn_return.items[0].received_qty = -1
		sn_return.items[0].serial_no = pr1_serial_nos[0]
		sn_return.items[0].use_serial_batch_fields = 1
		sn_return.save()
		self.assertRaises(frappe.ValidationError, sn_return.submit)

		sn_return = make_purchase_return(serial_no_pr.name)
		sn_return.items[0].qty = -1
		sn_return.items[0].received_qty = -1
		sn_return.items[0].serial_no = serial_no_pr_serial_nos[0]
		sn_return.save()
		sn_return.submit()

	def test_batch_no_return_validation(self):
		from erpnext.stock.doctype.purchase_receipt.purchase_receipt import (
			make_purchase_return,
		)

		batch_item_code = make_item(
			"Test Batch No for Validation",
			{"has_batch_no": 1, "batch_number_series": "BT-TSNFVAL-.#####", "create_new_batch": 1},
		).name

		pr1 = make_purchase_receipt(item_code=batch_item_code, qty=5, rate=100, use_serial_batch_fields=1)
		batch_no = get_batch_from_bundle(pr1.items[0].serial_and_batch_bundle)

		batch_no_pr = make_purchase_receipt(
			item_code=batch_item_code, qty=5, rate=100, use_serial_batch_fields=1
		)
		original_batch_no = get_batch_from_bundle(batch_no_pr.items[0].serial_and_batch_bundle)

		batch_return = make_purchase_return(batch_no_pr.name)
		batch_return.items[0].qty = -1
		batch_return.items[0].received_qty = -1
		batch_return.items[0].batch_no = batch_no
		batch_return.items[0].use_serial_batch_fields = 1
		batch_return.save()
		self.assertRaises(frappe.ValidationError, batch_return.submit)

		batch_return = make_purchase_return(batch_no_pr.name)
		batch_return.items[0].qty = -1
		batch_return.items[0].received_qty = -1
		batch_return.items[0].batch_no = original_batch_no
		batch_return.save()
		batch_return.submit()

	def test_pr_status_based_on_invoices_with_update_stock(self):
		from erpnext.buying.doctype.purchase_order.purchase_order import (
			make_purchase_invoice as _make_purchase_invoice,
		)
		from erpnext.buying.doctype.purchase_order.purchase_order import (
			make_purchase_receipt as _make_purchase_receipt,
		)
		from erpnext.buying.doctype.purchase_order.test_purchase_order import (
			create_pr_against_po,
			create_purchase_order,
		)

		item_code = "Test Item for PR Status Based on Invoices"
		create_item(item_code)

		po = create_purchase_order(item_code=item_code, qty=10)
		pi = _make_purchase_invoice(po.name)
		pi.update_stock = 1
		pi.items[0].qty = 5
		pi.submit()

		po.reload()
		self.assertEqual(po.per_billed, 50)

		pr = _make_purchase_receipt(po.name)
		self.assertEqual(pr.items[0].qty, 5)
		pr.submit()
		pr.reload()
		self.assertEqual(pr.status, "To Bill")

	def test_recreate_stock_ledgers(self):
		item_code = "Test Item for Recreate Stock Ledgers"
		create_item(item_code)

		pr = make_purchase_receipt(item_code=item_code, qty=10, rate=100)
		pr.submit()

		sles = frappe.get_all(
			"Stock Ledger Entry",
			filters={"voucher_type": pr.doctype, "voucher_no": pr.name},
			pluck="name",
		)

		self.assertTrue(sles)

		for row in sles:
			doc = frappe.get_doc("Stock Ledger Entry", row)
			doc.delete()

		sles = frappe.get_all(
			"Stock Ledger Entry",
			filters={"voucher_type": pr.doctype, "voucher_no": pr.name},
			pluck="name",
		)

		self.assertFalse(sles)

		frappe.get_doc(
			{
				"doctype": "Repost Item Valuation",
				"based_on": "Transaction",
				"voucher_type": pr.doctype,
				"voucher_no": pr.name,
				"posting_date": pr.posting_date,
				"posting_time": pr.posting_time,
				"company": pr.company,
				"recreate_stock_ledgers": 1,
			}
		).submit()

		sles = frappe.get_all(
			"Stock Ledger Entry",
			filters={"voucher_type": pr.doctype, "voucher_no": pr.name},
			pluck="name",
		)

		self.assertTrue(sles)

	def test_validate_recreate_stock_ledgers_for_sn_item(self):
		item_code = "Test SN Item for Recreate Stock Ledgers"
		make_item(item_code, {"has_serial_no": 1, "serial_no_series": "SN-TRSLR-.#####"})

		pr = make_purchase_receipt(item_code=item_code, qty=10, rate=100)
		pr.submit()

		sles = frappe.get_all(
			"Stock Ledger Entry",
			filters={"voucher_type": pr.doctype, "voucher_no": pr.name},
			pluck="name",
		)

		self.assertTrue(sles)

		repost_doc = frappe.get_doc(
			{
				"doctype": "Repost Item Valuation",
				"based_on": "Transaction",
				"voucher_type": pr.doctype,
				"voucher_no": pr.name,
				"posting_date": pr.posting_date,
				"posting_time": pr.posting_time,
				"company": pr.company,
				"recreate_stock_ledgers": 1,
			}
		)

		self.assertRaises(frappe.ValidationError, repost_doc.save)

	def test_internal_pr_qty_change_only_single_batch(self):
		from erpnext.stock.doctype.delivery_note.delivery_note import make_inter_company_purchase_receipt
		from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note

		prepare_data_for_internal_transfer()

		def get_sabb_qty(sabb):
			return frappe.get_value("Serial and Batch Bundle", sabb, "total_qty")

		item = make_item("Item with only Batch", {"has_batch_no": 1})
		item.create_new_batch = 1
		item.save()

		make_purchase_receipt(
			item_code=item.item_code,
			qty=10,
			rate=100,
			company="_Test Company with perpetual inventory",
			warehouse="Stores - TCP1",
		)

		dn = create_delivery_note(
			item_code=item.item_code,
			qty=10,
			rate=100,
			company="_Test Company with perpetual inventory",
			customer="_Test Internal Customer 2",
			cost_center="Main - TCP1",
			warehouse="Stores - TCP1",
			target_warehouse="Work In Progress - TCP1",
		)
		pr = make_inter_company_purchase_receipt(dn.name)

		pr.items[0].warehouse = "Stores - TCP1"
		pr.items[0].qty = 8
		pr.save()

		# Test 1 - Check if SABB qty is changed on first save
		self.assertEqual(abs(get_sabb_qty(pr.items[0].serial_and_batch_bundle)), 8)

		pr.items[0].qty = 6
		pr.items[0].received_qty = 6
		pr.save()

		# Test 2 - Check if SABB qty is changed when saved again
		self.assertEqual(abs(get_sabb_qty(pr.items[0].serial_and_batch_bundle)), 6)

		pr.items[0].qty = 12
		pr.items[0].received_qty = 12

		# Test 3 - OverAllowanceError should be thrown as qty is greater than qty in DN
		self.assertRaises(erpnext.controllers.status_updater.OverAllowanceError, pr.submit)

	def test_valuation_rate_for_rejected_materials(self):
		item = make_item("Test Item with Rej Material Valuation", {"is_stock_item": 1})
		company = "_Test Company with perpetual inventory"

		warehouse = create_warehouse(
			"_Test In-ward Warehouse",
			company="_Test Company with perpetual inventory",
		)

		rej_warehouse = create_warehouse(
			"_Test Warehouse - Rejected Material",
			company="_Test Company with perpetual inventory",
		)

		frappe.db.set_single_value("Buying Settings", "bill_for_rejected_quantity_in_purchase_invoice", 1)

		frappe.db.set_single_value("Buying Settings", "set_valuation_rate_for_rejected_materials", 1)

		pr = make_purchase_receipt(
			item_code=item.name,
			qty=10,
			rate=100,
			company=company,
			warehouse=warehouse,
			rejected_qty=5,
			rejected_warehouse=rej_warehouse,
		)

		stock_received_but_not_billed_account = frappe.get_value(
			"Company",
			company,
			"stock_received_but_not_billed",
		)

		rejected_item_cost = frappe.db.get_value(
			"Stock Ledger Entry",
			{
				"voucher_type": "Purchase Receipt",
				"voucher_no": pr.name,
				"warehouse": rej_warehouse,
			},
			"stock_value_difference",
		)

		self.assertEqual(rejected_item_cost, 500)

		srbnb_cost = frappe.db.get_value(
			"GL Entry",
			{
				"voucher_type": "Purchase Receipt",
				"voucher_no": pr.name,
				"account": stock_received_but_not_billed_account,
			},
			"credit",
		)

		self.assertEqual(srbnb_cost, 1500)

		frappe.db.set_single_value("Buying Settings", "bill_for_rejected_quantity_in_purchase_invoice", 0)

		frappe.db.set_single_value("Buying Settings", "set_valuation_rate_for_rejected_materials", 0)

	@ERPNextTestSuite.change_settings(
		"Buying Settings",
		{"bill_for_rejected_quantity_in_purchase_invoice": 1, "set_valuation_rate_for_rejected_materials": 1},
	)
	def test_valuation_rate_for_rejected_materials_with_serial_no(self):
		item = make_item(
			"Test Serial Item with Rej Material Valuation",
			{"is_stock_item": 1, "has_serial_no": 1, "serial_no_series": "SNU-TSIRMV-.#####"},
		)
		company = "_Test Company with perpetual inventory"

		warehouse = create_warehouse(
			"_Test In-ward Warehouse",
			company="_Test Company with perpetual inventory",
		)

		rej_warehouse = create_warehouse(
			"_Test Warehouse - Rejected Material",
			company="_Test Company with perpetual inventory",
		)

		pr = make_purchase_receipt(
			item_code=item.name,
			qty=10,
			rate=100,
			company=company,
			warehouse=warehouse,
			rejected_qty=5,
			rejected_warehouse=rej_warehouse,
		)

		stock_received_but_not_billed_account = frappe.get_value(
			"Company",
			company,
			"stock_received_but_not_billed",
		)

		rejected_item_cost = frappe.db.get_value(
			"Stock Ledger Entry",
			{
				"voucher_type": "Purchase Receipt",
				"voucher_no": pr.name,
				"warehouse": rej_warehouse,
			},
			"stock_value_difference",
		)

		self.assertEqual(rejected_item_cost, 500)

		srbnb_cost = frappe.db.get_value(
			"GL Entry",
			{
				"voucher_type": "Purchase Receipt",
				"voucher_no": pr.name,
				"account": stock_received_but_not_billed_account,
			},
			"credit",
		)

		self.assertEqual(srbnb_cost, 1500)

	def test_valuation_rate_for_rejected_materials_withoout_accepted_materials(self):
		item = make_item("Test Item with Rej Material Valuation WO Accepted", {"is_stock_item": 1})
		company = "_Test Company with perpetual inventory"

		warehouse = create_warehouse(
			"_Test In-ward Warehouse",
			company="_Test Company with perpetual inventory",
		)

		rej_warehouse = create_warehouse(
			"_Test Warehouse - Rejected Material",
			company="_Test Company with perpetual inventory",
		)

		frappe.db.set_single_value("Buying Settings", "bill_for_rejected_quantity_in_purchase_invoice", 1)

		frappe.db.set_single_value("Buying Settings", "set_valuation_rate_for_rejected_materials", 1)

		pr = make_purchase_receipt(
			item_code=item.name,
			qty=0,
			rate=100,
			company=company,
			warehouse=warehouse,
			rejected_qty=5,
			rejected_warehouse=rej_warehouse,
		)

		gl_entry = frappe.get_all(
			"GL Entry", filters={"debit": (">", 0), "voucher_no": pr.name}, pluck="name"
		)

		stock_value_diff = frappe.db.get_value(
			"Stock Ledger Entry",
			{"warehouse": rej_warehouse, "voucher_no": pr.name},
			"stock_value_difference",
		)

		self.assertTrue(gl_entry)
		self.assertEqual(stock_value_diff, 500.00)

	def test_no_valuation_rate_for_rejected_materials(self):
		item = make_item("Test Item with Rej Material No Valuation", {"is_stock_item": 1})
		company = "_Test Company with perpetual inventory"

		warehouse = create_warehouse(
			"_Test In-ward Warehouse",
			company="_Test Company with perpetual inventory",
		)

		rej_warehouse = create_warehouse(
			"_Test Warehouse - Rejected Material",
			company="_Test Company with perpetual inventory",
		)

		frappe.db.set_single_value("Buying Settings", "bill_for_rejected_quantity_in_purchase_invoice", 0)

		frappe.db.set_single_value("Buying Settings", "set_valuation_rate_for_rejected_materials", 0)

		pr = make_purchase_receipt(
			item_code=item.name,
			qty=10,
			rate=100,
			company=company,
			warehouse=warehouse,
			rejected_qty=5,
			rejected_warehouse=rej_warehouse,
		)

		stock_received_but_not_billed_account = frappe.get_value(
			"Company",
			company,
			"stock_received_but_not_billed",
		)

		rejected_item_cost = frappe.db.get_value(
			"Stock Ledger Entry",
			{
				"voucher_type": "Purchase Receipt",
				"voucher_no": pr.name,
				"warehouse": rej_warehouse,
			},
			"stock_value_difference",
		)

		self.assertEqual(rejected_item_cost, 0.0)

		srbnb_cost = frappe.db.get_value(
			"GL Entry",
			{
				"voucher_type": "Purchase Receipt",
				"voucher_no": pr.name,
				"account": stock_received_but_not_billed_account,
			},
			"credit",
		)

		self.assertEqual(srbnb_cost, 1000)

	def test_purchase_expense_account(self):
		# Single, so it outlives this test - every later Purchase Receipt / Invoice would otherwise
		# be forced to resolve the expense account pair and throw for unconfigured companies.
		previous = frappe.db.get_single_value("Accounts Settings", "book_stock_expense_gl_entries")
		self.addCleanup(
			frappe.db.set_single_value, "Accounts Settings", "book_stock_expense_gl_entries", previous
		)
		frappe.db.set_single_value("Accounts Settings", "book_stock_expense_gl_entries", 1)

		item = "Test Item with Purchase Expense Account"
		make_item(item, {"is_stock_item": 1})
		company = "_Test Company with perpetual inventory"

		expense_account = "_Test Account Purchase Expense - TCP1"
		expense_contra_account = "_Test Account Purchase Contra Expense - TCP1"
		if not frappe.db.exists("Account", expense_account):
			frappe.get_doc(
				{
					"doctype": "Account",
					"account_name": "_Test Account Purchase Expense",
					"parent_account": "Stock Expenses - TCP1",
					"company": company,
					"is_group": 0,
					"root_type": "Expense",
				}
			).insert()

		if not frappe.db.exists("Account", expense_contra_account):
			frappe.get_doc(
				{
					"doctype": "Account",
					"account_name": "_Test Account Purchase Contra Expense",
					"parent_account": "Stock Expenses - TCP1",
					"company": company,
					"is_group": 0,
					"root_type": "Expense",
				}
			).insert()

		item_doc = frappe.get_doc("Item", item)
		item_doc.append(
			"item_defaults",
			{
				"company": company,
				"default_warehouse": "Stores - TCP1",
				"purchase_expense_account": expense_account,
				"purchase_expense_contra_account": expense_contra_account,
			},
		)

		item_doc.save()

		pr = make_purchase_receipt(
			item_code=item,
			qty=10,
			rate=100,
			company=company,
			warehouse="Stores - TCP1",
		)

		gl_entries = get_gl_entries(pr.doctype, pr.name)
		accounts = [d.account for d in gl_entries]
		self.assertTrue(expense_account in accounts)
		self.assertTrue(expense_contra_account in accounts)

		for row in gl_entries:
			if row.account == expense_account:
				self.assertEqual(row.debit, 1000)
			if row.account == expense_contra_account:
				self.assertEqual(row.credit, 1000)

	def test_repost_gl_entries(self):
		from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry

		item = "Test Item for Repost GL Entries"
		make_item(item, {"is_stock_item": 1})
		company = "_Test Company with perpetual inventory"

		account = "Reposting Adjustment - TCP1"
		if not frappe.db.exists("Account", account):
			frappe.get_doc(
				{
					"doctype": "Account",
					"account_name": "Reposting Adjustment",
					"parent_account": "Stock Expenses - TCP1",
					"company": company,
					"is_group": 0,
					"account_type": "Expense Account",
				}
			).insert()

		se = make_stock_entry(
			item_code=item,
			qty=10,
			rate=100,
			company=company,
			target="Stores - TCP1",
		)

		gl_entries = get_gl_entries(se.doctype, se.name)
		for row in gl_entries:
			self.assertTrue(row.account in ["Stock In Hand - TCP1", "Stock Adjustment - TCP1"])

		se.items[0].db_set("expense_account", account)
		se.reload()

		repost_doc = frappe.get_doc(
			{
				"doctype": "Repost Item Valuation",
				"based_on": "Transaction",
				"voucher_type": se.doctype,
				"voucher_no": se.name,
				"posting_date": se.posting_date,
				"posting_time": se.posting_time,
				"company": se.company,
				"repost_only_accounting_ledgers": 1,
			}
		)

		repost_doc.submit()

		gl_entries = get_gl_entries(se.doctype, se.name)
		for row in gl_entries:
			self.assertTrue(row.account in ["Stock In Hand - TCP1", account])

	def test_lcv_for_repack_entry(self):
		from erpnext.stock.doctype.landed_cost_voucher.test_landed_cost_voucher import (
			create_landed_cost_voucher,
		)
		from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry

		for item in [
			"Potatoes Raw Material Item",
			"Fries Finished Goods Item",
		]:
			create_item(item)

		pr = make_purchase_receipt(
			item_code="Potatoes Raw Material Item",
			warehouse="_Test Warehouse - _TC",
			qty=100,
			rate=50,
		)

		wh1 = create_warehouse("WH A1", company=pr.company)
		wh2 = create_warehouse("WH A2", company=pr.company)

		ste = make_stock_entry(
			purpose="Repack",
			source="_Test Warehouse - _TC",
			item_code="Potatoes Raw Material Item",
			qty=100,
			company=pr.company,
			do_not_save=1,
		)

		ste.append(
			"items",
			{
				"item_code": "Fries Finished Goods Item",
				"qty": 50,
				"t_warehouse": wh1,
			},
		)

		ste.append(
			"items",
			{
				"item_code": "Fries Finished Goods Item",
				"qty": 50,
				"t_warehouse": wh2,
			},
		)

		ste.insert()
		ste.submit()
		ste.reload()

		for row in ste.items:
			if row.t_warehouse:
				self.assertEqual(row.valuation_rate, 50)

		sles = frappe.get_all(
			"Stock Ledger Entry",
			filters={"voucher_type": ste.doctype, "voucher_no": ste.name, "actual_qty": (">", 0)},
			pluck="stock_value_difference",
		)

		self.assertEqual(sles, [2500.0, 2500.0])

		create_landed_cost_voucher("Purchase Receipt", pr.name, pr.company, charges=2000 * -1)

		ste.reload()

		for row in ste.items:
			if row.t_warehouse:
				self.assertEqual(row.valuation_rate, 30)

		sles = frappe.get_all(
			"Stock Ledger Entry",
			filters={"voucher_type": ste.doctype, "voucher_no": ste.name, "actual_qty": (">", 0)},
			pluck="stock_value_difference",
		)

		self.assertEqual(sles, [1500.0, 1500.0])

	@ERPNextTestSuite.change_settings("Stock Settings", {"allow_negative_stock": 0})
	def test_multiple_transactions_with_same_posting_datetime(self):
		from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note
		from erpnext.stock.stock_ledger import NegativeStockError

		item_code = make_item(
			"Test Item for Multiple Txn with Same Posting Datetime", {"is_stock_item": 1}
		).name

		pr = make_purchase_receipt(
			item_code=item_code,
			qty=100,
			rate=100,
			posting_date=today(),
			posting_time="10:00:00",
		)

		create_delivery_note(
			item_code=item_code,
			qty=100,
			rate=100,
			posting_date=today(),
			posting_time="10:00:00",
		)

		make_purchase_receipt(
			item_code=item_code,
			qty=150,
			rate=100,
			posting_date=today(),
			posting_time="10:00:00",
		)

		self.assertRaises(NegativeStockError, pr.cancel)

	@ERPNextTestSuite.change_settings(
		"Buying Settings",
		{
			"set_landed_cost_based_on_purchase_invoice_rate": 1,
			"maintain_same_rate": 0,
			"allow_multiple_items": 1,
		},
	)
	@ERPNextTestSuite.change_settings("Accounts Settings", {"over_billing_allowance": 100})
	def test_set_lcv_from_pi_created_against_po(self):
		from erpnext.buying.doctype.purchase_order.purchase_order import (
			make_purchase_invoice as make_pi_against_po,
		)
		from erpnext.buying.doctype.purchase_order.purchase_order import (
			make_purchase_receipt as make_pr_against_po,
		)
		from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order

		item_code = create_item("Test Item for LCV from PI against PO").name

		po = create_purchase_order(item_code=item_code, qty=10, rate=400)
		pr = make_pr_against_po(po.name)
		pr.items[0].qty = 5
		item = frappe.copy_doc(pr.items[0])
		item.qty = 2
		pr.append("items", item)

		item = frappe.copy_doc(pr.items[0])
		item.qty = 3
		pr.append("items", item)
		pr.submit()

		pi = make_pi_against_po(po.name)
		pi.items[0].rate = 500
		pi.submit()

		pr.reload()
		for row in pr.items:
			self.assertTrue(row.amount_difference_with_purchase_invoice)
			amt_diff = 5000 * (row.qty / 10) - row.amount
			self.assertEqual(row.amount_difference_with_purchase_invoice, amt_diff)

	def test_purchase_return_with_and_without_return_against_rejected_qty(self):
		from erpnext.stock.doctype.purchase_receipt.purchase_receipt import (
			make_purchase_return as _make_purchase_return,
		)
		from erpnext.stock.doctype.purchase_receipt.purchase_receipt import (
			make_purchase_return_against_rejected_warehouse,
		)

		item_code = create_item("Test Item for PR against Rejected Qty").name
		warehouse = "_Test Warehouse - _TC"

		company = frappe.db.get_value("Warehouse", warehouse, "company")
		rejected_wh = create_warehouse("_Test Rejected Warehouse", company=company)

		pr = make_purchase_receipt(
			item_code=item_code,
			qty=10,
			rejected_qty=5,
			rate=100,
			warehouse=warehouse,
			rejected_warehouse=rejected_wh,
		)

		# Purchase Return against rejected qty partially
		return_entry = make_purchase_return_against_rejected_warehouse(pr.name)
		return_entry.items[0].qty = -2
		return_entry.items[0].received_qty = -2
		return_entry.save()
		return_entry.submit()
		pr.reload()

		# Purchase Return against rejected qty partially
		return_entry = _make_purchase_return(pr.name)

		self.assertEqual(return_entry.items[0].qty, -10)
		self.assertEqual(return_entry.items[0].rejected_qty, -3)  # 5-2=3

		return_entry.items[0].qty = -8
		return_entry.items[0].stock_qty = -8
		return_entry.items[0].received_qty = -11

		return_entry.save()
		return_entry.submit()

		pr.reload()

		# Purchase Return against rejected qty partially
		return_entry = _make_purchase_return(pr.name)

		self.assertEqual(return_entry.items[0].qty, -2)
		self.assertEqual(return_entry.items[0].rejected_qty, 0)  # 3-3=0

	def test_do_not_use_batchwise_valuation_with_fifo(self):
		from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry

		item_code = make_item(
			"Test Item Do Not Use Batchwise Valuation with FIFO",
			{
				"is_stock_item": 1,
				"has_batch_no": 1,
				"batch_number_series": "BN-TESTDNUBVWF-.#####",
				"valuation_method": "FIFO",
			},
		).name

		doc = frappe.new_doc("Batch")
		doc.update(
			{
				"batch_id": "BN-TESTDNUBVWF-00001",
				"item": item_code,
			}
		).insert()

		doc.db_set("use_batchwise_valuation", 0)
		doc.reload()

		self.assertTrue(doc.use_batchwise_valuation == 0)

		doc = frappe.new_doc("Batch")
		doc.update(
			{
				"batch_id": "BN-TESTDNUBVWF-00002",
				"item": item_code,
			}
		).insert()

		self.assertTrue(doc.use_batchwise_valuation == 1)

		warehouse = "_Test Warehouse - _TC"
		make_stock_entry(
			item_code=item_code,
			qty=10,
			rate=100,
			target=warehouse,
			batch_no="BN-TESTDNUBVWF-00001",
			use_serial_batch_fields=1,
		)

		se1 = make_stock_entry(
			item_code=item_code,
			qty=10,
			rate=200,
			target=warehouse,
			batch_no="BN-TESTDNUBVWF-00001",
			use_serial_batch_fields=1,
		)

		stock_queue = frappe.db.get_value(
			"Stock Ledger Entry",
			{
				"item_code": item_code,
				"warehouse": warehouse,
				"is_cancelled": 0,
				"voucher_type": "Stock Entry",
				"voucher_no": se1.name,
			},
			"stock_queue",
		)

		stock_queue = frappe.parse_json(stock_queue)

		self.assertEqual(stock_queue, [[10, 100.0], [10, 200.0]])

		se2 = make_stock_entry(
			item_code=item_code,
			qty=10,
			rate=2,
			target=warehouse,
			batch_no="BN-TESTDNUBVWF-00002",
			use_serial_batch_fields=1,
		)

		stock_queue = frappe.db.get_value(
			"Stock Ledger Entry",
			{
				"item_code": item_code,
				"warehouse": warehouse,
				"is_cancelled": 0,
				"voucher_type": "Stock Entry",
				"voucher_no": se2.name,
			},
			"stock_queue",
		)

		stock_queue = frappe.parse_json(stock_queue)
		self.assertEqual(stock_queue, [[10, 100.0], [10, 200.0]])

		se3 = make_stock_entry(
			item_code=item_code,
			qty=20,
			source=warehouse,
			batch_no="BN-TESTDNUBVWF-00001",
			use_serial_batch_fields=1,
		)

		ste_details = frappe.db.get_value(
			"Stock Ledger Entry",
			{
				"item_code": item_code,
				"warehouse": warehouse,
				"is_cancelled": 0,
				"voucher_type": "Stock Entry",
				"voucher_no": se3.name,
			},
			["stock_queue", "stock_value_difference"],
			as_dict=1,
		)

		stock_queue = frappe.parse_json(ste_details.stock_queue)
		self.assertEqual(stock_queue, [])
		self.assertEqual(ste_details.stock_value_difference, 3000 * -1)

		se4 = make_stock_entry(
			item_code=item_code,
			qty=20,
			rate=0,
			target=warehouse,
			batch_no="BN-TESTDNUBVWF-00001",
			use_serial_batch_fields=1,
			do_not_submit=1,
		)

		se4.items[0].basic_rate = 0.0
		se4.items[0].allow_zero_valuation_rate = 1
		se4.submit()

		stock_queue = frappe.db.get_value(
			"Stock Ledger Entry",
			{
				"item_code": item_code,
				"warehouse": warehouse,
				"is_cancelled": 0,
				"voucher_type": "Stock Entry",
				"voucher_no": se4.name,
			},
			"stock_queue",
		)

		self.assertEqual(frappe.parse_json(stock_queue), [[20, 0.0]])

	def test_purchase_return_valuation_for_batchwise_valuation_batch(self):
		from erpnext.controllers.sales_and_purchase_return import make_return_doc
		from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note

		item_code = make_item(
			"Test Purchase Return Batchwise Valn Item",
			{
				"is_stock_item": 1,
				"has_batch_no": 1,
				"batch_number_series": "BN-TPRBWV-.#####",
			},
		).name

		batch_no = "BN-TPRBWV-00001"
		batch = frappe.new_doc("Batch").update({"batch_id": batch_no, "item": item_code}).insert()
		self.assertEqual(batch.use_batchwise_valuation, 1)

		warehouse = "_Test Warehouse - _TC"
		pr = make_purchase_receipt(
			item_code=item_code,
			qty=100,
			rate=1000,
			warehouse=warehouse,
			batch_no=batch_no,
			use_serial_batch_fields=1,
		)
		make_purchase_receipt(
			item_code=item_code,
			qty=100,
			rate=400,
			warehouse=warehouse,
			batch_no=batch_no,
			use_serial_batch_fields=1,
		)
		create_delivery_note(
			item_code=item_code,
			qty=100,
			warehouse=warehouse,
			batch_no=batch_no,
			use_serial_batch_fields=1,
		)

		return_pr = make_return_doc("Purchase Receipt", pr.name)
		return_pr.submit()

		sle = frappe.db.get_value(
			"Stock Ledger Entry",
			{"voucher_no": return_pr.name, "is_cancelled": 0},
			["stock_value_difference", "qty_after_transaction", "stock_value", "serial_and_batch_bundle"],
			as_dict=True,
		)
		self.assertEqual(flt(sle.qty_after_transaction), 0.0)
		self.assertEqual(flt(sle.stock_value_difference, 2), -70000.0)
		self.assertEqual(flt(sle.stock_value, 2), 0.0)

		rate = frappe.db.get_value(
			"Serial and Batch Entry", {"parent": sle.serial_and_batch_bundle}, "incoming_rate"
		)
		self.assertEqual(flt(rate, 2), 700.0)

	def test_negative_stock_error_for_purchase_return(self):
		from erpnext.controllers.sales_and_purchase_return import make_return_doc
		from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry

		item_code = make_item(
			"Test Negative Stock for Purchase Return Item",
			{"has_batch_no": 1, "create_new_batch": 1, "batch_number_series": "TNSFPRI.#####"},
		).name

		pr = make_purchase_receipt(
			item_code=item_code,
			posting_date=add_days(today(), -3),
			qty=10,
			rate=100,
			warehouse="_Test Warehouse - _TC",
		)

		batch_no = get_batch_from_bundle(pr.items[0].serial_and_batch_bundle)

		make_purchase_receipt(
			item_code=item_code,
			posting_date=add_days(today(), -4),
			qty=10,
			rate=100,
			warehouse="_Test Warehouse - _TC",
		)

		make_stock_entry(
			item_code=item_code,
			qty=10,
			source="_Test Warehouse - _TC",
			target="_Test Warehouse 1 - _TC",
			batch_no=batch_no,
			use_serial_batch_fields=1,
		)

		return_pr = make_return_doc("Purchase Receipt", pr.name)
		self.assertRaises(frappe.ValidationError, return_pr.submit)

	def test_internal_purchase_receipt_incoming_rate_with_lcv(self):
		"""
		To test inter branch transaction incoming rate calculation with lcv after item reposting
		"""
		from erpnext.stock.doctype.delivery_note.delivery_note import make_inter_company_purchase_receipt
		from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note

		prepare_data_for_internal_transfer()
		customer = "_Test Internal Customer 2"
		company = "_Test Company with perpetual inventory"
		item_doc = create_item("_Test Internal PR LCV Item")
		lcv_expense_account = "Expenses Included In Valuation - TCP1"

		from_warehouse = create_warehouse("_Test Internal From Warehouse LCV", company=company)
		to_warehouse = create_warehouse("_Test Internal To Warehouse LCV", company=company)

		# inward qty for internal transactions
		make_purchase_receipt(
			item_code=item_doc.item_code,
			qty=5,
			rate=100,
			company="_Test Company with perpetual inventory",
			warehouse=from_warehouse,
		)

		idn = create_delivery_note(
			item_code=item_doc.name,
			company=company,
			customer=customer,
			cost_center="Main - TCP1",
			expense_account="Cost of Goods Sold - TCP1",
			qty=5,
			rate=100,
			warehouse=from_warehouse,
			target_warehouse=to_warehouse,
		)
		self.assertEqual(idn.items[0].rate, 100)

		ipr = make_inter_company_purchase_receipt(idn.name)
		ipr.items[0].warehouse = from_warehouse
		self.assertEqual(ipr.items[0].rate, 100)
		ipr.submit()

		self.create_lcv(ipr.doctype, ipr.name, company, lcv_expense_account, charges=100)
		ipr.reload()

		self.assertEqual(ipr.items[0].landed_cost_voucher_amount, 100)
		self.assertEqual(ipr.items[0].valuation_rate, 120)

		# repost the receipt and check the stock ledger values
		repost_doc = frappe.new_doc("Repost Item Valuation")
		repost_doc.update(
			{
				"based_on": "Transaction",
				"voucher_type": ipr.doctype,
				"voucher_no": ipr.name,
				"posting_date": ipr.posting_date,
				"posting_time": ipr.posting_time,
				"company": ipr.company,
				"allow_negative_stock": 1,
				"via_landed_cost_voucher": 0,
				"allow_zero_rate": 0,
			}
		)
		repost_doc.save()
		repost_doc.submit()

		stk_ledger = frappe.db.get_value(
			"Stock Ledger Entry",
			{"voucher_type": "Purchase Receipt", "voucher_no": ipr.name, "warehouse": from_warehouse},
			["incoming_rate", "stock_value_difference"],
			as_dict=True,
		)

		# check the incoming rate and stock value change
		self.assertEqual(stk_ledger.incoming_rate, 120)
		self.assertEqual(stk_ledger.stock_value_difference, 600)

	def test_negative_stock_error_for_purchase_return_when_stock_exists_in_future_date(self):
		from erpnext.controllers.sales_and_purchase_return import make_return_doc
		from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
		from erpnext.stock.stock_ledger import NegativeStockError

		item_code = make_item(
			"Test Negative Stock for Purchase Return with Future Stock Item",
			{
				"is_stock_item": 1,
				"has_batch_no": 1,
				"create_new_batch": 1,
				"batch_number_series": "TNSPFPRI.#####",
			},
		).name

		make_purchase_receipt(
			item_code=item_code,
			posting_date=add_days(today(), -4),
			qty=100,
			rate=100,
			warehouse="_Test Warehouse - _TC",
		)

		pr1 = make_purchase_receipt(
			item_code=item_code,
			posting_date=add_days(today(), -3),
			qty=100,
			rate=100,
			warehouse="_Test Warehouse - _TC",
		)

		batch1 = get_batch_from_bundle(pr1.items[0].serial_and_batch_bundle)

		pr2 = make_purchase_receipt(
			item_code=item_code,
			posting_date=add_days(today(), -2),
			qty=100,
			rate=100,
			warehouse="_Test Warehouse - _TC",
		)

		batch2 = get_batch_from_bundle(pr2.items[0].serial_and_batch_bundle)

		make_stock_entry(
			item_code=item_code,
			qty=100,
			posting_date=add_days(today(), -1),
			source="_Test Warehouse - _TC",
			target="_Test Warehouse 1 - _TC",
			batch_no=batch1,
			use_serial_batch_fields=1,
		)

		make_stock_entry(
			item_code=item_code,
			qty=100,
			posting_date=add_days(today(), -1),
			source="_Test Warehouse - _TC",
			target="_Test Warehouse 1 - _TC",
			batch_no=batch2,
			use_serial_batch_fields=1,
		)

		make_stock_entry(
			item_code=item_code,
			qty=100,
			posting_date=today(),
			source="_Test Warehouse 1 - _TC",
			target="_Test Warehouse - _TC",
			batch_no=batch1,
			use_serial_batch_fields=1,
		)

		make_purchase_entry = make_return_doc("Purchase Receipt", pr1.name)
		make_purchase_entry.set_posting_time = 1
		make_purchase_entry.posting_date = pr1.posting_date
		self.assertRaises(NegativeStockError, make_purchase_entry.submit)

	def test_purchase_return_from_different_warehouse(self):
		from erpnext.controllers.sales_and_purchase_return import make_return_doc
		from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry

		item_code = make_item(
			"Test Purchase Return From Different Warehouse Item",
			{
				"is_stock_item": 1,
				"has_batch_no": 1,
				"create_new_batch": 1,
				"batch_number_series": "TPRFDWU.#####",
			},
		).name

		pr1 = make_purchase_receipt(
			item_code=item_code,
			posting_date=add_days(today(), -4),
			qty=100,
			rate=100,
			warehouse="_Test Warehouse - _TC",
		)

		batch1 = get_batch_from_bundle(pr1.items[0].serial_and_batch_bundle)

		make_stock_entry(
			item_code=item_code,
			qty=100,
			posting_date=add_days(today(), -1),
			source="_Test Warehouse - _TC",
			target="_Test Warehouse 1 - _TC",
			batch_no=batch1,
			use_serial_batch_fields=1,
		)

		make_purchase_entry = make_return_doc("Purchase Receipt", pr1.name)
		make_purchase_entry.items[0].warehouse = "_Test Warehouse 1 - _TC"
		make_purchase_entry.submit()
		make_purchase_entry.reload()

		sabb = frappe.get_doc("Serial and Batch Bundle", make_purchase_entry.items[0].serial_and_batch_bundle)
		for row in sabb.entries:
			self.assertEqual(row.warehouse, "_Test Warehouse 1 - _TC")
			self.assertEqual(row.incoming_rate, 100)

	def test_different_exchange_rate_in_pr_and_pi(self):
		from erpnext.accounts.doctype.account.test_account import create_account

		original_value = frappe.db.get_single_value(
			"Buying Settings", "set_landed_cost_based_on_purchase_invoice_rate"
		)

		frappe.db.set_single_value("Buying Settings", "set_landed_cost_based_on_purchase_invoice_rate", 1)

		party_account = create_account(
			account_name="USD Party Account Creditors",
			parent_account="Accounts Payable - TCP1",
			account_type="Payable",
			company="_Test Company with perpetual inventory",
			account_currency="USD",
		)

		supplier = create_supplier(
			supplier_name="_Test USD Supplier New 1", default_currency="USD", party_account=party_account
		).name
		item_code = make_item("Test Item for Different Exchange Rate", {"is_stock_item": 1}).name

		pr = make_purchase_receipt(
			item_code=item_code,
			qty=1,
			currency="USD",
			conversion_rate=80,
			rate=100,
			company="_Test Company with perpetual inventory",
			warehouse=frappe.get_value(
				"Warehouse", {"company": "_Test Company with perpetual inventory"}, "name"
			),
			supplier=supplier,
		)

		self.assertEqual(pr.currency, "USD")
		self.assertEqual(pr.conversion_rate, 80)

		gl_entries = get_gl_entries(pr.doctype, pr.name)
		self.assertTrue(len(gl_entries) == 2)
		for row in gl_entries:
			amount = row.credit or row.debit
			self.assertEqual(amount, 8000.0)

		pi = make_purchase_invoice(pr.name)
		pi.conversion_rate = 90
		pi.currency = "USD"

		pi.save()
		pi.submit()

		gl_entries = get_gl_entries(pi.doctype, pi.name)
		self.assertTrue(len(gl_entries) == 2)

		accounts = ["USD Party Account Creditors - TCP1", "Stock Received But Not Billed - TCP1"]
		for row in gl_entries:
			amount = row.credit or row.debit
			self.assertEqual(amount, 9000.0)
			self.assertTrue(row.account in accounts)

		frappe.db.set_single_value(
			"Buying Settings", "set_landed_cost_based_on_purchase_invoice_rate", original_value
		)

	def test_purchase_receipt_gl_entries_for_asset_item(self):
		from erpnext.assets.doctype.asset.test_asset import create_fixed_asset_item

		# Create a Company without Stock Accounts Linked.
		company = frappe.get_doc(
			{
				"doctype": "Company",
				"company_name": "Asset Company",
				"country": "India",
				"default_currency": "INR",
			}
		).insert()

		stock_accounts = (
			company.default_inventory_account,
			company.stock_adjustment_account,
			company.stock_received_but_not_billed,
		)

		company.update(
			{"stock_in_hand_account": "", "stock_adjustment_account": "", "stock_received_but_not_billed": ""}
		).save()

		for account in stock_accounts:
			frappe.db.delete("Account", account)

		asset_category = create_asset_category_for_pr_test()
		asset_item = create_fixed_asset_item(
			item_code="Test Fixed Asset Item for PR GL Test", asset_category=asset_category.name
		)
		arnb_account = frappe.db.get_value("Company", company.name, "asset_received_but_not_billed")

		# Purchase Receipt should be able to create even without any stock accounts linked to company
		pr = make_purchase_receipt(
			item_code=asset_item.name, warehouse="Stores - AC", qty=1, rate=10000, company=company.name
		)

		gl_entries = get_gl_entries("Purchase Receipt", pr.name)

		self.assertTrue(gl_entries)
		gl_accounts = [d.account for d in gl_entries]

		# The fixed asset account set on the item row must be debited
		asset_expense_account = pr.items[0].expense_account
		self.assertIn(asset_expense_account, gl_accounts)

		# Asset Received But Not Billed must be credited
		self.assertIn(arnb_account, gl_accounts)

		# No Stock-type account should appear — the inventory account map is not
		# needed and must not be consulted for an asset-only receipt
		for entry in gl_entries:
			account_type = frappe.db.get_value("Account", entry.account, "account_type")
			self.assertNotEqual(account_type, "Stock")

		pr.cancel()

	def test_purchase_receipt_gl_entries_with_mixed_asset_and_stock_items(self):
		from erpnext.assets.doctype.asset.test_asset import create_fixed_asset_item

		company = frappe.get_doc(
			{
				"doctype": "Company",
				"company_name": "Asset Company",
				"country": "India",
				"default_currency": "INR",
			}
		).insert()

		asset_category = create_asset_category_for_pr_test()
		asset_item = create_fixed_asset_item(
			item_code="Test Fixed Asset Item for PR GL Test", asset_category=asset_category.name
		)
		arnb_account = frappe.db.get_value("Company", company.name, "asset_received_but_not_billed")

		pr = make_purchase_receipt(
			item_code=asset_item.name,
			qty=1,
			rate=10000,
			warehouse="Stores - AC",
			do_not_save=True,
			company=company.name,
		)
		pr.append(
			"items",
			{
				"item_code": "_Test Item",
				"warehouse": "Stores - AC",
				"qty": 5,
				"received_qty": 5,
				"rejected_qty": 0,
				"rate": 50,
				"uom": "_Test UOM",
				"stock_uom": "_Test UOM",
				"conversion_factor": 1.0,
				"cost_center": frappe.get_cached_value("Company", pr.company, "cost_center"),
			},
		)
		pr.insert()
		pr.submit()

		gl_entries = get_gl_entries("Purchase Receipt", pr.name)
		self.assertTrue(gl_entries)

		gl_accounts = [d.account for d in gl_entries]
		self.assertIn(arnb_account, gl_accounts)

		# The fixed asset account set on the item row must be debited
		asset_expense_account = pr.items[0].expense_account
		self.assertIn(asset_expense_account, gl_accounts)

		# Asset Received But Not Billed must be credited
		self.assertIn(asset_category.accounts[0].fixed_asset_account, gl_accounts)

		# Stock Accounts should be used for Stock Items
		self.assertIn(company.stock_received_but_not_billed, gl_accounts)
		self.assertIn(company.default_inventory_account, gl_accounts)
		pr.cancel()

	@ERPNextTestSuite.change_settings(
		"Buying Settings", {"set_landed_cost_based_on_purchase_invoice_rate": 1, "maintain_same_rate": 0}
	)
	def test_srbnb_with_inclusive_tax_and_rate_change_in_pi(self):
		"""
		When 'Set Landed Cost Based on PI Rate' is enabled and PI has an inclusive tax:
		  - PR: qty=2, rate=1000 INR → base_net_amount=2000
		  - PI: rate changed to 2000, 5% tax included in basic rate
		      → PI base_net_amount = 2 * 2000 / 1.05 ≈ 3809.52

		The system must use PI's base_net_amount (not amount=4000) so that
		SRBNB credit on PR = 3809.52, not 4000.
		"""
		company = "_Test Company with perpetual inventory"
		warehouse = "Stores - TCP1"
		cost_center = "Main - TCP1"

		item_code = make_item(
			"Test Item for SRBNB Inclusive Tax Rate Change",
			{"is_stock_item": 1},
		).name

		pr = make_purchase_receipt(
			item_code=item_code,
			qty=2,
			rate=1000,
			company=company,
			warehouse=warehouse,
			cost_center=cost_center,
		)

		pi = make_purchase_invoice(pr.name)
		pi.items[0].rate = 2000
		pi.append(
			"taxes",
			{
				"charge_type": "On Net Total",
				"account_head": "_Test Account VAT - TCP1",
				"category": "Total",
				"add_deduct_tax": "Add",
				"included_in_print_rate": 1,
				"rate": 5,
				"description": "Test Inclusive Tax",
				"cost_center": cost_center,
			},
		)
		pi.save()
		pi.submit()

		pr.reload()

		# PI base_net_amount = qty * (rate / (1 + tax_rate/100)) = 2 * (2000 / 1.05)
		pi_base_net_amount = flt(2 * 2000 / 1.05, 2)
		pr_base_net_amount = flt(pr.items[0].amount, 2)  # 2 * 1000 = 2000
		expected_diff = flt(pi_base_net_amount - pr_base_net_amount, 2)

		self.assertAlmostEqual(pr.items[0].amount_difference_with_purchase_invoice, expected_diff, places=2)

		# Total SRBNB credit = PR base_net_amount + amount_difference = PI base_net_amount
		srbnb_account = "Stock Received But Not Billed - TCP1"
		gl_entries = get_gl_entries("Purchase Receipt", pr.name, skip_cancelled=True)
		srbnb_credit = sum(flt(row.credit) for row in gl_entries if row.account == srbnb_account)
		self.assertAlmostEqual(srbnb_credit, pi_base_net_amount, places=2)

	@ERPNextTestSuite.change_settings(
		"Buying Settings", {"set_landed_cost_based_on_purchase_invoice_rate": 1, "maintain_same_rate": 0}
	)
	def test_srbnb_with_inclusive_tax_and_exchange_rate_change_in_pi(self):
		"""
		When 'Set Landed Cost Based on PI Rate' is enabled, PI has an inclusive tax, and only
		the exchange rate changes on the PI (rate stays the same):
		  - PR: qty=2, rate=100 USD, conversion_rate=70 → base_net_amount=14000 INR
		  - PI: same rate=100 USD, conversion_rate changed to 90, 5% tax included in basic rate
		      → PI base_net_amount = 2 * (100 / 1.05) * 90 ≈ 17142.86 INR

		The system must use PI's base_net_amount (not amount = 2*100*90 = 18000) so that
		SRBNB credit on PR = 17142.86, not 18000.
		"""
		from erpnext.accounts.doctype.account.test_account import create_account

		company = "_Test Company with perpetual inventory"
		warehouse = "Stores - TCP1"
		cost_center = "Main - TCP1"

		party_account = create_account(
			account_name="USD Payable For SRBNB Exchange Rate Test",
			parent_account="Accounts Payable - TCP1",
			account_type="Payable",
			company=company,
			account_currency="USD",
		)

		supplier = create_supplier(
			supplier_name="_Test USD Supplier for SRBNB Exchange Rate",
			default_currency="USD",
			party_account=party_account,
		).name

		item_code = make_item(
			"Test Item for SRBNB Inclusive Tax Exchange Rate Change",
			{"is_stock_item": 1},
		).name

		pr = make_purchase_receipt(
			item_code=item_code,
			qty=2,
			rate=100,
			currency="USD",
			conversion_rate=70,
			company=company,
			warehouse=warehouse,
			supplier=supplier,
		)

		pi = make_purchase_invoice(pr.name)
		pi.conversion_rate = 90
		pi.append(
			"taxes",
			{
				"charge_type": "On Net Total",
				"account_head": "_Test Account VAT - TCP1",
				"category": "Total",
				"add_deduct_tax": "Add",
				"included_in_print_rate": 1,
				"rate": 5,
				"description": "Test Inclusive Tax",
				"cost_center": cost_center,
			},
		)
		pi.save()
		pi.submit()

		pr.reload()

		# PI base_net_amount = qty * (rate / (1 + tax_rate/100)) * new_conversion_rate
		#                    = 2 * (100 / 1.05) * 90 ≈ 17142.86 INR
		# PR base_net_amount = qty * rate * pr_conversion_rate = 2 * 100 * 70 = 14000 INR
		tax_amount_pr = (200 - flt(200 / 1.05, 2)) * 90

		pi_base_net_amount = flt(2 * 100 * 90) - flt(tax_amount_pr)
		pr_base_net_amount = flt(2 * 100 * 70)
		expected_diff = flt(pi_base_net_amount - pr_base_net_amount)

		self.assertAlmostEqual(pr.items[0].amount_difference_with_purchase_invoice, expected_diff, places=2)

		# Total SRBNB credit = PR base_net_amount + amount_difference = PI base_net_amount
		srbnb_account = "Stock Received But Not Billed - TCP1"
		gl_entries = get_gl_entries("Purchase Receipt", pr.name, skip_cancelled=True)
		srbnb_credit = sum(flt(row.credit) for row in gl_entries if row.account == srbnb_account)
		self.assertAlmostEqual(srbnb_credit, pi_base_net_amount, places=2)

	def test_cancel_blocked_by_submitted_invoice_rolls_back(self):
		"""A submitted Purchase Invoice must block cancelling its Purchase Receipt. Frappe's backlink
		check rejects the cancel only after on_cancel has run stock, GL, and status work, so the whole
		transaction has to roll back: the receipt stays submitted with no leaked ledger entries."""
		pr = make_purchase_receipt()
		pi = make_purchase_invoice(pr.name)
		pi.insert()
		pi.submit()

		pr.reload()
		status_before = pr.status
		sle_before = frappe.db.count("Stock Ledger Entry", {"voucher_no": pr.name})
		gle_before = frappe.db.count("GL Entry", {"voucher_no": pr.name})

		frappe.db.savepoint("before_blocked_cancel")
		with self.assertRaises(frappe.LinkExistsError) as cm:
			pr.cancel()
		self.assertIn(pi.name, str(cm.exception))
		frappe.db.rollback(save_point="before_blocked_cancel")  # mimic the request-level rollback

		pr.reload()
		self.assertEqual(pr.docstatus, 1)
		self.assertEqual(pr.status, status_before)
		self.assertEqual(frappe.db.count("Stock Ledger Entry", {"voucher_no": pr.name}), sle_before)
		self.assertEqual(frappe.db.count("GL Entry", {"voucher_no": pr.name}), gle_before)


def create_asset_category_for_pr_test():
	category_name = "Test Asset Category for PR"

	asset_category = frappe.get_doc(
		{
			"doctype": "Asset Category",
			"asset_category_name": category_name,
			"enable_cwip_accounting": 0,
			"depreciation_method": "Straight Line",
			"total_number_of_depreciations": 12,
			"frequency_of_depreciation": 1,
			"accounts": [
				{
					"company_name": "Asset Company",
					"fixed_asset_account": "Electronic Equipment - AC",
				}
			],
		}
	).insert()
	return asset_category


def prepare_data_for_internal_transfer():
	from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier
	from erpnext.selling.doctype.customer.test_customer import create_internal_customer

	company = "_Test Company with perpetual inventory"

	create_internal_customer(
		"_Test Internal Customer 2",
		company,
		company,
	)

	create_internal_supplier(
		"_Test Internal Supplier 2",
		company,
		company,
	)

	if not frappe.db.get_value("Company", company, "unrealized_profit_loss_account"):
		account = "Unrealized Profit and Loss - TCP1"
		if not frappe.db.exists("Account", account):
			frappe.get_doc(
				{
					"doctype": "Account",
					"account_name": "Unrealized Profit and Loss",
					"parent_account": "Direct Income - TCP1",
					"company": company,
					"is_group": 0,
					"account_type": "Income Account",
				}
			).insert()

		frappe.db.set_value("Company", company, "unrealized_profit_loss_account", account)


def get_sl_entries(voucher_type, voucher_no):
	return frappe.db.sql(
		""" select actual_qty, warehouse, stock_value_difference
		from `tabStock Ledger Entry` where voucher_type=%s and voucher_no=%s
		order by posting_time desc""",
		(voucher_type, voucher_no),
		as_dict=1,
	)


def get_gl_entries(voucher_type, voucher_no, skip_cancelled=False, as_dict=True):
	gl = frappe.qb.DocType("GL Entry")
	gl_query = (
		frappe.qb.from_(gl)
		.select(
			gl.account,
			gl.debit,
			gl.credit,
			gl.cost_center,
		)
		.where((gl.voucher_type == voucher_type) & (gl.voucher_no == voucher_no))
		.orderby(gl.account, order=frappe.qb.desc)
	)
	if skip_cancelled:
		gl_query = gl_query.where(gl.is_cancelled == 0)
	else:
		gl_query = gl_query.select(gl.is_cancelled)
	return gl_query.run(as_dict=as_dict)


def get_taxes(**args):
	args = frappe._dict(args)

	return [
		{
			"account_head": "_Test Account Shipping Charges - TCP1",
			"add_deduct_tax": "Add",
			"category": "Valuation and Total",
			"charge_type": "Actual",
			"cost_center": args.cost_center or "Main - TCP1",
			"description": "Shipping Charges",
			"doctype": "Purchase Taxes and Charges",
			"parentfield": "taxes",
			"rate": 100.0,
			"tax_amount": 100.0,
		},
		{
			"account_head": "_Test Account VAT - TCP1",
			"add_deduct_tax": "Add",
			"category": "Total",
			"charge_type": "Actual",
			"cost_center": args.cost_center or "Main - TCP1",
			"description": "VAT",
			"doctype": "Purchase Taxes and Charges",
			"parentfield": "taxes",
			"rate": 120.0,
			"tax_amount": 120.0,
		},
		{
			"account_head": "_Test Account Customs Duty - TCP1",
			"add_deduct_tax": "Add",
			"category": "Valuation",
			"charge_type": "Actual",
			"cost_center": args.cost_center or "Main - TCP1",
			"description": "Customs Duty",
			"doctype": "Purchase Taxes and Charges",
			"parentfield": "taxes",
			"rate": 150.0,
			"tax_amount": 150.0,
		},
	]


def get_items(**args):
	args = frappe._dict(args)
	return [
		{
			"base_amount": 250.0,
			"conversion_factor": 1.0,
			"description": "_Test Item",
			"doctype": "Purchase Receipt Item",
			"item_code": "_Test Item",
			"item_name": "_Test Item",
			"parentfield": "items",
			"qty": 5.0,
			"rate": 50.0,
			"received_qty": 5.0,
			"rejected_qty": 0.0,
			"stock_uom": "_Test UOM",
			"uom": "_Test UOM",
			"warehouse": args.warehouse or "_Test Warehouse - _TC",
			"cost_center": args.cost_center or "Main - _TC",
		},
		{
			"base_amount": 250.0,
			"conversion_factor": 1.0,
			"description": "_Test Item Home Desktop 100",
			"doctype": "Purchase Receipt Item",
			"item_code": "_Test Item Home Desktop 100",
			"item_name": "_Test Item Home Desktop 100",
			"parentfield": "items",
			"qty": 5.0,
			"rate": 50.0,
			"received_qty": 5.0,
			"rejected_qty": 0.0,
			"stock_uom": "_Test UOM",
			"uom": "_Test UOM",
			"warehouse": args.warehouse or "_Test Warehouse 1 - _TC",
			"cost_center": args.cost_center or "Main - _TC",
		},
	]


def make_purchase_receipt(**args):
	if not frappe.db.exists("Location", "Test Location"):
		frappe.get_doc({"doctype": "Location", "location_name": "Test Location"}).insert()

	frappe.db.set_single_value("Buying Settings", "allow_multiple_items", 1)
	pr = frappe.new_doc("Purchase Receipt")
	args = frappe._dict(args)
	pr.posting_date = args.posting_date or today()
	if args.posting_time:
		pr.posting_time = args.posting_time
	if args.posting_date or args.posting_time:
		pr.set_posting_time = 1
	pr.company = args.company or "_Test Company"
	pr.supplier = args.supplier or "_Test Supplier"
	pr.is_subcontracted = args.is_subcontracted or 0
	pr.supplier_warehouse = args.supplier_warehouse or "_Test Warehouse 1 - _TC"
	pr.currency = args.currency or "INR"
	pr.is_return = args.is_return
	pr.return_against = args.return_against
	pr.apply_putaway_rule = args.apply_putaway_rule

	if args.get("conversion_rate") is not None:
		pr.conversion_rate = args.conversion_rate

	qty = args.qty if args.qty is not None else 5
	rejected_qty = args.rejected_qty or 0
	received_qty = args.received_qty or flt(rejected_qty) + flt(qty)

	item_code = args.item or args.item_code or "_Test Item"
	uom = args.uom or frappe.db.get_value("Item", item_code, "stock_uom") or "_Test UOM"

	bundle_id = None
	if not args.use_serial_batch_fields and (args.get("batch_no") or args.get("serial_no")):
		batches = {}
		if args.get("batch_no"):
			batches = frappe._dict({args.batch_no: qty})

		serial_nos = args.get("serial_no") or []

		bundle_id = make_serial_batch_bundle(
			frappe._dict(
				{
					"item_code": item_code,
					"warehouse": args.warehouse or "_Test Warehouse - _TC",
					"qty": qty,
					"batches": batches,
					"voucher_type": "Purchase Receipt",
					"serial_nos": serial_nos,
					"posting_date": args.posting_date or today(),
					"posting_time": args.posting_time,
					"do_not_submit": 1,
				}
			)
		).name

	pr.append(
		"items",
		{
			"item_code": item_code,
			"warehouse": args.warehouse or "_Test Warehouse - _TC",
			"qty": qty,
			"received_qty": received_qty,
			"rejected_qty": rejected_qty,
			"rejected_warehouse": args.rejected_warehouse or "_Test Rejected Warehouse - _TC"
			if rejected_qty != 0
			else "",
			"rate": args.rate if args.rate is not None else 50,
			"conversion_factor": args.conversion_factor or 1.0,
			"stock_qty": flt(qty) * (flt(args.conversion_factor) or 1.0),
			"serial_and_batch_bundle": bundle_id,
			"stock_uom": args.stock_uom or "_Test UOM",
			"uom": uom,
			"cost_center": args.cost_center or frappe.get_cached_value("Company", pr.company, "cost_center"),
			"asset_location": args.location or "Test Location",
			"use_serial_batch_fields": args.use_serial_batch_fields or 0,
			"serial_no": args.serial_no if args.use_serial_batch_fields else "",
			"batch_no": args.batch_no if args.use_serial_batch_fields else "",
		},
	)

	if args.get_multiple_items:
		pr.items = []

		company_cost_center = frappe.get_cached_value("Company", pr.company, "cost_center")
		cost_center = args.cost_center or company_cost_center

		for item in get_items(warehouse=args.warehouse, cost_center=cost_center):
			pr.append("items", item)

	if args.get_taxes_and_charges:
		for tax in get_taxes():
			pr.append("taxes", tax)

	if not args.do_not_save:
		pr.insert()
		if not args.do_not_submit:
			pr.submit()
		pr.load_from_db()

	return pr
