Merge pull request #32563 from rohitwaghchaure/fixed-source-stock-reposting-for-purchase-flow
fix: consider sales rate as incoming rate for transit warehouse in purchase flow
This commit is contained in:
commit
ab137472b0
@ -705,6 +705,10 @@ class PurchaseInvoice(BuyingController):
|
||||
)
|
||||
)
|
||||
|
||||
credit_amount = item.base_net_amount
|
||||
if self.is_internal_supplier and item.valuation_rate:
|
||||
credit_amount = flt(item.valuation_rate * item.stock_qty)
|
||||
|
||||
# Intentionally passed negative debit amount to avoid incorrect GL Entry validation
|
||||
gl_entries.append(
|
||||
self.get_gl_dict(
|
||||
@ -714,7 +718,7 @@ class PurchaseInvoice(BuyingController):
|
||||
"cost_center": item.cost_center,
|
||||
"project": item.project or self.project,
|
||||
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
|
||||
"debit": -1 * flt(item.base_net_amount, item.precision("base_net_amount")),
|
||||
"debit": -1 * flt(credit_amount, item.precision("base_net_amount")),
|
||||
},
|
||||
warehouse_account[item.from_warehouse]["account_currency"],
|
||||
item=item,
|
||||
|
@ -3306,6 +3306,7 @@ def create_sales_invoice(**args):
|
||||
"item_name": args.item_name or "_Test Item",
|
||||
"description": args.description or "_Test Item",
|
||||
"warehouse": args.warehouse or "_Test Warehouse - _TC",
|
||||
"target_warehouse": args.target_warehouse,
|
||||
"qty": args.qty or 1,
|
||||
"uom": args.uom or "Nos",
|
||||
"stock_uom": args.uom or "Nos",
|
||||
|
@ -2,6 +2,9 @@
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
|
||||
|
||||
from cmath import pi
|
||||
from turtle import update
|
||||
|
||||
import frappe
|
||||
from frappe.tests.utils import FrappeTestCase, change_settings
|
||||
from frappe.utils import add_days, cint, cstr, flt, today
|
||||
@ -14,6 +17,7 @@ from erpnext.stock.doctype.item.test_item import create_item, make_item
|
||||
from erpnext.stock.doctype.purchase_receipt.purchase_receipt import make_purchase_invoice
|
||||
from erpnext.stock.doctype.serial_no.serial_no import SerialNoDuplicateError, get_serial_nos
|
||||
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
|
||||
from erpnext.stock.get_item_details import update_stock
|
||||
from erpnext.stock.stock_ledger import SerialNoExistsInFutureTransaction
|
||||
|
||||
|
||||
@ -1199,6 +1203,8 @@ class TestPurchaseReceipt(FrappeTestCase):
|
||||
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,
|
||||
@ -1241,6 +1247,234 @@ class TestPurchaseReceipt(FrappeTestCase):
|
||||
|
||||
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_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
|
||||
|
||||
|
@ -542,6 +542,14 @@ class update_entries_after(object):
|
||||
if not self.args.get("sle_id"):
|
||||
self.get_dynamic_incoming_outgoing_rate(sle)
|
||||
|
||||
if (
|
||||
sle.voucher_type in ["Purchase Receipt", "Purchase Invoice"]
|
||||
and sle.voucher_detail_no
|
||||
and sle.actual_qty < 0
|
||||
and frappe.get_cached_value(sle.voucher_type, sle.voucher_no, "is_internal_supplier")
|
||||
):
|
||||
sle.outgoing_rate = get_incoming_rate_for_inter_company_transfer(sle)
|
||||
|
||||
if get_serial_nos(sle.serial_no):
|
||||
self.get_serialized_values(sle)
|
||||
self.wh_data.qty_after_transaction += flt(sle.actual_qty)
|
||||
@ -589,6 +597,7 @@ class update_entries_after(object):
|
||||
sle.stock_queue = json.dumps(self.wh_data.stock_queue)
|
||||
sle.stock_value_difference = stock_value_difference
|
||||
sle.doctype = "Stock Ledger Entry"
|
||||
|
||||
frappe.get_doc(sle).db_update()
|
||||
|
||||
if not self.args.get("sle_id"):
|
||||
@ -652,22 +661,7 @@ class update_entries_after(object):
|
||||
and sle.voucher_detail_no
|
||||
and frappe.get_cached_value(sle.voucher_type, sle.voucher_no, "is_internal_supplier")
|
||||
):
|
||||
field = (
|
||||
"delivery_note_item" if sle.voucher_type == "Purchase Receipt" else "sales_invoice_item"
|
||||
)
|
||||
doctype = (
|
||||
"Delivery Note Item" if sle.voucher_type == "Purchase Receipt" else "Sales Invoice Item"
|
||||
)
|
||||
refernce_name = frappe.get_cached_value(
|
||||
sle.voucher_type + " Item", sle.voucher_detail_no, field
|
||||
)
|
||||
|
||||
if refernce_name:
|
||||
rate = frappe.get_cached_value(
|
||||
doctype,
|
||||
refernce_name,
|
||||
"incoming_rate",
|
||||
)
|
||||
rate = get_incoming_rate_for_inter_company_transfer(sle)
|
||||
else:
|
||||
if sle.voucher_type in ("Purchase Receipt", "Purchase Invoice"):
|
||||
rate_field = "valuation_rate"
|
||||
@ -748,14 +742,12 @@ class update_entries_after(object):
|
||||
|
||||
def update_rate_on_purchase_receipt(self, sle, outgoing_rate):
|
||||
if frappe.db.exists(sle.voucher_type + " Item", sle.voucher_detail_no):
|
||||
frappe.db.set_value(
|
||||
sle.voucher_type + " Item",
|
||||
sle.voucher_detail_no,
|
||||
{
|
||||
"base_net_rate": outgoing_rate,
|
||||
"valuation_rate": outgoing_rate,
|
||||
},
|
||||
)
|
||||
if sle.voucher_type in ["Purchase Receipt", "Purchase Invoice"] and frappe.get_cached_value(
|
||||
sle.voucher_type, sle.voucher_no, "is_internal_supplier"
|
||||
):
|
||||
frappe.db.set_value(
|
||||
f"{sle.voucher_type} Item", sle.voucher_detail_no, "valuation_rate", sle.outgoing_rate
|
||||
)
|
||||
else:
|
||||
frappe.db.set_value(
|
||||
"Purchase Receipt Item Supplied", sle.voucher_detail_no, "rate", outgoing_rate
|
||||
@ -1546,3 +1538,25 @@ def is_negative_stock_allowed(*, item_code: Optional[str] = None) -> bool:
|
||||
if item_code and cint(frappe.db.get_value("Item", item_code, "allow_negative_stock", cache=True)):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def get_incoming_rate_for_inter_company_transfer(sle) -> float:
|
||||
"""
|
||||
For inter company transfer, incoming rate is the average of the outgoing rate
|
||||
"""
|
||||
rate = 0.0
|
||||
|
||||
field = "delivery_note_item" if sle.voucher_type == "Purchase Receipt" else "sales_invoice_item"
|
||||
|
||||
doctype = "Delivery Note Item" if sle.voucher_type == "Purchase Receipt" else "Sales Invoice Item"
|
||||
|
||||
reference_name = frappe.get_cached_value(sle.voucher_type + " Item", sle.voucher_detail_no, field)
|
||||
|
||||
if reference_name:
|
||||
rate = frappe.get_cached_value(
|
||||
doctype,
|
||||
reference_name,
|
||||
"incoming_rate",
|
||||
)
|
||||
|
||||
return rate
|
||||
|
Loading…
x
Reference in New Issue
Block a user