fix: consider sales rate as incoming rate for transit warehouse in purchase flow
(cherry picked from commit 683a47f7a1)
# Conflicts:
# erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
# erpnext/stock/stock_ledger.py
This commit is contained in:
committed by
Mergify
parent
002ae8ae13
commit
f72602ebf3
@@ -695,6 +695,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
|
# Intentionally passed negative debit amount to avoid incorrect GL Entry validation
|
||||||
gl_entries.append(
|
gl_entries.append(
|
||||||
self.get_gl_dict(
|
self.get_gl_dict(
|
||||||
@@ -704,7 +708,7 @@ class PurchaseInvoice(BuyingController):
|
|||||||
"cost_center": item.cost_center,
|
"cost_center": item.cost_center,
|
||||||
"project": item.project or self.project,
|
"project": item.project or self.project,
|
||||||
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
|
"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"],
|
warehouse_account[item.from_warehouse]["account_currency"],
|
||||||
item=item,
|
item=item,
|
||||||
|
|||||||
@@ -3683,6 +3683,7 @@ def create_sales_invoice(**args):
|
|||||||
"description": args.description or "_Test Item",
|
"description": args.description or "_Test Item",
|
||||||
"gst_hsn_code": "999800",
|
"gst_hsn_code": "999800",
|
||||||
"warehouse": args.warehouse or "_Test Warehouse - _TC",
|
"warehouse": args.warehouse or "_Test Warehouse - _TC",
|
||||||
|
"target_warehouse": args.target_warehouse or "_Test Warehouse 1 - _TC",
|
||||||
"qty": args.qty or 1,
|
"qty": args.qty or 1,
|
||||||
"uom": args.uom or "Nos",
|
"uom": args.uom or "Nos",
|
||||||
"stock_uom": args.uom or "Nos",
|
"stock_uom": args.uom or "Nos",
|
||||||
|
|||||||
@@ -2,9 +2,14 @@
|
|||||||
# License: GNU General Public License v3. See license.txt
|
# License: GNU General Public License v3. See license.txt
|
||||||
|
|
||||||
|
|
||||||
|
<<<<<<< HEAD
|
||||||
import json
|
import json
|
||||||
import unittest
|
import unittest
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
|
=======
|
||||||
|
from cmath import pi
|
||||||
|
from turtle import update
|
||||||
|
>>>>>>> 683a47f7a1 (fix: consider sales rate as incoming rate for transit warehouse in purchase flow)
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe.tests.utils import FrappeTestCase, change_settings
|
from frappe.tests.utils import FrappeTestCase, change_settings
|
||||||
@@ -19,6 +24,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.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.serial_no.serial_no import SerialNoDuplicateError, get_serial_nos
|
||||||
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
|
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
|
from erpnext.stock.stock_ledger import SerialNoExistsInFutureTransaction
|
||||||
|
|
||||||
|
|
||||||
@@ -1404,6 +1410,8 @@ class TestPurchaseReceipt(FrappeTestCase):
|
|||||||
self.assertEqual(pr1.items[0].rate, 100)
|
self.assertEqual(pr1.items[0].rate, 100)
|
||||||
pr1.submit()
|
pr1.submit()
|
||||||
|
|
||||||
|
self.assertEqual(pr1.is_internal_supplier, 1)
|
||||||
|
|
||||||
# Backdated purchase receipt entry, the valuation rate should be updated for DN1 and PR1
|
# Backdated purchase receipt entry, the valuation rate should be updated for DN1 and PR1
|
||||||
make_purchase_receipt(
|
make_purchase_receipt(
|
||||||
item_code=item_doc.name,
|
item_code=item_doc.name,
|
||||||
@@ -1446,6 +1454,234 @@ class TestPurchaseReceipt(FrappeTestCase):
|
|||||||
|
|
||||||
self.assertEqual(query[0].value, 0)
|
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):
|
def test_batch_expiry_for_purchase_receipt(self):
|
||||||
from erpnext.controllers.sales_and_purchase_return import make_return_doc
|
from erpnext.controllers.sales_and_purchase_return import make_return_doc
|
||||||
|
|
||||||
|
|||||||
@@ -579,6 +579,15 @@ class update_entries_after(object):
|
|||||||
sle.stock_queue = json.dumps(self.wh_data.stock_queue)
|
sle.stock_queue = json.dumps(self.wh_data.stock_queue)
|
||||||
sle.stock_value_difference = stock_value_difference
|
sle.stock_value_difference = stock_value_difference
|
||||||
sle.doctype = "Stock Ledger Entry"
|
sle.doctype = "Stock Ledger Entry"
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
frappe.get_doc(sle).db_update()
|
frappe.get_doc(sle).db_update()
|
||||||
|
|
||||||
if not self.args.get("sle_id"):
|
if not self.args.get("sle_id"):
|
||||||
@@ -641,22 +650,7 @@ class update_entries_after(object):
|
|||||||
and sle.voucher_detail_no
|
and sle.voucher_detail_no
|
||||||
and frappe.get_cached_value(sle.voucher_type, sle.voucher_no, "is_internal_supplier")
|
and frappe.get_cached_value(sle.voucher_type, sle.voucher_no, "is_internal_supplier")
|
||||||
):
|
):
|
||||||
field = (
|
rate = get_incoming_rate_for_inter_company_transfer(sle)
|
||||||
"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",
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
if sle.voucher_type in ("Purchase Receipt", "Purchase Invoice"):
|
if sle.voucher_type in ("Purchase Receipt", "Purchase Invoice"):
|
||||||
rate_field = "valuation_rate"
|
rate_field = "valuation_rate"
|
||||||
@@ -731,14 +725,12 @@ class update_entries_after(object):
|
|||||||
|
|
||||||
def update_rate_on_purchase_receipt(self, sle, outgoing_rate):
|
def update_rate_on_purchase_receipt(self, sle, outgoing_rate):
|
||||||
if frappe.db.exists(sle.voucher_type + " Item", sle.voucher_detail_no):
|
if frappe.db.exists(sle.voucher_type + " Item", sle.voucher_detail_no):
|
||||||
frappe.db.set_value(
|
if sle.voucher_type in ["Purchase Receipt", "Purchase Invoice"] and frappe.get_cached_value(
|
||||||
sle.voucher_type + " Item",
|
sle.voucher_type, sle.voucher_no, "is_internal_supplier"
|
||||||
sle.voucher_detail_no,
|
):
|
||||||
{
|
frappe.db.set_value(
|
||||||
"base_net_rate": outgoing_rate,
|
f"{sle.voucher_type} Item", sle.voucher_detail_no, "valuation_rate", sle.outgoing_rate
|
||||||
"valuation_rate": outgoing_rate,
|
)
|
||||||
},
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
frappe.db.set_value(
|
frappe.db.set_value(
|
||||||
"Purchase Receipt Item Supplied", sle.voucher_detail_no, "rate", outgoing_rate
|
"Purchase Receipt Item Supplied", sle.voucher_detail_no, "rate", outgoing_rate
|
||||||
@@ -1481,4 +1473,35 @@ def _round_off_if_near_zero(number: float, precision: int = 6) -> float:
|
|||||||
if abs(0.0 - flt(number)) < (1.0 / (10**precision)):
|
if abs(0.0 - flt(number)) < (1.0 / (10**precision)):
|
||||||
return 0.0
|
return 0.0
|
||||||
|
|
||||||
|
<<<<<<< HEAD
|
||||||
return flt(number)
|
return flt(number)
|
||||||
|
=======
|
||||||
|
def is_negative_stock_allowed(*, item_code: Optional[str] = None) -> bool:
|
||||||
|
if cint(frappe.db.get_single_value("Stock Settings", "allow_negative_stock", cache=True)):
|
||||||
|
return True
|
||||||
|
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
|
||||||
|
>>>>>>> 683a47f7a1 (fix: consider sales rate as incoming rate for transit warehouse in purchase flow)
|
||||||
|
|||||||
Reference in New Issue
Block a user