Merge pull request #32272 from deepeshgarg007/internal_transfer_precision_fixes
fix: Incoming rate precision fixes for intra company transfer
diff --git a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json
index fca7e3a..9de9036 100644
--- a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json
+++ b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json
@@ -711,6 +711,7 @@
"label": "Valuation Rate",
"no_copy": 1,
"options": "Company:company:default_currency",
+ "precision": "6",
"print_hide": 1,
"read_only": 1
},
@@ -870,7 +871,7 @@
"idx": 1,
"istable": 1,
"links": [],
- "modified": "2022-09-27 10:54:23.980713",
+ "modified": "2022-10-12 03:37:29.032732",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Purchase Invoice Item",
diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
index 52e221c..301d3e1 100644
--- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
@@ -32,10 +32,20 @@
get_qty_after_transaction,
make_stock_entry,
)
-from erpnext.stock.utils import get_incoming_rate
+from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import (
+ create_stock_reconciliation,
+)
+from erpnext.stock.utils import get_incoming_rate, get_stock_balance
class TestSalesInvoice(unittest.TestCase):
+ def setUp(self):
+ from erpnext.stock.doctype.stock_ledger_entry.test_stock_ledger_entry import create_items
+
+ create_items(["_Test Internal Transfer Item"], uoms=[{"uom": "Box", "conversion_factor": 10}])
+ create_internal_parties()
+ setup_accounts()
+
def make(self):
w = frappe.copy_doc(test_records[0])
w.is_pos = 0
@@ -1705,7 +1715,7 @@
si.save()
self.assertEqual(si.get("items")[0].rate, flt((price_list_rate * 25) / 100 + price_list_rate))
- def test_outstanding_amount_after_advance_jv_cancelation(self):
+ def test_outstanding_amount_after_advance_jv_cancellation(self):
from erpnext.accounts.doctype.journal_entry.test_journal_entry import (
test_records as jv_test_records,
)
@@ -1749,7 +1759,7 @@
flt(si.rounded_total + si.total_advance, si.precision("outstanding_amount")),
)
- def test_outstanding_amount_after_advance_payment_entry_cancelation(self):
+ def test_outstanding_amount_after_advance_payment_entry_cancellation(self):
pe = frappe.get_doc(
{
"doctype": "Payment Entry",
@@ -2367,29 +2377,6 @@
acc_settings.save()
def test_inter_company_transaction(self):
- from erpnext.selling.doctype.customer.test_customer import create_internal_customer
-
- create_internal_customer(
- customer_name="_Test Internal Customer",
- represents_company="_Test Company 1",
- allowed_to_interact_with="Wind Power LLC",
- )
-
- if not frappe.db.exists("Supplier", "_Test Internal Supplier"):
- supplier = frappe.get_doc(
- {
- "supplier_group": "_Test Supplier Group",
- "supplier_name": "_Test Internal Supplier",
- "doctype": "Supplier",
- "is_internal_supplier": 1,
- "represents_company": "Wind Power LLC",
- }
- )
-
- supplier.append("companies", {"company": "_Test Company 1"})
-
- supplier.insert()
-
si = create_sales_invoice(
company="Wind Power LLC",
customer="_Test Internal Customer",
@@ -2440,38 +2427,6 @@
"Expenses Included In Valuation - _TC1",
)
- if not frappe.db.exists("Customer", "_Test Internal Customer"):
- customer = frappe.get_doc(
- {
- "customer_group": "_Test Customer Group",
- "customer_name": "_Test Internal Customer",
- "customer_type": "Individual",
- "doctype": "Customer",
- "territory": "_Test Territory",
- "is_internal_customer": 1,
- "represents_company": "_Test Company 1",
- }
- )
-
- customer.append("companies", {"company": "Wind Power LLC"})
-
- customer.insert()
-
- if not frappe.db.exists("Supplier", "_Test Internal Supplier"):
- supplier = frappe.get_doc(
- {
- "supplier_group": "_Test Supplier Group",
- "supplier_name": "_Test Internal Supplier",
- "doctype": "Supplier",
- "is_internal_supplier": 1,
- "represents_company": "Wind Power LLC",
- }
- )
-
- supplier.append("companies", {"company": "_Test Company 1"})
-
- supplier.insert()
-
# begin test
si = create_sales_invoice(
company="Wind Power LLC",
@@ -2541,34 +2496,9 @@
se.cancel()
def test_internal_transfer_gl_entry(self):
- ## Create internal transfer account
- from erpnext.selling.doctype.customer.test_customer import create_internal_customer
-
- account = create_account(
- account_name="Unrealized Profit",
- parent_account="Current Liabilities - TCP1",
- company="_Test Company with perpetual inventory",
- )
-
- frappe.db.set_value(
- "Company", "_Test Company with perpetual inventory", "unrealized_profit_loss_account", account
- )
-
- customer = create_internal_customer(
- "_Test Internal Customer 2",
- "_Test Company with perpetual inventory",
- "_Test Company with perpetual inventory",
- )
-
- create_internal_supplier(
- "_Test Internal Supplier 2",
- "_Test Company with perpetual inventory",
- "_Test Company with perpetual inventory",
- )
-
si = create_sales_invoice(
company="_Test Company with perpetual inventory",
- customer=customer,
+ customer="_Test Internal Customer 2",
debit_to="Debtors - TCP1",
warehouse="Stores - TCP1",
income_account="Sales - TCP1",
@@ -2582,7 +2512,7 @@
si.update_stock = 1
si.items[0].target_warehouse = "Work In Progress - TCP1"
- # Add stock to stores for succesful stock transfer
+ # Add stock to stores for successful stock transfer
make_stock_entry(
target="Stores - TCP1", company="_Test Company with perpetual inventory", qty=1, basic_rate=100
)
@@ -2638,6 +2568,77 @@
check_gl_entries(self, target_doc.name, pi_gl_entries, add_days(nowdate(), -1))
+ def test_internal_transfer_gl_precision_issues(self):
+ # Make a stock queue of an item with two valuations
+
+ # Remove all existing stock for this
+ if get_stock_balance("_Test Internal Transfer Item", "Stores - TCP1", "2022-04-10"):
+ create_stock_reconciliation(
+ item_code="_Test Internal Transfer Item",
+ warehouse="Stores - TCP1",
+ qty=0,
+ rate=0,
+ company="_Test Company with perpetual inventory",
+ expense_account="Stock Adjustment - TCP1"
+ if frappe.get_all("Stock Ledger Entry")
+ else "Temporary Opening - TCP1",
+ posting_date="2020-04-10",
+ posting_time="14:00",
+ )
+
+ make_stock_entry(
+ item_code="_Test Internal Transfer Item",
+ target="Stores - TCP1",
+ qty=9000000,
+ basic_rate=52.0,
+ posting_date="2020-04-10",
+ posting_time="14:00",
+ )
+ make_stock_entry(
+ item_code="_Test Internal Transfer Item",
+ target="Stores - TCP1",
+ qty=60000000,
+ basic_rate=52.349777,
+ posting_date="2020-04-10",
+ posting_time="14:00",
+ )
+
+ # Make an internal transfer Sales Invoice Stock in non stock uom to check
+ # for rounding errors while converting to stock uom
+ si = create_sales_invoice(
+ company="_Test Company with perpetual inventory",
+ customer="_Test Internal Customer 2",
+ item_code="_Test Internal Transfer Item",
+ qty=5000000,
+ uom="Box",
+ debit_to="Debtors - TCP1",
+ warehouse="Stores - TCP1",
+ income_account="Sales - TCP1",
+ expense_account="Cost of Goods Sold - TCP1",
+ cost_center="Main - TCP1",
+ currency="INR",
+ do_not_save=1,
+ )
+
+ # Check GL Entries with precision
+ si.update_stock = 1
+ si.items[0].target_warehouse = "Work In Progress - TCP1"
+ si.items[0].conversion_factor = 10
+ si.save()
+ si.submit()
+
+ # Check if adjustment entry is created
+ self.assertTrue(
+ frappe.db.exists(
+ "GL Entry",
+ {
+ "voucher_type": "Sales Invoice",
+ "voucher_no": si.name,
+ "remarks": "Rounding gain/loss Entry for Stock Transfer",
+ },
+ )
+ )
+
def test_item_tax_net_range(self):
item = create_item("T Shirt")
@@ -3077,7 +3078,7 @@
[deferred_account, 2022.47, 0.0, "2019-03-15"],
]
- gl_entries = gl_entries = frappe.db.sql(
+ gl_entries = frappe.db.sql(
"""select account, debit, credit, posting_date
from `tabGL Entry`
where voucher_type='Journal Entry' and voucher_detail_no=%s and posting_date <= %s
@@ -3432,6 +3433,34 @@
]
+def create_internal_parties():
+ from erpnext.selling.doctype.customer.test_customer import create_internal_customer
+
+ create_internal_customer(
+ customer_name="_Test Internal Customer",
+ represents_company="_Test Company 1",
+ allowed_to_interact_with="Wind Power LLC",
+ )
+
+ create_internal_customer(
+ customer_name="_Test Internal Customer 2",
+ represents_company="_Test Company with perpetual inventory",
+ allowed_to_interact_with="_Test Company with perpetual inventory",
+ )
+
+ create_internal_supplier(
+ supplier_name="_Test Internal Supplier",
+ represents_company="Wind Power LLC",
+ allowed_to_interact_with="_Test Company 1",
+ )
+
+ create_internal_supplier(
+ supplier_name="_Test Internal Supplier 2",
+ represents_company="_Test Company with perpetual inventory",
+ allowed_to_interact_with="_Test Company with perpetual inventory",
+ )
+
+
def create_internal_supplier(supplier_name, represents_company, allowed_to_interact_with):
if not frappe.db.exists("Supplier", supplier_name):
supplier = frappe.get_doc(
@@ -3454,6 +3483,19 @@
return supplier_name
+def setup_accounts():
+ ## Create internal transfer account
+ account = create_account(
+ account_name="Unrealized Profit",
+ parent_account="Current Liabilities - TCP1",
+ company="_Test Company with perpetual inventory",
+ )
+
+ frappe.db.set_value(
+ "Company", "_Test Company with perpetual inventory", "unrealized_profit_loss_account", account
+ )
+
+
def add_taxes(doc):
doc.append(
"taxes",
diff --git a/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json b/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json
index 4f97b63..a307a6c 100644
--- a/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json
+++ b/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json
@@ -820,6 +820,7 @@
"label": "Incoming Rate (Costing)",
"no_copy": 1,
"options": "Company:company:default_currency",
+ "precision": "6",
"print_hide": 1
},
{
@@ -875,7 +876,7 @@
"idx": 1,
"istable": 1,
"links": [],
- "modified": "2022-09-06 14:17:43.394309",
+ "modified": "2022-10-10 20:57:38.340026",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice Item",
diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py
index 5e9c069..e8e9076 100644
--- a/erpnext/controllers/selling_controller.py
+++ b/erpnext/controllers/selling_controller.py
@@ -442,11 +442,17 @@
# For internal transfers use incoming rate as the valuation rate
if self.is_internal_transfer():
if d.doctype == "Packed Item":
- incoming_rate = flt(d.incoming_rate * d.conversion_factor, d.precision("incoming_rate"))
+ incoming_rate = flt(
+ flt(d.incoming_rate, d.precision("incoming_rate")) * d.conversion_factor,
+ d.precision("incoming_rate"),
+ )
if d.incoming_rate != incoming_rate:
d.incoming_rate = incoming_rate
else:
- rate = flt(d.incoming_rate * d.conversion_factor, d.precision("rate"))
+ rate = flt(
+ flt(d.incoming_rate, d.precision("incoming_rate")) * d.conversion_factor,
+ d.precision("rate"),
+ )
if d.rate != rate:
d.rate = rate
frappe.msgprint(
diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py
index 9149b4d..98dc586 100644
--- a/erpnext/controllers/stock_controller.py
+++ b/erpnext/controllers/stock_controller.py
@@ -142,13 +142,15 @@
warehouse_with_no_account = []
precision = self.get_debit_field_precision()
for item_row in voucher_details:
-
sle_list = sle_map.get(item_row.name)
+ sle_rounding_diff = 0.0
if sle_list:
for sle in sle_list:
if warehouse_account.get(sle.warehouse):
# from warehouse account
+ sle_rounding_diff += flt(sle.stock_value_difference)
+
self.check_expense_account(item_row)
# expense account/ target_warehouse / source_warehouse
@@ -191,6 +193,46 @@
elif sle.warehouse not in warehouse_with_no_account:
warehouse_with_no_account.append(sle.warehouse)
+ if abs(sle_rounding_diff) > (1.0 / (10**precision)) and self.is_internal_transfer():
+ warehouse_asset_account = ""
+ if self.get("is_internal_customer"):
+ warehouse_asset_account = warehouse_account[item_row.get("target_warehouse")]["account"]
+ elif self.get("is_internal_supplier"):
+ warehouse_asset_account = warehouse_account[item_row.get("warehouse")]["account"]
+
+ expense_account = frappe.db.get_value("Company", self.company, "default_expense_account")
+
+ gl_list.append(
+ self.get_gl_dict(
+ {
+ "account": expense_account,
+ "against": warehouse_asset_account,
+ "cost_center": item_row.cost_center,
+ "project": item_row.project or self.get("project"),
+ "remarks": _("Rounding gain/loss Entry for Stock Transfer"),
+ "debit": sle_rounding_diff,
+ "is_opening": item_row.get("is_opening") or self.get("is_opening") or "No",
+ },
+ warehouse_account[sle.warehouse]["account_currency"],
+ item=item_row,
+ )
+ )
+
+ gl_list.append(
+ self.get_gl_dict(
+ {
+ "account": warehouse_asset_account,
+ "against": expense_account,
+ "cost_center": item_row.cost_center,
+ "remarks": _("Rounding gain/loss Entry for Stock Transfer"),
+ "credit": sle_rounding_diff,
+ "project": item_row.get("project") or self.get("project"),
+ "is_opening": item_row.get("is_opening") or self.get("is_opening") or "No",
+ },
+ item=item_row,
+ )
+ )
+
if warehouse_with_no_account:
for wh in warehouse_with_no_account:
if frappe.db.get_value("Warehouse", wh, "company"):
diff --git a/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json b/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json
index 0911cdb..0a5cbab 100644
--- a/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json
+++ b/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json
@@ -753,6 +753,7 @@
"fieldtype": "Currency",
"label": "Incoming Rate",
"no_copy": 1,
+ "precision": "6",
"print_hide": 1,
"read_only": 1
},
@@ -813,7 +814,7 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
- "modified": "2022-09-06 14:19:42.876357",
+ "modified": "2022-10-12 03:36:05.344847",
"modified_by": "Administrator",
"module": "Stock",
"name": "Delivery Note Item",
diff --git a/erpnext/stock/doctype/item/test_item.py b/erpnext/stock/doctype/item/test_item.py
index 0c710b0..e1ee938 100644
--- a/erpnext/stock/doctype/item/test_item.py
+++ b/erpnext/stock/doctype/item/test_item.py
@@ -32,7 +32,7 @@
test_dependencies = ["Warehouse", "Item Group", "Item Tax Template", "Brand", "Item Attribute"]
-def make_item(item_code=None, properties=None):
+def make_item(item_code=None, properties=None, uoms=None):
if not item_code:
item_code = frappe.generate_hash(length=16)
@@ -56,6 +56,11 @@
for item_default in [doc for doc in item.get("item_defaults") if not doc.default_warehouse]:
item_default.default_warehouse = "_Test Warehouse - _TC"
item_default.company = "_Test Company"
+
+ if uoms:
+ for uom in uoms:
+ item.append("uoms", uom)
+
item.insert()
return item
diff --git a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json
index 39833b5..772736e 100644
--- a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json
+++ b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json
@@ -744,6 +744,7 @@
"oldfieldname": "valuation_rate",
"oldfieldtype": "Currency",
"options": "Company:company:default_currency",
+ "precision": "6",
"print_hide": 1,
"print_width": "80px",
"read_only": 1,
@@ -999,7 +1000,7 @@
"idx": 1,
"istable": 1,
"links": [],
- "modified": "2022-07-28 19:27:54.880781",
+ "modified": "2022-10-12 03:37:59.516609",
"modified_by": "Administrator",
"module": "Stock",
"name": "Purchase Receipt Item",
diff --git a/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py b/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py
index 1410da5..6c341d9 100644
--- a/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py
+++ b/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py
@@ -1323,13 +1323,15 @@
item.save()
-def create_items():
- items = [
- "_Test Item for Reposting",
- "_Test Finished Item for Reposting",
- "_Test Subcontracted Item for Reposting",
- "_Test Bundled Item for Reposting",
- ]
+def create_items(items=None, uoms=None):
+ if not items:
+ items = [
+ "_Test Item for Reposting",
+ "_Test Finished Item for Reposting",
+ "_Test Subcontracted Item for Reposting",
+ "_Test Bundled Item for Reposting",
+ ]
+
for d in items:
properties = {"valuation_method": "FIFO"}
if d == "_Test Bundled Item for Reposting":
@@ -1337,7 +1339,7 @@
elif d == "_Test Subcontracted Item for Reposting":
properties.update({"is_sub_contracted_item": 1})
- make_item(d, properties=properties)
+ make_item(d, properties=properties, uoms=uoms)
return items
diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
index 23e0f1e..d92d0f1 100644
--- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
+++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
@@ -132,7 +132,9 @@
key.append(row.get(field))
if key in item_warehouse_combinations:
- self.validation_messages.append(_get_msg(row_num, _("Duplicate entry")))
+ self.validation_messages.append(
+ _get_msg(row_num, _("Same item and warehouse combination already entered."))
+ )
else:
item_warehouse_combinations.append(key)