From 8963edae5120cb4c63026706c6d7e045aabca9f3 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Thu, 10 Dec 2020 22:09:06 +0530 Subject: [PATCH] fix: Move item price link to batch --- erpnext/stock/doctype/batch/batch.js | 9 ++ erpnext/stock/doctype/batch/batch.json | 89 +++++++++++++++---- erpnext/stock/doctype/batch/batch.py | 13 ++- erpnext/stock/doctype/batch/test_batch.py | 26 +++--- .../stock/doctype/item_price/item_price.json | 19 ++-- .../stock/doctype/item_price/item_price.py | 16 ++-- erpnext/stock/get_item_details.py | 29 +++++- 7 files changed, 145 insertions(+), 56 deletions(-) diff --git a/erpnext/stock/doctype/batch/batch.js b/erpnext/stock/doctype/batch/batch.js index e2ea7f992ce..599ecea6849 100644 --- a/erpnext/stock/doctype/batch/batch.js +++ b/erpnext/stock/doctype/batch/batch.js @@ -12,6 +12,15 @@ frappe.ui.form.on('Batch', { } } } + + frm.set_query('selling_price', function() { + return { + filters: { + 'item_code': frm.doc.item, + 'selling': 1 + } + } + }) }, refresh: (frm) => { if(!frm.is_new()) { diff --git a/erpnext/stock/doctype/batch/batch.json b/erpnext/stock/doctype/batch/batch.json index 943cb3401ff..b6a61a58f72 100644 --- a/erpnext/stock/doctype/batch/batch.json +++ b/erpnext/stock/doctype/batch/batch.json @@ -1,4 +1,5 @@ { + "actions": [], "allow_import": 1, "autoname": "field:batch_id", "creation": "2013-03-05 14:50:38", @@ -19,6 +20,7 @@ "batch_qty", "stock_uom", "expiry_date", + "selling_price", "source", "supplier", "column_break_9", @@ -32,7 +34,9 @@ "default": "0", "fieldname": "disabled", "fieldtype": "Check", - "label": "Disabled" + "label": "Disabled", + "show_days": 1, + "show_seconds": 1 }, { "depends_on": "eval:doc.__islocal", @@ -44,6 +48,8 @@ "oldfieldname": "batch_id", "oldfieldtype": "Data", "reqd": 1, + "show_days": 1, + "show_seconds": 1, "unique": 1 }, { @@ -54,13 +60,17 @@ "oldfieldname": "item", "oldfieldtype": "Link", "options": "Item", - "reqd": 1 + "reqd": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "image", "fieldtype": "Attach Image", "hidden": 1, - "label": "image" + "label": "image", + "show_days": 1, + "show_seconds": 1 }, { "depends_on": "eval:doc.parent_batch", @@ -68,58 +78,78 @@ "fieldtype": "Link", "label": "Parent Batch", "options": "Batch", - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "default": "Today", "fieldname": "manufacturing_date", "fieldtype": "Date", - "label": "Manufacturing Date" + "label": "Manufacturing Date", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "column_break_3", - "fieldtype": "Column Break" + "fieldtype": "Column Break", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "expiry_date", "fieldtype": "Date", "label": "Expiry Date", "oldfieldname": "expiry_date", - "oldfieldtype": "Date" + "oldfieldtype": "Date", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "source", "fieldtype": "Section Break", - "label": "Source" + "label": "Source", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "supplier", "fieldtype": "Link", "label": "Supplier", "options": "Supplier", - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "column_break_9", - "fieldtype": "Column Break" + "fieldtype": "Column Break", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "reference_doctype", "fieldtype": "Link", "label": "Source Document Type", "options": "DocType", - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "reference_name", "fieldtype": "Dynamic Link", "label": "Source Document Name", "options": "reference_doctype", - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "section_break_7", - "fieldtype": "Section Break" + "fieldtype": "Section Break", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "description", @@ -127,16 +157,22 @@ "label": "Batch Description", "oldfieldname": "description", "oldfieldtype": "Small Text", + "show_days": 1, + "show_seconds": 1, "width": "300px" }, { "fieldname": "sb_disabled", - "fieldtype": "Section Break" + "fieldtype": "Section Break", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "sb_batch", "fieldtype": "Section Break", - "label": "Batch Details" + "label": "Batch Details", + "show_days": 1, + "show_seconds": 1 }, { "fetch_from": "item.item_name", @@ -144,14 +180,18 @@ "fieldtype": "Data", "in_list_view": 1, "label": "Item Name", - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "batch_qty", "fieldtype": "Float", "in_list_view": 1, "label": "Batch Quantity", - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fetch_from": "item.stock_uom", @@ -159,14 +199,25 @@ "fieldtype": "Link", "label": "Batch UOM", "options": "UOM", - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 + }, + { + "fieldname": "selling_price", + "fieldtype": "Link", + "label": "Selling Price", + "options": "Item Price", + "show_days": 1, + "show_seconds": 1 } ], "icon": "fa fa-archive", "idx": 1, "image_field": "image", + "links": [], "max_attachments": 5, - "modified": "2020-09-18 17:26:09.703215", + "modified": "2020-12-09 19:57:46.592638", "modified_by": "Administrator", "module": "Stock", "name": "Batch", diff --git a/erpnext/stock/doctype/batch/batch.py b/erpnext/stock/doctype/batch/batch.py index c8424f13e12..9f45b4473bb 100644 --- a/erpnext/stock/doctype/batch/batch.py +++ b/erpnext/stock/doctype/batch/batch.py @@ -106,6 +106,10 @@ class Batch(Document): def onload(self): self.image = frappe.db.get_value('Item', self.item, 'image') + def before_insert(self): + if not self.selling_price: + self.selling_price = get_item_price(self.item) + def after_delete(self): revert_series_if_last(get_batch_naming_series(), self.name) @@ -308,4 +312,11 @@ def validate_serial_no_with_batch(serial_nos, item_code): message = "Serial Nos" if len(serial_nos) > 1 else "Serial No" frappe.throw(_("There is no batch found against the {0}: {1}") - .format(message, serial_no_link)) \ No newline at end of file + .format(message, serial_no_link)) + +def get_item_price(item_code): + item_price = frappe.get_all('Item Price', fields=['name'], + filters={'item_code': item_code, 'selling': 1}, order_by='valid_from desc, uom desc') + + if item_price: + return item_price[0].name \ No newline at end of file diff --git a/erpnext/stock/doctype/batch/test_batch.py b/erpnext/stock/doctype/batch/test_batch.py index 97f85bafd95..9da0a00885e 100644 --- a/erpnext/stock/doctype/batch/test_batch.py +++ b/erpnext/stock/doctype/batch/test_batch.py @@ -7,7 +7,7 @@ from frappe.exceptions import ValidationError import unittest from erpnext.stock.doctype.batch.batch import get_batch_qty, UnableToSelectBatchError, get_batch_no -from frappe.utils import cint, flt +from frappe.utils import cint, flt, getdate from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice from erpnext.stock.get_item_details import get_item_details @@ -265,9 +265,9 @@ class TestBatch(unittest.TestCase): 'create_new_batch': 1 }).insert(ignore_permissions=True) - batch1 = create_batch('_Test Batch Price Item', 200, 1) - batch2 = create_batch('_Test Batch Price Item', 300, 1) - batch3 = create_batch('_Test Batch Price Item', 400, 0) + batch1 = create_batch('_Test Batch Price Item', 200) + batch2 = create_batch('_Test Batch Price Item', 300) + batch3 = create_batch('_Test Batch Price Item', 400) args = frappe._dict({ "item_code": "_Test Batch Price Item", @@ -275,6 +275,8 @@ class TestBatch(unittest.TestCase): "price_list": "_Test Price List", "currency": "_Test Currency", "doctype": "Sales Invoice", + "posting_date": getdate(), + "qty": 1, "conversion_rate": 1, "price_list_currency": "_Test Currency", "plc_conversion_rate": 1, @@ -297,29 +299,27 @@ class TestBatch(unittest.TestCase): details = get_item_details(args) self.assertEqual(details.get('price_list_rate'), 400) -def create_batch(item_code, rate, create_item_price_for_batch): +def create_batch(item_code, rate): pi = make_purchase_invoice(company="_Test Company with perpetual inventory", warehouse= "Stores - TCP1", cost_center = "Main - TCP1", update_stock=1, expense_account ="_Test Account Cost for Goods Sold - TCP1", item_code=item_code) + item_price = create_item_price_for_batch(item_code, rate) batch = frappe.db.get_value('Batch', {'item': item_code, 'reference_name': pi.name}) - - if not create_item_price_for_batch: - create_price_list_for_batch(item_code, None, rate) - else: - create_price_list_for_batch(item_code, batch, rate) + frappe.db.set_value('Batch', batch, 'selling_price', item_price) return batch -def create_price_list_for_batch(item_code, batch, rate): - frappe.get_doc({ +def create_item_price_for_batch(item_code, rate): + item_price = frappe.get_doc({ 'doctype': 'Item Price', 'item_code': '_Test Batch Price Item', 'price_list': '_Test Price List', - 'batch_no': batch, 'price_list_rate': rate }).insert() + return item_price.name + def make_new_batch(**args): args = frappe._dict(args) diff --git a/erpnext/stock/doctype/item_price/item_price.json b/erpnext/stock/doctype/item_price/item_price.json index 83177b372ad..37dd32d7843 100644 --- a/erpnext/stock/doctype/item_price/item_price.json +++ b/erpnext/stock/doctype/item_price/item_price.json @@ -18,7 +18,6 @@ "price_list", "customer", "supplier", - "batch_no", "column_break_3", "buying", "selling", @@ -256,21 +255,18 @@ "label": "Reference", "show_days": 1, "show_seconds": 1 - }, - { - "fieldname": "batch_no", - "fieldtype": "Link", - "label": "Batch No", - "options": "Batch", - "show_days": 1, - "show_seconds": 1 } ], "icon": "fa fa-flag", "idx": 1, "index_web_pages_for_search": 1, - "links": [], - "modified": "2020-12-08 18:12:15.395772", + "links": [ + { + "link_doctype": "Batch", + "link_fieldname": "selling_price" + } + ], + "modified": "2020-12-10 22:05:35.481386", "modified_by": "Administrator", "module": "Stock", "name": "Item Price", @@ -305,6 +301,7 @@ } ], "quick_entry": 1, + "search_fields": "item_name,price_list,price_list_rate", "sort_field": "modified", "sort_order": "ASC", "title_field": "item_name", diff --git a/erpnext/stock/doctype/item_price/item_price.py b/erpnext/stock/doctype/item_price/item_price.py index e82a19b0dc0..0441b28223d 100644 --- a/erpnext/stock/doctype/item_price/item_price.py +++ b/erpnext/stock/doctype/item_price/item_price.py @@ -54,22 +54,22 @@ class ItemPrice(Document): "valid_upto", "packing_unit", "customer", - "supplier", - "batch_no"]: + "supplier"]: if self.get(field): conditions += " and {0} = %({0})s ".format(field) else: conditions += "and (isnull({0}) or {0} = '')".format(field) - price_list_rate = frappe.db.sql(""" - select price_list_rate + price_list_rates = frappe.db.sql(""" + select name, price_list_rate from `tabItem Price` {conditions} - """.format(conditions=conditions), - self.as_dict(),) + """.format(conditions=conditions), self.as_dict(), as_dict=1) - if price_list_rate: - frappe.throw(_("Item Price appears multiple times based on Price List, Supplier/Customer, Currency, Item, Batch, UOM, Qty, and Dates."), ItemPriceDuplicateItem,) + if price_list_rates: + for item_price in price_list_rates: + if not frappe.get_value('Batch', {'selling_price': item_price.name}): + frappe.throw(_("Item Price appears multiple times based on Price List, Supplier/Customer, Currency, Item, UOM, Qty, and Dates."), ItemPriceDuplicateItem,) def before_save(self): if self.selling: diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index 2d2abd71aa1..867ca56b875 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -666,14 +666,25 @@ def get_item_price(args, item_code, ignore_party=False): :param item_code: str, Item Doctype field item_code """ + selling_doctypes = ['Sales Invoice', 'Delivery Note', 'Sales Invoice Item', + 'Delivery Note Item'] + + # check for selling price in batch + if args.get('doctype') in selling_doctypes and args.get('batch_no'): + batch_selling_price = frappe.get_value('Batch', args.get('batch_no'), 'selling_price') + if batch_selling_price: + item_price = frappe.get_value('Item Price', batch_selling_price, ['name', 'price_list_rate', 'uom', + 'valid_from', 'valid_upto'], as_dict=1) + + if is_valid_item_price(item_price, args.get('posting_date')): + return [[item_price.name, item_price.price_list_rate, item_price.uom]] + args['item_code'] = item_code conditions = """where item_code=%(item_code)s and price_list=%(price_list)s and ifnull(uom, '') in ('', %(uom)s)""" - conditions += "and ifnull(batch_no, '') in ('', %(batch_no)s)" - if not ignore_party: if args.get("customer"): conditions += " and customer=%(customer)s" @@ -692,7 +703,16 @@ def get_item_price(args, item_code, ignore_party=False): return frappe.db.sql(""" select name, price_list_rate, uom from `tabItem Price` {conditions} - order by valid_from desc, batch_no desc, uom desc """.format(conditions=conditions), args) + order by valid_from desc, uom desc """.format(conditions=conditions), args) + +def is_valid_item_price(item_price, posting_date): + if item_price.valid_upto and getdate(posting_date) <= getdate(valid_upto): + return True + + if getdate(posting_date) >= getdate(item_price.valid_from): + return True + + return False def get_price_list_rate_for(args, item_code): """ @@ -711,7 +731,8 @@ def get_price_list_rate_for(args, item_code): "uom": args.get('uom'), "transaction_date": args.get('transaction_date'), "posting_date": args.get('posting_date'), - "batch_no": args.get('batch_no') + "batch_no": args.get('batch_no'), + "doctype": args.get('doctype') } item_price_data = 0