From f72602ebf3c8c641225dfc4afcde354bf8ead13a Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Tue, 11 Oct 2022 15:11:39 +0530 Subject: [PATCH 01/19] fix: consider sales rate as incoming rate for transit warehouse in purchase flow (cherry picked from commit 683a47f7a1676aced82a2abab086510228fcc7b4) # Conflicts: # erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py # erpnext/stock/stock_ledger.py --- .../purchase_invoice/purchase_invoice.py | 6 +- .../sales_invoice/test_sales_invoice.py | 1 + .../purchase_receipt/test_purchase_receipt.py | 236 ++++++++++++++++++ erpnext/stock/stock_ledger.py | 71 ++++-- 4 files changed, 289 insertions(+), 25 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 6476845de78..fffaa9bfaec 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -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 gl_entries.append( self.get_gl_dict( @@ -704,7 +708,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, diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index e300e0f35d3..4f16c545f6e 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -3683,6 +3683,7 @@ def create_sales_invoice(**args): "description": args.description or "_Test Item", "gst_hsn_code": "999800", "warehouse": args.warehouse or "_Test Warehouse - _TC", + "target_warehouse": args.target_warehouse or "_Test Warehouse 1 - _TC", "qty": args.qty or 1, "uom": args.uom or "Nos", "stock_uom": args.uom or "Nos", diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index e9c52436bc4..4fdf8a7a3f2 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -2,9 +2,14 @@ # License: GNU General Public License v3. See license.txt +<<<<<<< HEAD import json import unittest 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 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.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 @@ -1404,6 +1410,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, @@ -1446,6 +1454,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 diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index 0dadda46807..3655258c1b8 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -579,6 +579,15 @@ 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" + + 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() if not self.args.get("sle_id"): @@ -641,22 +650,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" @@ -731,14 +725,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 @@ -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)): return 0.0 +<<<<<<< HEAD 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) From 6f43133c04637e252965e28ce1df7bb16ffefb48 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Wed, 12 Oct 2022 15:09:50 +0530 Subject: [PATCH 02/19] fix: consider outgoingrate while valuation rate calculate (cherry picked from commit 3266e54e33b0ac65cc8e1b51cf4d65a75c12040f) --- erpnext/stock/stock_ledger.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index 3655258c1b8..a1c14e37924 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -532,6 +532,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) @@ -580,14 +588,6 @@ class update_entries_after(object): sle.stock_value_difference = stock_value_difference 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() if not self.args.get("sle_id"): From acd64ba7c137d32fa217af1a349d5663a7086251 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Wed, 12 Oct 2022 15:45:36 +0530 Subject: [PATCH 03/19] fix: test case (cherry picked from commit 98bf8e13043a5efcf4b92a02d9c0c1e6ae67b3e8) --- erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 4f16c545f6e..b3b48298676 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -3683,7 +3683,7 @@ def create_sales_invoice(**args): "description": args.description or "_Test Item", "gst_hsn_code": "999800", "warehouse": args.warehouse or "_Test Warehouse - _TC", - "target_warehouse": args.target_warehouse or "_Test Warehouse 1 - _TC", + "target_warehouse": args.target_warehouse, "qty": args.qty or 1, "uom": args.uom or "Nos", "stock_uom": args.uom or "Nos", From 227ce5f8a28b1d469f7ee9820d460ef05ff4a9df Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 19 Sep 2022 15:06:06 +0530 Subject: [PATCH 04/19] fix: Incoming rate precision fixes for intra company transfer (cherry picked from commit 083309c056512f86ece9b3276338108d1e5b98bf) --- erpnext/controllers/selling_controller.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py index 8a26d9e2c89..76a717b8317 100644 --- a/erpnext/controllers/selling_controller.py +++ b/erpnext/controllers/selling_controller.py @@ -439,11 +439,17 @@ class SellingController(StockController): # 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( From dd26ef96e0c1baeb650dab1fa07e3c2b6ef172b0 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 26 Sep 2022 21:15:57 +0530 Subject: [PATCH 05/19] fix: Hanlde rounding loss for internal transfer (cherry picked from commit 6e47fd54a0c4d4b77ed6bedac896286d591416f8) --- erpnext/controllers/stock_controller.py | 40 ++++++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index 2eea0bde8c6..d232cd58f45 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -139,13 +139,15 @@ class StockController(AccountsController): 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, precision) + self.check_expense_account(item_row) # expense account/ target_warehouse / source_warehouse @@ -188,6 +190,42 @@ class StockController(AccountsController): elif sle.warehouse not in warehouse_with_no_account: warehouse_with_no_account.append(sle.warehouse) + if sle_rounding_diff > 0: + expense_account = item_row.get("expense_account") + target_warehouse_account = warehouse_account[item_row.get("target_warehouse")]["account"] + source_warehouse_account = warehouse_account[item_row.get("warehouse")]["account"] + + gl_list.append( + self.get_gl_dict( + { + "account": target_warehouse_account or expense_account, + "against": expense_account, + "cost_center": item_row.cost_center, + "project": item_row.project or self.get("project"), + "remarks": self.get("remarks") or _("Accounting Entry for Stock"), + "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": source_warehouse_account or expense_account, + "against": target_warehouse_account, + "cost_center": item_row.cost_center, + "remarks": self.get("remarks") or _("Accounting Entry for Stock"), + "debit": -1 * 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"): From b531a38efe4b92e07e2e5437c83802c517978771 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 11 Oct 2022 14:53:26 +0530 Subject: [PATCH 06/19] chore: Increase incoming_rate field precision to 6 (cherry picked from commit b31c3bd35db39a1abdbb30aaebc017a833a5a61f) # Conflicts: # erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json --- .../doctype/sales_invoice_item/sales_invoice_item.json | 9 +++++++++ 1 file changed, 9 insertions(+) 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 7bb564b066a..f7d9c2bb161 100644 --- a/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json +++ b/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json @@ -812,6 +812,11 @@ "fieldtype": "Currency", "label": "Incoming Rate (Costing)", "no_copy": 1, +<<<<<<< HEAD +======= + "options": "Company:company:default_currency", + "precision": "6", +>>>>>>> b31c3bd35d (chore: Increase incoming_rate field precision to 6) "print_hide": 1 }, { @@ -841,7 +846,11 @@ "idx": 1, "istable": 1, "links": [], +<<<<<<< HEAD "modified": "2022-08-26 12:06:31.205417", +======= + "modified": "2022-10-10 20:57:38.340026", +>>>>>>> b31c3bd35d (chore: Increase incoming_rate field precision to 6) "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice Item", From 0b48f1387341a089399140190dacc6bc1a4e2fd4 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 11 Oct 2022 14:54:27 +0530 Subject: [PATCH 07/19] test: Internal tranfer precision loss test (cherry picked from commit dc20b21fb5162c7905662c10d954a07b4c30ca54) # Conflicts: # erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py --- .../sales_invoice/test_sales_invoice.py | 247 ++++++++++++++---- erpnext/stock/doctype/item/test_item.py | 7 +- .../test_stock_ledger_entry.py | 18 +- .../stock_reconciliation.py | 4 +- 4 files changed, 212 insertions(+), 64 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index e300e0f35d3..81899c65bd3 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -31,10 +31,20 @@ from erpnext.stock.doctype.stock_entry.test_stock_entry import ( 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 @@ -1687,7 +1697,7 @@ class TestSalesInvoice(unittest.TestCase): 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, ) @@ -1731,7 +1741,7 @@ class TestSalesInvoice(unittest.TestCase): 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", @@ -2369,29 +2379,6 @@ class TestSalesInvoice(unittest.TestCase): 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", @@ -2420,6 +2407,69 @@ class TestSalesInvoice(unittest.TestCase): self.assertEqual(target_doc.company, "_Test Company 1") self.assertEqual(target_doc.supplier, "_Test Internal Supplier") +<<<<<<< HEAD +======= + def test_inter_company_transaction_without_default_warehouse(self): + "Check mapping (expense account) of inter company SI to PI in absence of default warehouse." + # setup + old_negative_stock = frappe.db.get_single_value("Stock Settings", "allow_negative_stock") + frappe.db.set_value("Stock Settings", None, "allow_negative_stock", 1) + + old_perpetual_inventory = erpnext.is_perpetual_inventory_enabled("_Test Company 1") + frappe.local.enable_perpetual_inventory["_Test Company 1"] = 1 + + frappe.db.set_value( + "Company", + "_Test Company 1", + "stock_received_but_not_billed", + "Stock Received But Not Billed - _TC1", + ) + frappe.db.set_value( + "Company", + "_Test Company 1", + "expenses_included_in_valuation", + "Expenses Included In Valuation - _TC1", + ) + + # begin test + si = create_sales_invoice( + company="Wind Power LLC", + customer="_Test Internal Customer", + debit_to="Debtors - WP", + warehouse="Stores - WP", + income_account="Sales - WP", + expense_account="Cost of Goods Sold - WP", + cost_center="Main - WP", + currency="USD", + update_stock=1, + do_not_save=1, + ) + si.selling_price_list = "_Test Price List Rest of the World" + si.submit() + + target_doc = make_inter_company_transaction("Sales Invoice", si.name) + + # in absence of warehouse Stock Received But Not Billed is set as expense account while mapping + # mapping is not obstructed + self.assertIsNone(target_doc.items[0].warehouse) + self.assertEqual(target_doc.items[0].expense_account, "Stock Received But Not Billed - _TC1") + + target_doc.items[0].update({"cost_center": "Main - _TC1"}) + + # missing warehouse is validated on save, after mapping + self.assertRaises(WarehouseMissingError, target_doc.save) + + target_doc.items[0].update({"warehouse": "Stores - _TC1"}) + target_doc.save() + + # after warehouse is set, linked account or default inventory account is set + self.assertEqual(target_doc.items[0].expense_account, "Stock In Hand - _TC1") + + # tear down + frappe.local.enable_perpetual_inventory["_Test Company 1"] = old_perpetual_inventory + frappe.db.set_value("Stock Settings", None, "allow_negative_stock", old_negative_stock) + +>>>>>>> dc20b21fb5 (test: Internal tranfer precision loss test) def test_sle_for_target_warehouse(self): se = make_stock_entry( item_code="138-CMS Shoe", @@ -2451,34 +2501,9 @@ class TestSalesInvoice(unittest.TestCase): 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", @@ -2492,7 +2517,7 @@ class TestSalesInvoice(unittest.TestCase): 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 ) @@ -2548,6 +2573,7 @@ class TestSalesInvoice(unittest.TestCase): check_gl_entries(self, target_doc.name, pi_gl_entries, add_days(nowdate(), -1)) +<<<<<<< HEAD def test_eway_bill_json(self): si = make_sales_invoice_for_ewaybill() @@ -2829,6 +2855,78 @@ class TestSalesInvoice(unittest.TestCase): self.assertEqual(einvoice["ItemList"][1]["Discount"], 0) self.assertEqual(einvoice["ItemList"][1]["UnitPrice"], 20) +======= + 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", + }, + ) + ) +>>>>>>> dc20b21fb5 (test: Internal tranfer precision loss test) def test_item_tax_net_range(self): item = create_item("T Shirt") @@ -3278,7 +3376,7 @@ class TestSalesInvoice(unittest.TestCase): [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 @@ -3809,6 +3907,34 @@ def get_taxes_and_charges(): ] +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( @@ -3831,6 +3957,19 @@ def create_internal_supplier(supplier_name, represents_company, allowed_to_inter 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/stock/doctype/item/test_item.py b/erpnext/stock/doctype/item/test_item.py index 179f7237f96..911c8d0a665 100644 --- a/erpnext/stock/doctype/item/test_item.py +++ b/erpnext/stock/doctype/item/test_item.py @@ -30,7 +30,7 @@ test_ignore = ["BOM"] 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) @@ -54,6 +54,11 @@ def make_item(item_code=None, properties=None): 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/stock_ledger_entry/test_stock_ledger_entry.py b/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py index e810c7933cc..1ec229d2e1f 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 @@ -902,13 +902,15 @@ def create_product_bundle_item(new_item_code, packed_items): 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": @@ -916,7 +918,7 @@ def create_items(): 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 87cc8c6a957..823d0ed0b31 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py @@ -131,7 +131,9 @@ class StockReconciliation(StockController): 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) From b966f107118b99f909d929a4a002f633534b87e5 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 11 Oct 2022 14:55:09 +0530 Subject: [PATCH 08/19] chore: GL Entries for SLE diff (cherry picked from commit df2a0e265b85010d92b1aab50ccccc531b26c8fe) --- erpnext/controllers/stock_controller.py | 29 +++++++++++++++---------- 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index d232cd58f45..7bfb9863558 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -146,7 +146,7 @@ class StockController(AccountsController): if warehouse_account.get(sle.warehouse): # from warehouse account - sle_rounding_diff += flt(sle.stock_value_difference, precision) + sle_rounding_diff += flt(sle.stock_value_difference) self.check_expense_account(item_row) @@ -190,19 +190,24 @@ class StockController(AccountsController): elif sle.warehouse not in warehouse_with_no_account: warehouse_with_no_account.append(sle.warehouse) - if sle_rounding_diff > 0: - expense_account = item_row.get("expense_account") - target_warehouse_account = warehouse_account[item_row.get("target_warehouse")]["account"] - source_warehouse_account = warehouse_account[item_row.get("warehouse")]["account"] + if abs(sle_rounding_diff) > 0.1 and ( + self.get("is_internal_customer") or self.get("is_internal_supplier") + ): + asset_account = "" + if self.get("is_internal_customer"): + asset_account = warehouse_account[item_row.get("target_warehouse")]["account"] + elif self.get("is_internal_supplier"): + asset_account = warehouse_account[item_row.get("from_warehouse")]["account"] + expense_account = item_row.get("expense_account") gl_list.append( self.get_gl_dict( { - "account": target_warehouse_account or expense_account, - "against": expense_account, + "account": expense_account, + "against": asset_account, "cost_center": item_row.cost_center, "project": item_row.project or self.get("project"), - "remarks": self.get("remarks") or _("Accounting Entry for Stock"), + "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", }, @@ -214,11 +219,11 @@ class StockController(AccountsController): gl_list.append( self.get_gl_dict( { - "account": source_warehouse_account or expense_account, - "against": target_warehouse_account, + "account": asset_account, + "against": expense_account, "cost_center": item_row.cost_center, - "remarks": self.get("remarks") or _("Accounting Entry for Stock"), - "debit": -1 * sle_rounding_diff, + "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", }, From 151b9d4d7b4ee47dc7d969a20b2f657ed6adcfa5 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 12 Oct 2022 14:17:55 +0530 Subject: [PATCH 09/19] chore: Increase precision for other doc fields (cherry picked from commit c8d2181498616c8c6757e28255977c66a65a30d9) # Conflicts: # erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json # erpnext/stock/doctype/delivery_note_item/delivery_note_item.json --- .../doctype/purchase_invoice_item/purchase_invoice_item.json | 5 +++++ .../stock/doctype/delivery_note_item/delivery_note_item.json | 5 +++++ .../doctype/purchase_receipt_item/purchase_receipt_item.json | 3 ++- 3 files changed, 12 insertions(+), 1 deletion(-) 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 4b12f9954e8..7775769dacc 100644 --- a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json +++ b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json @@ -706,6 +706,7 @@ "label": "Valuation Rate", "no_copy": 1, "options": "Company:company:default_currency", + "precision": "6", "print_hide": 1, "read_only": 1 }, @@ -871,7 +872,11 @@ "idx": 1, "istable": 1, "links": [], +<<<<<<< HEAD "modified": "2021-11-15 17:04:07.191013", +======= + "modified": "2022-10-12 03:37:29.032732", +>>>>>>> c8d2181498 (chore: Increase precision for other doc fields) "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Invoice Item", 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 2d7abc8a0d6..1e5d03a1cd2 100644 --- a/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json +++ b/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json @@ -746,6 +746,7 @@ "fieldtype": "Currency", "label": "Incoming Rate", "no_copy": 1, + "precision": "6", "print_hide": 1, "read_only": 1 }, @@ -780,7 +781,11 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], +<<<<<<< HEAD "modified": "2022-05-02 12:09:39.610075", +======= + "modified": "2022-10-12 03:36:05.344847", +>>>>>>> c8d2181498 (chore: Increase precision for other doc fields) "modified_by": "Administrator", "module": "Stock", "name": "Delivery Note 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 5a04e7d2eec..3d30533a5e7 100644 --- a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json +++ b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json @@ -738,6 +738,7 @@ "oldfieldname": "valuation_rate", "oldfieldtype": "Currency", "options": "Company:company:default_currency", + "precision": "6", "print_hide": 1, "print_width": "80px", "read_only": 1, @@ -992,7 +993,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", From 7a9e7b66ac5b48a534ff6f6d2f853cb1115a9a96 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 12 Oct 2022 14:19:09 +0530 Subject: [PATCH 10/19] chore: Use proper accounts (cherry picked from commit 1c05c004cd1739ecdf724a87edd2472991c16cfe) --- erpnext/controllers/stock_controller.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index 7bfb9863558..d9b8db01b25 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -190,21 +190,22 @@ class StockController(AccountsController): elif sle.warehouse not in warehouse_with_no_account: warehouse_with_no_account.append(sle.warehouse) - if abs(sle_rounding_diff) > 0.1 and ( + if abs(sle_rounding_diff) < (1.0 / (10**precision)) and ( self.get("is_internal_customer") or self.get("is_internal_supplier") ): - asset_account = "" + warehouse_asset_account = "" if self.get("is_internal_customer"): - asset_account = warehouse_account[item_row.get("target_warehouse")]["account"] + warehouse_asset_account = warehouse_account[item_row.get("target_warehouse")]["account"] elif self.get("is_internal_supplier"): - asset_account = warehouse_account[item_row.get("from_warehouse")]["account"] + warehouse_asset_account = warehouse_account[item_row.get("warehouse")]["account"] + + expense_account = frappe.db.get_value("Company", self.company, "default_expense_account") - expense_account = item_row.get("expense_account") gl_list.append( self.get_gl_dict( { "account": expense_account, - "against": asset_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"), @@ -219,7 +220,7 @@ class StockController(AccountsController): gl_list.append( self.get_gl_dict( { - "account": asset_account, + "account": warehouse_asset_account, "against": expense_account, "cost_center": item_row.cost_center, "remarks": _("Rounding gain/loss Entry for Stock Transfer"), From c67bdcf3c609cf09305a5e70d1e2f3f5a5f6a25d Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 12 Oct 2022 14:57:16 +0530 Subject: [PATCH 11/19] chore: fix precision condition (cherry picked from commit 49601558c6f10beafa64372dc3203645259d9264) --- erpnext/controllers/stock_controller.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index d9b8db01b25..41e641f7a49 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -190,7 +190,7 @@ class StockController(AccountsController): 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 ( + if abs(sle_rounding_diff) > (1.0 / (10**precision)) and ( self.get("is_internal_customer") or self.get("is_internal_supplier") ): warehouse_asset_account = "" From 4a8c42d62b4f67dcae8f11e5f5236832032d27da Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 12 Oct 2022 15:53:28 +0530 Subject: [PATCH 12/19] chore: check only for inter-company transfers (cherry picked from commit 9aa5e20ef7a466713e33a35f35d3219b36358b51) --- erpnext/controllers/stock_controller.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index 41e641f7a49..3a08051b2f3 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -190,9 +190,7 @@ class StockController(AccountsController): 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.get("is_internal_customer") or self.get("is_internal_supplier") - ): + 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"] From cb7aef505dec2f53590bd92fd40e8662687d22b4 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Wed, 12 Oct 2022 16:24:50 +0530 Subject: [PATCH 13/19] fix: conflict --- erpnext/stock/stock_ledger.py | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index a1c14e37924..ce4daa65317 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -1473,16 +1473,7 @@ def _round_off_if_near_zero(number: float, precision: int = 6) -> float: if abs(0.0 - flt(number)) < (1.0 / (10**precision)): return 0.0 -<<<<<<< HEAD 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: """ @@ -1504,4 +1495,3 @@ def get_incoming_rate_for_inter_company_transfer(sle) -> float: ) return rate ->>>>>>> 683a47f7a1 (fix: consider sales rate as incoming rate for transit warehouse in purchase flow) From 6ae2f90683b6c3309bcafbb8e9772770c0264741 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Wed, 12 Oct 2022 16:26:52 +0530 Subject: [PATCH 14/19] fix: conflct in test purchase receipt --- .../stock/doctype/purchase_receipt/test_purchase_receipt.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index 4fdf8a7a3f2..7452fc380aa 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -2,14 +2,9 @@ # License: GNU General Public License v3. See license.txt -<<<<<<< HEAD import json import unittest 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 from frappe.tests.utils import FrappeTestCase, change_settings From 8238b89907a91d5830ed372bbcc609b999a62345 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Wed, 12 Oct 2022 16:28:31 +0530 Subject: [PATCH 15/19] fix: incorrect import --- erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py | 1 - 1 file changed, 1 deletion(-) diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index 7452fc380aa..2457c8e80ab 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -19,7 +19,6 @@ 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 From 3b5889ef85856446370ec0e4c8d9b6042891c7f0 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 12 Oct 2022 16:30:23 +0530 Subject: [PATCH 16/19] chore: resolve conflicts --- .../purchase_invoice_item.json | 4 -- .../sales_invoice/test_sales_invoice.py | 67 +------------------ .../sales_invoice_item.json | 7 -- .../delivery_note_item.json | 4 -- 4 files changed, 1 insertion(+), 81 deletions(-) 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 7775769dacc..c0afa7fe691 100644 --- a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json +++ b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json @@ -872,11 +872,7 @@ "idx": 1, "istable": 1, "links": [], -<<<<<<< HEAD - "modified": "2021-11-15 17:04:07.191013", -======= "modified": "2022-10-12 03:37:29.032732", ->>>>>>> c8d2181498 (chore: Increase precision for other doc fields) "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 81899c65bd3..56323eeaf29 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -2407,69 +2407,6 @@ class TestSalesInvoice(unittest.TestCase): self.assertEqual(target_doc.company, "_Test Company 1") self.assertEqual(target_doc.supplier, "_Test Internal Supplier") -<<<<<<< HEAD -======= - def test_inter_company_transaction_without_default_warehouse(self): - "Check mapping (expense account) of inter company SI to PI in absence of default warehouse." - # setup - old_negative_stock = frappe.db.get_single_value("Stock Settings", "allow_negative_stock") - frappe.db.set_value("Stock Settings", None, "allow_negative_stock", 1) - - old_perpetual_inventory = erpnext.is_perpetual_inventory_enabled("_Test Company 1") - frappe.local.enable_perpetual_inventory["_Test Company 1"] = 1 - - frappe.db.set_value( - "Company", - "_Test Company 1", - "stock_received_but_not_billed", - "Stock Received But Not Billed - _TC1", - ) - frappe.db.set_value( - "Company", - "_Test Company 1", - "expenses_included_in_valuation", - "Expenses Included In Valuation - _TC1", - ) - - # begin test - si = create_sales_invoice( - company="Wind Power LLC", - customer="_Test Internal Customer", - debit_to="Debtors - WP", - warehouse="Stores - WP", - income_account="Sales - WP", - expense_account="Cost of Goods Sold - WP", - cost_center="Main - WP", - currency="USD", - update_stock=1, - do_not_save=1, - ) - si.selling_price_list = "_Test Price List Rest of the World" - si.submit() - - target_doc = make_inter_company_transaction("Sales Invoice", si.name) - - # in absence of warehouse Stock Received But Not Billed is set as expense account while mapping - # mapping is not obstructed - self.assertIsNone(target_doc.items[0].warehouse) - self.assertEqual(target_doc.items[0].expense_account, "Stock Received But Not Billed - _TC1") - - target_doc.items[0].update({"cost_center": "Main - _TC1"}) - - # missing warehouse is validated on save, after mapping - self.assertRaises(WarehouseMissingError, target_doc.save) - - target_doc.items[0].update({"warehouse": "Stores - _TC1"}) - target_doc.save() - - # after warehouse is set, linked account or default inventory account is set - self.assertEqual(target_doc.items[0].expense_account, "Stock In Hand - _TC1") - - # tear down - frappe.local.enable_perpetual_inventory["_Test Company 1"] = old_perpetual_inventory - frappe.db.set_value("Stock Settings", None, "allow_negative_stock", old_negative_stock) - ->>>>>>> dc20b21fb5 (test: Internal tranfer precision loss test) def test_sle_for_target_warehouse(self): se = make_stock_entry( item_code="138-CMS Shoe", @@ -2573,7 +2510,6 @@ class TestSalesInvoice(unittest.TestCase): check_gl_entries(self, target_doc.name, pi_gl_entries, add_days(nowdate(), -1)) -<<<<<<< HEAD def test_eway_bill_json(self): si = make_sales_invoice_for_ewaybill() @@ -2855,7 +2791,7 @@ class TestSalesInvoice(unittest.TestCase): self.assertEqual(einvoice["ItemList"][1]["Discount"], 0) self.assertEqual(einvoice["ItemList"][1]["UnitPrice"], 20) -======= + def test_internal_transfer_gl_precision_issues(self): # Make a stock queue of an item with two valuations @@ -2926,7 +2862,6 @@ class TestSalesInvoice(unittest.TestCase): }, ) ) ->>>>>>> dc20b21fb5 (test: Internal tranfer precision loss test) def test_item_tax_net_range(self): item = create_item("T Shirt") 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 f7d9c2bb161..9b62c6a8f37 100644 --- a/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json +++ b/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json @@ -812,11 +812,8 @@ "fieldtype": "Currency", "label": "Incoming Rate (Costing)", "no_copy": 1, -<<<<<<< HEAD -======= "options": "Company:company:default_currency", "precision": "6", ->>>>>>> b31c3bd35d (chore: Increase incoming_rate field precision to 6) "print_hide": 1 }, { @@ -846,11 +843,7 @@ "idx": 1, "istable": 1, "links": [], -<<<<<<< HEAD - "modified": "2022-08-26 12:06:31.205417", -======= "modified": "2022-10-10 20:57:38.340026", ->>>>>>> b31c3bd35d (chore: Increase incoming_rate field precision to 6) "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice Item", 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 1e5d03a1cd2..fb89aef5b26 100644 --- a/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json +++ b/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json @@ -781,11 +781,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], -<<<<<<< HEAD - "modified": "2022-05-02 12:09:39.610075", -======= "modified": "2022-10-12 03:36:05.344847", ->>>>>>> c8d2181498 (chore: Increase precision for other doc fields) "modified_by": "Administrator", "module": "Stock", "name": "Delivery Note Item", From aa0552d7884a543e3e97235b52a8af222ad4ae0a Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Wed, 12 Oct 2022 16:33:53 +0530 Subject: [PATCH 17/19] fix: linter issue --- erpnext/stock/stock_ledger.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index ce4daa65317..d7c362b4978 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -1475,6 +1475,7 @@ def _round_off_if_near_zero(number: float, precision: int = 6) -> float: return flt(number) + def get_incoming_rate_for_inter_company_transfer(sle) -> float: """ For inter company transfer, incoming rate is the average of the outgoing rate From 4383980bcc2d38564b60bc1163bbd50b9b9b12f6 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 11 Oct 2022 08:14:15 +0530 Subject: [PATCH 18/19] fix: value error on pos submit (cherry picked from commit 4b908ebcd6f01459bbf29db3a0863963dac45fac) --- erpnext/stock/doctype/serial_no/serial_no.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/erpnext/stock/doctype/serial_no/serial_no.py b/erpnext/stock/doctype/serial_no/serial_no.py index cfa5cee453a..76029f0e001 100644 --- a/erpnext/stock/doctype/serial_no/serial_no.py +++ b/erpnext/stock/doctype/serial_no/serial_no.py @@ -864,16 +864,15 @@ def get_pos_reserved_serial_nos(filters): pos_transacted_sr_nos = query.run(as_dict=True) - reserved_sr_nos = [] - returned_sr_nos = [] + reserved_sr_nos = set() + returned_sr_nos = set() for d in pos_transacted_sr_nos: if d.is_return == 0: - reserved_sr_nos += get_serial_nos(d.serial_no) + [reserved_sr_nos.add(x) for x in get_serial_nos(d.serial_no)] elif d.is_return == 1: - returned_sr_nos += get_serial_nos(d.serial_no) + [returned_sr_nos.add(x) for x in get_serial_nos(d.serial_no)] - for sr_no in returned_sr_nos: - reserved_sr_nos.remove(sr_no) + reserved_sr_nos = list(reserved_sr_nos - returned_sr_nos) return reserved_sr_nos From 48efcc82b681e5c1a95e389df8efb809a54c483d Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 11 Oct 2022 11:17:35 +0530 Subject: [PATCH 19/19] test: value error on serial no validation on pos (cherry picked from commit 9e2bd10d032f49ed65896d7456cbc7c176b5570e) --- .../doctype/pos_invoice/test_pos_invoice.py | 61 +++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py index 70f128e0e39..3132fdd259a 100644 --- a/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py +++ b/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py @@ -495,6 +495,67 @@ class TestPOSInvoice(unittest.TestCase): self.assertRaises(frappe.ValidationError, pos.submit) + def test_value_error_on_serial_no_validation(self): + from erpnext.stock.doctype.stock_entry.test_stock_entry import make_serialized_item + + se = make_serialized_item( + company="_Test Company", + target_warehouse="Stores - _TC", + cost_center="Main - _TC", + expense_account="Cost of Goods Sold - _TC", + ) + serial_nos = se.get("items")[0].serial_no + + # make a pos invoice + pos = create_pos_invoice( + company="_Test Company", + debit_to="Debtors - _TC", + account_for_change_amount="Cash - _TC", + warehouse="Stores - _TC", + income_account="Sales - _TC", + expense_account="Cost of Goods Sold - _TC", + cost_center="Main - _TC", + item=se.get("items")[0].item_code, + rate=1000, + qty=1, + do_not_save=1, + ) + pos.get("items")[0].has_serial_no = 1 + pos.get("items")[0].serial_no = serial_nos.split("\n")[0] + pos.set("payments", []) + pos.append( + "payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 1000, "default": 1} + ) + pos = pos.save().submit() + + # make a return + pos_return = make_sales_return(pos.name) + pos_return.paid_amount = pos_return.grand_total + pos_return.save() + pos_return.submit() + + # set docstatus to 2 for pos to trigger this issue + frappe.db.set_value("POS Invoice", pos.name, "docstatus", 2) + + pos2 = create_pos_invoice( + company="_Test Company", + debit_to="Debtors - _TC", + account_for_change_amount="Cash - _TC", + warehouse="Stores - _TC", + income_account="Sales - _TC", + expense_account="Cost of Goods Sold - _TC", + cost_center="Main - _TC", + item=se.get("items")[0].item_code, + rate=1000, + qty=1, + do_not_save=1, + ) + + pos2.get("items")[0].has_serial_no = 1 + pos2.get("items")[0].serial_no = serial_nos.split("\n")[0] + # Value error should not be triggered on validation + pos2.save() + def test_loyalty_points(self): from erpnext.accounts.doctype.loyalty_program.loyalty_program import ( get_loyalty_program_details_with_points,