Enhancement and bug fix in batch (#12753)
* merge shelf like and manufacturing date * setting default manufacturing date as todays date * setting default manufacturing date as todays date * fix * Improvements to batch auto naming (#12496) * refactor: add new function - batch_uses_naming_series use batch_uses_naming_series in autoname method * properly update naming series on delete: - add new functions - get_batch_prefix, get_batch_naming_series_key, get_batch_naming_series - refactor get_name_from_naming_series - add after_delete method * add documentation and rename some functions * PEP 8 compliance * test * added support for jinja template and added validation if expiry date is set or not if item has_expiry_date is set * bug fix, renamed item_code to item as in batch doctype we refer 'item code' by 'item' * added manufacturing date wise sort * added prefix to expiry date and manufacturing date
This commit is contained in:
parent
277935be79
commit
621740efd9
@ -270,13 +270,14 @@ def get_batch_no(doctype, txt, searchfield, start, page_len, filters):
|
|||||||
}
|
}
|
||||||
|
|
||||||
if args.get('warehouse'):
|
if args.get('warehouse'):
|
||||||
batch_nos = frappe.db.sql("""select sle.batch_no, round(sum(sle.actual_qty),2), sle.stock_uom, batch.expiry_date
|
batch_nos = frappe.db.sql("""select sle.batch_no, round(sum(sle.actual_qty),2), sle.stock_uom, concat('MFG-',batch.manufacturing_date), concat('EXP-',batch.expiry_date)
|
||||||
from `tabStock Ledger Entry` sle
|
from `tabStock Ledger Entry` sle
|
||||||
INNER JOIN `tabBatch` batch on sle.batch_no = batch.name
|
INNER JOIN `tabBatch` batch on sle.batch_no = batch.name
|
||||||
where
|
where
|
||||||
sle.item_code = %(item_code)s
|
sle.item_code = %(item_code)s
|
||||||
and sle.warehouse = %(warehouse)s
|
and sle.warehouse = %(warehouse)s
|
||||||
and sle.batch_no like %(txt)s
|
and (sle.batch_no like %(txt)s
|
||||||
|
or batch.manufacturing_date like %(txt)s)
|
||||||
and batch.docstatus < 2
|
and batch.docstatus < 2
|
||||||
{0}
|
{0}
|
||||||
{match_conditions}
|
{match_conditions}
|
||||||
@ -287,9 +288,10 @@ def get_batch_no(doctype, txt, searchfield, start, page_len, filters):
|
|||||||
if batch_nos:
|
if batch_nos:
|
||||||
return batch_nos
|
return batch_nos
|
||||||
else:
|
else:
|
||||||
return frappe.db.sql("""select name, expiry_date from `tabBatch` batch
|
return frappe.db.sql("""select name, concat('MFG-', manufacturing_date), concat('EXP-',expiry_date) from `tabBatch` batch
|
||||||
where item = %(item_code)s
|
where item = %(item_code)s
|
||||||
and name like %(txt)s
|
and (name like %(txt)s
|
||||||
|
or manufacturing_date like %(txt)s)
|
||||||
and docstatus < 2
|
and docstatus < 2
|
||||||
{0}
|
{0}
|
||||||
{match_conditions}
|
{match_conditions}
|
||||||
|
@ -215,7 +215,7 @@ erpnext.SerialNoBatchSelector = Class.extend({
|
|||||||
in_list_view:1,
|
in_list_view:1,
|
||||||
get_query: function() {
|
get_query: function() {
|
||||||
return {
|
return {
|
||||||
filters: {item_code: me.item_code },
|
filters: {item: me.item_code },
|
||||||
query: 'erpnext.controllers.queries.get_batch_numbers'
|
query: 'erpnext.controllers.queries.get_batch_numbers'
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
@ -25,9 +25,17 @@ frappe.ui.form.on('Batch', {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
item: (frm) => {
|
item: (frm) => {
|
||||||
frappe.db.get_value('Item', {name: frm.doc.item}, 'has_expiry_date', (r) => {
|
// frappe.db.get_value('Item', {name: frm.doc.item}, 'has_expiry_date', (r) => {
|
||||||
frm.toggle_reqd('expiry_date', r.has_expiry_date);
|
// frm.toggle_reqd('expiry_date', r.has_expiry_date);
|
||||||
});
|
// });
|
||||||
|
frappe.db.get_value('Item', {name: frm.doc.item}, ['shelf_life_in_days', 'has_expiry_date'], (r) => {
|
||||||
|
if (r.has_expiry_date && r.shelf_life_in_days) {
|
||||||
|
// Calculate expiry date based on shelf_life_in_days
|
||||||
|
frm.set_value('expiry_date', frappe.datetime.add_days(frm.doc.manufacturing_date, r.shelf_life_in_days));
|
||||||
|
}else if(r.has_expiry_date){
|
||||||
|
frm.toggle_reqd('expiry_date', r.has_expiry_date);
|
||||||
|
}
|
||||||
|
})
|
||||||
},
|
},
|
||||||
make_dashboard: (frm) => {
|
make_dashboard: (frm) => {
|
||||||
if(!frm.is_new()) {
|
if(!frm.is_new()) {
|
||||||
|
@ -174,6 +174,7 @@
|
|||||||
"bold": 0,
|
"bold": 0,
|
||||||
"collapsible": 0,
|
"collapsible": 0,
|
||||||
"columns": 0,
|
"columns": 0,
|
||||||
|
"default": "Today",
|
||||||
"fieldname": "manufacturing_date",
|
"fieldname": "manufacturing_date",
|
||||||
"fieldtype": "Date",
|
"fieldtype": "Date",
|
||||||
"hidden": 0,
|
"hidden": 0,
|
||||||
@ -455,7 +456,7 @@
|
|||||||
"issingle": 0,
|
"issingle": 0,
|
||||||
"istable": 0,
|
"istable": 0,
|
||||||
"max_attachments": 5,
|
"max_attachments": 5,
|
||||||
"modified": "2018-01-08 21:55:54.306693",
|
"modified": "2018-01-23 17:41:06.862477",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Stock",
|
"module": "Stock",
|
||||||
"name": "Batch",
|
"name": "Batch",
|
||||||
|
@ -7,24 +7,14 @@ from frappe import _
|
|||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
from frappe.model.naming import make_autoname, revert_series_if_last
|
from frappe.model.naming import make_autoname, revert_series_if_last
|
||||||
from frappe.utils import flt, cint
|
from frappe.utils import flt, cint
|
||||||
|
from frappe.utils.jinja import render_template, validate_template
|
||||||
|
from frappe.utils.data import add_days
|
||||||
|
import json
|
||||||
|
|
||||||
class UnableToSelectBatchError(frappe.ValidationError):
|
class UnableToSelectBatchError(frappe.ValidationError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def get_name_from_naming_series():
|
|
||||||
"""
|
|
||||||
Get a name generated for a Batch from the Batch's naming series.
|
|
||||||
:return: The string that was generated.
|
|
||||||
"""
|
|
||||||
naming_series_prefix = _get_batch_prefix()
|
|
||||||
key = _make_naming_series_key(naming_series_prefix)
|
|
||||||
name = make_autoname(key)
|
|
||||||
|
|
||||||
return name
|
|
||||||
|
|
||||||
|
|
||||||
def get_name_from_hash():
|
def get_name_from_hash():
|
||||||
"""
|
"""
|
||||||
Get a name for a Batch by generating a unique hash.
|
Get a name for a Batch by generating a unique hash.
|
||||||
@ -93,13 +83,67 @@ def get_batch_naming_series():
|
|||||||
return series
|
return series
|
||||||
|
|
||||||
|
|
||||||
|
def batch_uses_naming_series():
|
||||||
|
"""
|
||||||
|
Verify if the Batch is to be named using a naming series
|
||||||
|
:return: bool
|
||||||
|
"""
|
||||||
|
use_naming_series = cint(frappe.db.get_single_value('Stock Settings', 'use_naming_series'))
|
||||||
|
return bool(use_naming_series)
|
||||||
|
|
||||||
|
|
||||||
|
def _get_batch_prefix():
|
||||||
|
"""
|
||||||
|
Get the naming series prefix set in Stock Settings.
|
||||||
|
|
||||||
|
It does not do any sanity checks so make sure to use it after checking if the Batch
|
||||||
|
is set to use naming series.
|
||||||
|
:return: The naming series.
|
||||||
|
"""
|
||||||
|
naming_series_prefix = frappe.db.get_single_value('Stock Settings', 'naming_series_prefix')
|
||||||
|
if not naming_series_prefix:
|
||||||
|
naming_series_prefix = 'BATCH-'
|
||||||
|
|
||||||
|
return naming_series_prefix
|
||||||
|
|
||||||
|
|
||||||
|
def _make_naming_series_key(prefix):
|
||||||
|
"""
|
||||||
|
Make naming series key for a Batch.
|
||||||
|
|
||||||
|
Naming series key is in the format [prefix].[#####]
|
||||||
|
:param prefix: Naming series prefix gotten from Stock Settings
|
||||||
|
:return: The derived key. If no prefix is given, an empty string is returned
|
||||||
|
"""
|
||||||
|
if not unicode(prefix):
|
||||||
|
return ''
|
||||||
|
else:
|
||||||
|
return prefix.upper() + '.#####'
|
||||||
|
|
||||||
|
|
||||||
|
def get_batch_naming_series():
|
||||||
|
"""
|
||||||
|
Get naming series key for a Batch.
|
||||||
|
|
||||||
|
Naming series key is in the format [prefix].[#####]
|
||||||
|
:return: The naming series or empty string if not available
|
||||||
|
"""
|
||||||
|
series = ''
|
||||||
|
if batch_uses_naming_series():
|
||||||
|
prefix = _get_batch_prefix()
|
||||||
|
key = _make_naming_series_key(prefix)
|
||||||
|
series = key
|
||||||
|
|
||||||
|
return series
|
||||||
|
|
||||||
|
|
||||||
class Batch(Document):
|
class Batch(Document):
|
||||||
def autoname(self):
|
def autoname(self):
|
||||||
"""Generate random ID for batch if not specified"""
|
"""Generate random ID for batch if not specified"""
|
||||||
if not self.batch_id:
|
if not self.batch_id:
|
||||||
if frappe.db.get_value('Item', self.item, 'create_new_batch'):
|
if frappe.db.get_value('Item', self.item, 'create_new_batch'):
|
||||||
if batch_uses_naming_series():
|
if batch_uses_naming_series():
|
||||||
self.batch_id = get_name_from_naming_series()
|
self.batch_id = self.get_name_from_naming_series()
|
||||||
else:
|
else:
|
||||||
self.batch_id = get_name_from_hash()
|
self.batch_id = get_name_from_hash()
|
||||||
else:
|
else:
|
||||||
@ -120,6 +164,28 @@ class Batch(Document):
|
|||||||
if frappe.db.get_value("Item", self.item, "has_batch_no") == 0:
|
if frappe.db.get_value("Item", self.item, "has_batch_no") == 0:
|
||||||
frappe.throw(_("The selected item cannot have Batch"))
|
frappe.throw(_("The selected item cannot have Batch"))
|
||||||
|
|
||||||
|
def before_save(self):
|
||||||
|
has_expiry_date, shelf_life_in_days = frappe.db.get_value('Item', self.item, ['has_expiry_date', 'shelf_life_in_days'])
|
||||||
|
if not self.expiry_date and has_expiry_date and shelf_life_in_days:
|
||||||
|
self.expiry_date = add_days(self.manufacturing_date, shelf_life_in_days)
|
||||||
|
|
||||||
|
if has_expiry_date and not self.expiry_date:
|
||||||
|
frappe.throw('Expiry date is mandatory for selected item')
|
||||||
|
frappe.msgprint('Set items shelf life in days, to set expiry based on manufacturing_date plus self life ')
|
||||||
|
|
||||||
|
def get_name_from_naming_series(self):
|
||||||
|
"""
|
||||||
|
Get a name generated for a Batch from the Batch's naming series.
|
||||||
|
:return: The string that was generated.
|
||||||
|
"""
|
||||||
|
naming_series_prefix = _get_batch_prefix()
|
||||||
|
# validate_template(naming_series_prefix)
|
||||||
|
naming_series_prefix = render_template(str(naming_series_prefix), self.__dict__)
|
||||||
|
key = _make_naming_series_key(naming_series_prefix)
|
||||||
|
name = make_autoname(key)
|
||||||
|
|
||||||
|
return name
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_batch_qty(batch_no=None, warehouse=None, item_code=None):
|
def get_batch_qty(batch_no=None, warehouse=None, item_code=None):
|
||||||
@ -206,6 +272,7 @@ def set_batch_nos(doc, warehouse_field, throw=False):
|
|||||||
frappe.throw(_("Row #{0}: The batch {1} has only {2} qty. Please select another batch which has {3} qty available or split the row into multiple rows, to deliver/issue from multiple batches").format(d.idx, d.batch_no, batch_qty, qty))
|
frappe.throw(_("Row #{0}: The batch {1} has only {2} qty. Please select another batch which has {3} qty available or split the row into multiple rows, to deliver/issue from multiple batches").format(d.idx, d.batch_no, batch_qty, qty))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_batch_no(item_code, warehouse, qty=1, throw=False):
|
def get_batch_no(item_code, warehouse, qty=1, throw=False):
|
||||||
"""
|
"""
|
||||||
|
Loading…
x
Reference in New Issue
Block a user