Merge pull request #24087 from deepeshgarg007/batch-wise-pricing

feat: Batch wise item selling pricing
This commit is contained in:
rohitwaghchaure 2020-12-28 14:37:33 +05:30 committed by GitHub
commit 72e8ec5eee
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 179 additions and 31 deletions

View File

@ -195,6 +195,10 @@ erpnext.buying.BuyingController = erpnext.TransactionController.extend({
this._super(doc, cdt, cdn);
},
batch_no: function(doc, cdt, cdn) {
this._super(doc, cdt, cdn);
},
received_qty: function(doc, cdt, cdn) {
this.calculate_accepted_qty(doc, cdt, cdn)
},

View File

@ -1104,6 +1104,11 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
}
},
batch_no: function(doc, cdt, cdn) {
let item = frappe.get_doc(cdt, cdn);
this.apply_price_list(item, true);
},
toggle_conversion_factor: function(item) {
// toggle read only property for conversion factor field if the uom and stock uom are same
if(this.frm.get_field('items').grid.fields_map.conversion_factor) {
@ -1408,6 +1413,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
"pricing_rules": d.pricing_rules,
"warehouse": d.warehouse,
"serial_no": d.serial_no,
"batch_no": d.batch_no,
"price_list_rate": d.price_list_rate,
"conversion_factor": d.conversion_factor || 1.0
});

View File

@ -399,6 +399,10 @@ erpnext.selling.SellingController = erpnext.TransactionController.extend({
}
},
batch_no: function(doc, cdt, cdn) {
this._super(doc, cdt, cdn);
},
qty: function(doc, cdt, cdn) {
this._super(doc, cdt, cdn);

View File

@ -8,6 +8,8 @@ import unittest
from erpnext.stock.doctype.batch.batch import get_batch_qty, UnableToSelectBatchError, get_batch_no
from frappe.utils import cint, flt
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
from erpnext.stock.get_item_details import get_item_details
class TestBatch(unittest.TestCase):
def test_item_has_batch_enabled(self):
@ -182,7 +184,7 @@ class TestBatch(unittest.TestCase):
stock_entry.cancel()
current_batch_qty = flt(frappe.db.get_value("Batch", "B100", "batch_qty"))
self.assertEqual(current_batch_qty, existing_batch_qty)
@classmethod
def make_new_batch_and_entry(cls, item_name, batch_name, warehouse):
'''Make a new stock entry for given target warehouse and batch name of item'''
@ -252,6 +254,72 @@ class TestBatch(unittest.TestCase):
return batch
def test_batch_wise_item_price(self):
if not frappe.db.get_value('Item', '_Test Batch Price Item'):
frappe.get_doc({
'doctype': 'Item',
'is_stock_item': 1,
'item_code': '_Test Batch Price Item',
'item_group': 'Products',
'has_batch_no': 1,
'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)
args = frappe._dict({
"item_code": "_Test Batch Price Item",
"company": "_Test Company with perpetual inventory",
"price_list": "_Test Price List",
"currency": "_Test Currency",
"doctype": "Sales Invoice",
"conversion_rate": 1,
"price_list_currency": "_Test Currency",
"plc_conversion_rate": 1,
"customer": "_Test Customer",
"name": None
})
#test price for batch1
args.update({'batch_no': batch1})
details = get_item_details(args)
self.assertEqual(details.get('price_list_rate'), 200)
#test price for batch2
args.update({'batch_no': batch2})
details = get_item_details(args)
self.assertEqual(details.get('price_list_rate'), 300)
#test price for batch3
args.update({'batch_no': batch3})
details = get_item_details(args)
self.assertEqual(details.get('price_list_rate'), 400)
def create_batch(item_code, rate, create_item_price_for_batch):
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)
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)
return batch
def create_price_list_for_batch(item_code, batch, rate):
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()
def make_new_batch(**args):
args = frappe._dict(args)

View File

@ -18,6 +18,7 @@
"price_list",
"customer",
"supplier",
"batch_no",
"column_break_3",
"buying",
"selling",
@ -47,31 +48,41 @@
"oldfieldtype": "Select",
"options": "Item",
"reqd": 1,
"search_index": 1
"search_index": 1,
"show_days": 1,
"show_seconds": 1
},
{
"fieldname": "uom",
"fieldtype": "Link",
"label": "UOM",
"options": "UOM"
"options": "UOM",
"show_days": 1,
"show_seconds": 1
},
{
"default": "0",
"description": "Quantity that must be bought or sold per UOM",
"fieldname": "packing_unit",
"fieldtype": "Int",
"label": "Packing Unit"
"label": "Packing Unit",
"show_days": 1,
"show_seconds": 1
},
{
"fieldname": "column_break_17",
"fieldtype": "Column Break"
"fieldtype": "Column Break",
"show_days": 1,
"show_seconds": 1
},
{
"fieldname": "item_name",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Item Name",
"read_only": 1
"read_only": 1,
"show_days": 1,
"show_seconds": 1
},
{
"fetch_from": "item_code.brand",
@ -79,19 +90,25 @@
"fieldtype": "Read Only",
"in_list_view": 1,
"label": "Brand",
"read_only": 1
"read_only": 1,
"show_days": 1,
"show_seconds": 1
},
{
"fieldname": "item_description",
"fieldtype": "Text",
"label": "Item Description",
"read_only": 1
"read_only": 1,
"show_days": 1,
"show_seconds": 1
},
{
"fieldname": "price_list_details",
"fieldtype": "Section Break",
"label": "Price List",
"options": "fa fa-tags"
"options": "fa fa-tags",
"show_days": 1,
"show_seconds": 1
},
{
"fieldname": "price_list",
@ -100,7 +117,9 @@
"in_standard_filter": 1,
"label": "Price List",
"options": "Price List",
"reqd": 1
"reqd": 1,
"show_days": 1,
"show_seconds": 1
},
{
"bold": 1,
@ -108,37 +127,49 @@
"fieldname": "customer",
"fieldtype": "Link",
"label": "Customer",
"options": "Customer"
"options": "Customer",
"show_days": 1,
"show_seconds": 1
},
{
"depends_on": "eval:doc.buying == 1",
"fieldname": "supplier",
"fieldtype": "Link",
"label": "Supplier",
"options": "Supplier"
"options": "Supplier",
"show_days": 1,
"show_seconds": 1
},
{
"fieldname": "column_break_3",
"fieldtype": "Column Break"
"fieldtype": "Column Break",
"show_days": 1,
"show_seconds": 1
},
{
"default": "0",
"fieldname": "buying",
"fieldtype": "Check",
"label": "Buying",
"read_only": 1
"read_only": 1,
"show_days": 1,
"show_seconds": 1
},
{
"default": "0",
"fieldname": "selling",
"fieldtype": "Check",
"label": "Selling",
"read_only": 1
"read_only": 1,
"show_days": 1,
"show_seconds": 1
},
{
"fieldname": "item_details",
"fieldtype": "Section Break",
"options": "fa fa-tag"
"options": "fa fa-tag",
"show_days": 1,
"show_seconds": 1
},
{
"bold": 1,
@ -146,11 +177,15 @@
"fieldtype": "Link",
"label": "Currency",
"options": "Currency",
"read_only": 1
"read_only": 1,
"show_days": 1,
"show_seconds": 1
},
{
"fieldname": "col_br_1",
"fieldtype": "Column Break"
"fieldtype": "Column Break",
"show_days": 1,
"show_seconds": 1
},
{
"fieldname": "price_list_rate",
@ -162,53 +197,80 @@
"oldfieldname": "ref_rate",
"oldfieldtype": "Currency",
"options": "currency",
"reqd": 1
"reqd": 1,
"show_days": 1,
"show_seconds": 1
},
{
"fieldname": "section_break_15",
"fieldtype": "Section Break"
"fieldtype": "Section Break",
"show_days": 1,
"show_seconds": 1
},
{
"default": "Today",
"fieldname": "valid_from",
"fieldtype": "Date",
"label": "Valid From"
"label": "Valid From",
"show_days": 1,
"show_seconds": 1
},
{
"default": "0",
"fieldname": "lead_time_days",
"fieldtype": "Int",
"label": "Lead Time in days"
"label": "Lead Time in days",
"show_days": 1,
"show_seconds": 1
},
{
"fieldname": "column_break_18",
"fieldtype": "Column Break"
"fieldtype": "Column Break",
"show_days": 1,
"show_seconds": 1
},
{
"fieldname": "valid_upto",
"fieldtype": "Date",
"label": "Valid Upto"
"label": "Valid Upto",
"show_days": 1,
"show_seconds": 1
},
{
"fieldname": "section_break_24",
"fieldtype": "Section Break"
"fieldtype": "Section Break",
"show_days": 1,
"show_seconds": 1
},
{
"fieldname": "note",
"fieldtype": "Text",
"label": "Note"
"label": "Note",
"show_days": 1,
"show_seconds": 1
},
{
"fieldname": "reference",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Reference"
"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-07-06 22:31:32.943475",
"modified": "2020-12-08 18:12:15.395772",
"modified_by": "Administrator",
"module": "Stock",
"name": "Item Price",

View File

@ -54,7 +54,8 @@ class ItemPrice(Document):
"valid_upto",
"packing_unit",
"customer",
"supplier",]:
"supplier",
"batch_no"]:
if self.get(field):
conditions += " and {0} = %({0})s ".format(field)
else:
@ -68,7 +69,7 @@ class ItemPrice(Document):
self.as_dict(),)
if price_list_rate:
frappe.throw(_("Item Price appears multiple times based on Price List, Supplier/Customer, Currency, Item, UOM, Qty, and Dates."), ItemPriceDuplicateItem,)
frappe.throw(_("Item Price appears multiple times based on Price List, Supplier/Customer, Currency, Item, Batch, UOM, Qty, and Dates."), ItemPriceDuplicateItem,)
def before_save(self):
if self.selling:

View File

@ -672,6 +672,8 @@ def get_item_price(args, item_code, ignore_party=False):
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"
@ -690,7 +692,7 @@ 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, uom desc """.format(conditions=conditions), args)
order by valid_from desc, batch_no desc, uom desc """.format(conditions=conditions), args)
def get_price_list_rate_for(args, item_code):
"""
@ -709,6 +711,7 @@ 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')
}
item_price_data = 0