[Serial No] Major updates, code cleanup, "In Store" is now "Available". Serial No can only be created via Stock Entry / Purchase Receipt. Serial No can be auto created using Series if mentioned in Item master

This commit is contained in:
Rushabh Mehta 2013-08-14 18:37:28 +05:30
parent ffe64db8f7
commit 62030e05cc
30 changed files with 686 additions and 656 deletions

View File

@ -181,7 +181,12 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte
set_dynamic_labels: function() { set_dynamic_labels: function() {
this._super(); this._super();
this.hide_fields(this.frm.doc); this.hide_fields(this.frm.doc);
},
entries_on_form_rendered: function(doc, grid_row) {
erpnext.setup_serial_no(grid_row)
} }
}); });
// for backward compatibility: combine new and previous states // for backward compatibility: combine new and previous states

View File

@ -65,9 +65,6 @@ class DocType(SellingController):
self.validate_write_off_account() self.validate_write_off_account()
if cint(self.doc.update_stock): if cint(self.doc.update_stock):
sl = get_obj('Stock Ledger')
sl.validate_serial_no(self, 'entries')
sl.validate_serial_no(self, 'packing_details')
self.validate_item_code() self.validate_item_code()
self.update_current_stock() self.update_current_stock()
self.validate_delivery_note() self.validate_delivery_note()
@ -84,15 +81,9 @@ class DocType(SellingController):
"delivery_note_details") "delivery_note_details")
def on_submit(self): def on_submit(self):
if cint(self.doc.update_stock) == 1: if cint(self.doc.update_stock) == 1:
sl_obj = get_obj("Stock Ledger")
sl_obj.validate_serial_no_warehouse(self, 'entries')
sl_obj.validate_serial_no_warehouse(self, 'packing_details')
sl_obj.update_serial_record(self, 'entries', is_submit = 1, is_incoming = 0)
sl_obj.update_serial_record(self, 'packing_details', is_submit = 1, is_incoming = 0)
self.update_stock_ledger(update_stock=1) self.update_stock_ledger(update_stock=1)
self.update_serial_nos()
else: else:
# Check for Approving Authority # Check for Approving Authority
if not self.doc.recurring_id: if not self.doc.recurring_id:
@ -120,11 +111,8 @@ class DocType(SellingController):
def on_cancel(self): def on_cancel(self):
if cint(self.doc.update_stock) == 1: if cint(self.doc.update_stock) == 1:
sl = get_obj('Stock Ledger')
sl.update_serial_record(self, 'entries', is_submit = 0, is_incoming = 0)
sl.update_serial_record(self, 'packing_details', is_submit = 0, is_incoming = 0)
self.update_stock_ledger(update_stock = -1) self.update_stock_ledger(update_stock = -1)
self.update_serial_nos(cancel = True)
sales_com_obj = get_obj(dt = 'Sales Common') sales_com_obj = get_obj(dt = 'Sales Common')
sales_com_obj.check_stop_sales_order(self) sales_com_obj.check_stop_sales_order(self)
@ -489,10 +477,6 @@ class DocType(SellingController):
def make_packing_list(self): def make_packing_list(self):
get_obj('Sales Common').make_packing_list(self,'entries') get_obj('Sales Common').make_packing_list(self,'entries')
sl = get_obj('Stock Ledger')
sl.scrub_serial_nos(self)
sl.scrub_serial_nos(self, 'packing_details')
def on_update(self): def on_update(self):
if cint(self.doc.update_stock) == 1: if cint(self.doc.update_stock) == 1:

View File

@ -644,7 +644,61 @@ class TestSalesInvoice(unittest.TestCase):
count = no_of_months == 12 and 3 or 13 count = no_of_months == 12 and 3 or 13
for i in xrange(count): for i in xrange(count):
base_si = _test(i) base_si = _test(i)
def test_serialized(self):
from stock.doctype.stock_entry.test_stock_entry import make_serialized_item
from stock.doctype.stock_ledger_entry.stock_ledger_entry import get_serial_nos
se = make_serialized_item()
serial_nos = get_serial_nos(se.doclist[1].serial_no)
si = webnotes.bean(copy=test_records[0])
si.doc.update_stock = 1
si.doclist[1].item_code = "_Test Serialized Item With Series"
si.doclist[1].qty = 1
si.doclist[1].serial_no = serial_nos[0]
si.insert()
si.submit()
self.assertEquals(webnotes.conn.get_value("Serial No", serial_nos[0], "status"), "Delivered")
self.assertFalse(webnotes.conn.get_value("Serial No", serial_nos[0], "warehouse"))
self.assertEquals(webnotes.conn.get_value("Serial No", serial_nos[0],
"delivery_document_no"), si.doc.name)
return si
def test_serialized_cancel(self):
from stock.doctype.stock_ledger_entry.stock_ledger_entry import get_serial_nos
si = self.test_serialized()
si.cancel()
serial_nos = get_serial_nos(si.doclist[1].serial_no)
self.assertEquals(webnotes.conn.get_value("Serial No", serial_nos[0], "status"), "Available")
self.assertEquals(webnotes.conn.get_value("Serial No", serial_nos[0], "warehouse"), "_Test Warehouse - _TC")
self.assertFalse(webnotes.conn.get_value("Serial No", serial_nos[0],
"delivery_document_no"))
def test_serialize_status(self):
from stock.doctype.stock_ledger_entry.stock_ledger_entry import SerialNoStatusError, get_serial_nos
from stock.doctype.stock_entry.test_stock_entry import make_serialized_item
se = make_serialized_item()
serial_nos = get_serial_nos(se.doclist[1].serial_no)
sr = webnotes.bean("Serial No", serial_nos[0])
sr.doc.status = "Not Available"
sr.save()
si = webnotes.bean(copy=test_records[0])
si.doc.update_stock = 1
si.doclist[1].item_code = "_Test Serialized Item With Series"
si.doclist[1].qty = 1
si.doclist[1].serial_no = serial_nos[0]
si.insert()
self.assertRaises(SerialNoStatusError, si.submit)
test_dependencies = ["Journal Voucher", "POS Setting", "Contact", "Address"] test_dependencies = ["Journal Voucher", "POS Setting", "Contact", "Address"]
test_records = [ test_records = [

View File

@ -46,10 +46,10 @@ cur_frm.fields_dict['item_serial_no'].get_query = function(doc, cdt, cdn) {
if (doc.item_code) { if (doc.item_code) {
filter = { filter = {
'item_code': doc.item_code, 'item_code': doc.item_code,
'status': "In Store" 'status': "Available"
} }
} else } else
filter = { 'status': "In Store" } filter = { 'status': "Available" }
return { filters: filter } return { filters: filter }
} }

View File

@ -271,3 +271,32 @@ class SellingController(StockController):
msgprint(_(self.meta.get_label("order_type")) + " " + msgprint(_(self.meta.get_label("order_type")) + " " +
_("must be one of") + ": " + comma_or(valid_types), _("must be one of") + ": " + comma_or(valid_types),
raise_exception=True) raise_exception=True)
def update_serial_nos(self, cancel=False):
from stock.doctype.stock_ledger_entry.stock_ledger_entry import update_serial_nos_after_submit, get_serial_nos
update_serial_nos_after_submit(self, self.doc.doctype, self.fname)
update_serial_nos_after_submit(self, self.doc.doctype, "packing_details")
for table_fieldname in (self.fname, "packing_details"):
for d in self.doclist.get({"parentfield": table_fieldname}):
for serial_no in get_serial_nos(d.serial_no):
sr = webnotes.bean("Serial No", serial_no)
if cancel:
sr.doc.status = "Available"
for fieldname in ("warranty_expiry_date", "delivery_document_type",
"delivery_document_no", "delivery_date", "delivery_time", "customer",
"customer_name"):
sr.doc.fields[fieldname] = None
else:
sr.doc.delivery_document_type = self.doc.doctype
sr.doc.delivery_document_no = self.doc.name
sr.doc.delivery_date = self.doc.posting_date
sr.doc.delivery_time = self.doc.posting_time
sr.doc.customer = self.doc.customer
sr.doc.customer_name = self.doc.customer_name
if sr.doc.warranty_period:
sr.doc.warranty_expiry_date = add_days(cstr(self.doc.delivery_date),
cint(sr.doc.warranty_period))
sr.doc.status = 'Delivered'
sr.save()

View File

@ -1,10 +0,0 @@
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd.
# License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
def execute():
import webnotes
from webnotes.modules import reload_doc
reload_doc('stock', 'doctype', 'serial_no')
webnotes.conn.sql("update `tabSerial No` set sle_exists = 1")

View File

@ -0,0 +1,5 @@
import webnotes
def execute():
webnotes.conn.sql("""update `tabSerial No` set status = 'Not Available' where status='Not In Store'""")
webnotes.conn.sql("""update `tabSerial No` set status = 'Available' where status='In Store'""")

View File

@ -39,7 +39,7 @@ def execute():
status = 'Not in Use' status = 'Not in Use'
if sle and flt(sle[0]['actual_qty']) > 0: if sle and flt(sle[0]['actual_qty']) > 0:
status = 'In Store' status = 'Available'
elif sle and flt(sle[0]['actual_qty']) < 0: elif sle and flt(sle[0]['actual_qty']) < 0:
status = 'Delivered' status = 'Delivered'

View File

@ -22,7 +22,6 @@ patch_list = [
"patches.april_2012.update_role_in_address", "patches.april_2012.update_role_in_address",
"patches.april_2012.update_permlevel_in_address", "patches.april_2012.update_permlevel_in_address",
"patches.april_2012.update_appraisal_permission", "patches.april_2012.update_appraisal_permission",
"patches.april_2012.serial_no_fixes",
"patches.april_2012.repost_stock_for_posting_time", "patches.april_2012.repost_stock_for_posting_time",
"patches.may_2012.cleanup_property_setter", "patches.may_2012.cleanup_property_setter",
"patches.may_2012.rename_prev_doctype", "patches.may_2012.rename_prev_doctype",
@ -255,4 +254,5 @@ patch_list = [
"patches.august_2013.p02_rename_price_list", "patches.august_2013.p02_rename_price_list",
"patches.august_2013.p03_pos_setting_replace_customer_account", "patches.august_2013.p03_pos_setting_replace_customer_account",
"patches.august_2013.p04_employee_birthdays", "patches.august_2013.p04_employee_birthdays",
"patches.august_2013.p05_update_serial_no_status",
] ]

View File

@ -36,4 +36,46 @@ $.extend(erpnext, {
territory.territory = wn.defaults.get_default("territory"); territory.territory = wn.defaults.get_default("territory");
} }
}, },
setup_serial_no: function(grid_row) {
if(grid_row.fields_dict.serial_no.get_status()!=="Write") return;
var $btn = $('<button class="btn btn-sm btn-default">Add Serial No</button>')
.appendTo($("<div>")
.css({"margin-bottom": "10px"})
.appendTo(grid_row.fields_dict.serial_no.$wrapper));
$btn.on("click", function() {
var d = new wn.ui.Dialog({
title: "Add Serial No",
fields: [
{
"fieldtype": "Link",
"options": "Serial No",
"label": "Serial No",
"get_query": {
item_code: grid_row.doc.item_code,
warehouse: grid_row.doc.warehouse
}
},
{
"fieldtype": "Button",
"label": "Add"
}
]
});
d.get_input("add").on("click", function() {
var serial_no = d.get_value("serial_no");
if(serial_no) {
var val = (grid_row.doc.serial_no || "").split("\n").concat([serial_no]).join("\n");
grid_row.fields_dict.serial_no.set_model_value(val.trim());
}
d.hide();
return false;
});
d.show();
});
}
}); });

View File

@ -105,7 +105,6 @@ class DocType(TransactionBase):
msgprint("Please fetch items from Delivery Note selected", raise_exception=1) msgprint("Please fetch items from Delivery Note selected", raise_exception=1)
def on_update(self): def on_update(self):
get_obj("Stock Ledger").scrub_serial_nos(self, 'installed_item_details')
webnotes.conn.set(self.doc, 'status', 'Draft') webnotes.conn.set(self.doc, 'status', 'Draft')
def on_submit(self): def on_submit(self):

View File

@ -70,9 +70,22 @@ def get_item_details(args):
if cint(args.is_pos): if cint(args.is_pos):
pos_settings = get_pos_settings(args.company) pos_settings = get_pos_settings(args.company)
out.update(apply_pos_settings(pos_settings, out)) out.update(apply_pos_settings(pos_settings, out))
if args.doctype in ("Sales Invoice", "Delivery Note"):
if item_bean.doc.has_serial_no and not args.serial_no:
out.serial_no = _get_serial_nos_by_fifo(args, item_bean)
return out return out
def _get_serial_nos_by_fifo(args, item_bean):
return "\n".join(webnotes.conn.sql_list("""select name from `tabSerial No`
where item_code=%(item_code)s and warehouse=%(warehouse)s and status='Available'
order by datetime(purchase_date, purchase_time) asc limit %(qty)s""" % {
"item_code": args.item_code,
"warehouse": args.warehouse,
"qty": cint(args.qty)
}))
def _get_item_code(barcode): def _get_item_code(barcode):
item_code = webnotes.conn.sql_list("""select name from `tabItem` where barcode=%s""", barcode) item_code = webnotes.conn.sql_list("""select name from `tabItem` where barcode=%s""", barcode)

View File

@ -74,6 +74,10 @@ erpnext.stock.DeliveryNoteController = erpnext.selling.SellingController.extend(
tc_name: function() { tc_name: function() {
this.get_terms(); this.get_terms();
}, },
delivery_note_details_on_form_rendered: function(doc, grid_row) {
erpnext.setup_serial_no(grid_row)
}
}); });

View File

@ -175,9 +175,6 @@ class DocType(SellingController):
def on_update(self): def on_update(self):
self.doclist = get_obj('Sales Common').make_packing_list(self,'delivery_note_details') self.doclist = get_obj('Sales Common').make_packing_list(self,'delivery_note_details')
sl = get_obj('Stock Ledger')
sl.scrub_serial_nos(self)
sl.scrub_serial_nos(self, 'packing_details')
def on_submit(self): def on_submit(self):
self.validate_packed_qty() self.validate_packed_qty()
@ -185,22 +182,12 @@ class DocType(SellingController):
# Check for Approving Authority # Check for Approving Authority
get_obj('Authorization Control').validate_approving_authority(self.doc.doctype, self.doc.company, self.doc.grand_total, self) get_obj('Authorization Control').validate_approving_authority(self.doc.doctype, self.doc.company, self.doc.grand_total, self)
# validate serial no for item table (non-sales-bom item) and packing list (sales-bom item)
sl_obj = get_obj("Stock Ledger")
sl_obj.validate_serial_no(self, 'delivery_note_details')
sl_obj.validate_serial_no_warehouse(self, 'delivery_note_details')
sl_obj.validate_serial_no(self, 'packing_details')
sl_obj.validate_serial_no_warehouse(self, 'packing_details')
# update delivery details in serial no
sl_obj.update_serial_record(self, 'delivery_note_details', is_submit = 1, is_incoming = 0)
sl_obj.update_serial_record(self, 'packing_details', is_submit = 1, is_incoming = 0)
# update delivered qty in sales order # update delivered qty in sales order
self.update_prevdoc_status() self.update_prevdoc_status()
# create stock ledger entry # create stock ledger entry
self.update_stock_ledger(update_stock = 1) self.update_stock_ledger(update_stock = 1)
self.update_serial_nos()
self.credit_limit() self.credit_limit()
@ -211,6 +198,50 @@ class DocType(SellingController):
webnotes.conn.set(self.doc, 'status', 'Submitted') webnotes.conn.set(self.doc, 'status', 'Submitted')
def on_cancel(self):
sales_com_obj = get_obj(dt = 'Sales Common')
sales_com_obj.check_stop_sales_order(self)
self.check_next_docstatus()
self.update_prevdoc_status()
self.update_stock_ledger(update_stock = -1)
self.update_serial_nos(cancel=True)
webnotes.conn.set(self.doc, 'status', 'Cancelled')
self.cancel_packing_slips()
self.make_cancel_gl_entries()
def update_serial_nos(self, cancel=False):
from stock.doctype.stock_ledger_entry.stock_ledger_entry import update_serial_nos_after_submit, get_serial_nos
update_serial_nos_after_submit(self, "Delivery Note", "delivery_note_details")
update_serial_nos_after_submit(self, "Delivery Note", "packing_details")
for table_fieldname in ("delivery_note_details", "packing_details"):
for d in self.doclist.get({"parentfield": table_fieldname}):
for serial_no in get_serial_nos(d.serial_no):
sr = webnotes.bean("Serial No", serial_no)
if cancel:
sr.doc.status = "Available"
for fieldname in ("warranty_expiry_date", "delivery_document_type",
"delivery_document_no", "delivery_date", "delivery_time", "customer",
"customer_name"):
sr.doc.fields[fieldname] = None
else:
sr.doc.delivery_document_type = "Delivery Note"
sr.doc.delivery_document_no = self.doc.name
sr.doc.delivery_date = self.doc.posting_date
sr.doc.delivery_time = self.doc.posting_time
sr.doc.customer = self.doc.customer
sr.doc.customer_name = self.doc.customer_name
if sr.doc.warranty_period:
sr.doc.warranty_expiry_date = add_days(cstr(self.doc.delivery_date),
cint(sr.doc.warranty_period))
sr.doc.status = 'Delivered'
sr.save()
def validate_packed_qty(self): def validate_packed_qty(self):
""" """
Validate that if packed qty exists, it should be equal to qty Validate that if packed qty exists, it should be equal to qty
@ -232,26 +263,6 @@ class DocType(SellingController):
+ ", Packed: " + cstr(d[2])) for d in packing_error_list]) + ", Packed: " + cstr(d[2])) for d in packing_error_list])
webnotes.msgprint("Packing Error:\n" + err_msg, raise_exception=1) webnotes.msgprint("Packing Error:\n" + err_msg, raise_exception=1)
def on_cancel(self):
sales_com_obj = get_obj(dt = 'Sales Common')
sales_com_obj.check_stop_sales_order(self)
self.check_next_docstatus()
# remove delivery details from serial no
sl = get_obj('Stock Ledger')
sl.update_serial_record(self, 'delivery_note_details', is_submit = 0, is_incoming = 0)
sl.update_serial_record(self, 'packing_details', is_submit = 0, is_incoming = 0)
self.update_prevdoc_status()
self.update_stock_ledger(update_stock = -1)
webnotes.conn.set(self.doc, 'status', 'Cancelled')
self.cancel_packing_slips()
self.make_cancel_gl_entries()
def check_next_docstatus(self): def check_next_docstatus(self):
submit_rv = sql("select t1.name from `tabSales Invoice` t1,`tabSales Invoice Item` t2 where t1.name = t2.parent and t2.delivery_note = '%s' and t1.docstatus = 1" % (self.doc.name)) submit_rv = sql("select t1.name from `tabSales Invoice` t1,`tabSales Invoice Item` t2 where t1.name = t2.parent and t2.delivery_note = '%s' and t1.docstatus = 1" % (self.doc.name))
if submit_rv: if submit_rv:
@ -263,7 +274,6 @@ class DocType(SellingController):
msgprint("Installation Note : "+cstr(submit_in[0][0]) +" has already been submitted !") msgprint("Installation Note : "+cstr(submit_in[0][0]) +" has already been submitted !")
raise Exception , "Validation Error." raise Exception , "Validation Error."
def cancel_packing_slips(self): def cancel_packing_slips(self):
""" """
Cancel submitted packing slips related to this delivery note Cancel submitted packing slips related to this delivery note

View File

@ -96,6 +96,59 @@ class TestDeliveryNote(unittest.TestCase):
self.assertEquals(bal, prev_bal - 375.0) self.assertEquals(bal, prev_bal - 375.0)
webnotes.defaults.set_global_default("auto_inventory_accounting", 0) webnotes.defaults.set_global_default("auto_inventory_accounting", 0)
def test_serialized(self):
from stock.doctype.stock_entry.test_stock_entry import make_serialized_item
from stock.doctype.stock_ledger_entry.stock_ledger_entry import get_serial_nos
se = make_serialized_item()
serial_nos = get_serial_nos(se.doclist[1].serial_no)
dn = webnotes.bean(copy=test_records[0])
dn.doclist[1].item_code = "_Test Serialized Item With Series"
dn.doclist[1].qty = 1
dn.doclist[1].serial_no = serial_nos[0]
dn.insert()
dn.submit()
self.assertEquals(webnotes.conn.get_value("Serial No", serial_nos[0], "status"), "Delivered")
self.assertFalse(webnotes.conn.get_value("Serial No", serial_nos[0], "warehouse"))
self.assertEquals(webnotes.conn.get_value("Serial No", serial_nos[0],
"delivery_document_no"), dn.doc.name)
return dn
def test_serialized_cancel(self):
from stock.doctype.stock_ledger_entry.stock_ledger_entry import get_serial_nos
dn = self.test_serialized()
dn.cancel()
serial_nos = get_serial_nos(dn.doclist[1].serial_no)
self.assertEquals(webnotes.conn.get_value("Serial No", serial_nos[0], "status"), "Available")
self.assertEquals(webnotes.conn.get_value("Serial No", serial_nos[0], "warehouse"), "_Test Warehouse - _TC")
self.assertFalse(webnotes.conn.get_value("Serial No", serial_nos[0],
"delivery_document_no"))
def test_serialize_status(self):
from stock.doctype.stock_ledger_entry.stock_ledger_entry import SerialNoStatusError, get_serial_nos
from stock.doctype.stock_entry.test_stock_entry import make_serialized_item
se = make_serialized_item()
serial_nos = get_serial_nos(se.doclist[1].serial_no)
sr = webnotes.bean("Serial No", serial_nos[0])
sr.doc.status = "Not Available"
sr.save()
dn = webnotes.bean(copy=test_records[0])
dn.doclist[1].item_code = "_Test Serialized Item With Series"
dn.doclist[1].qty = 1
dn.doclist[1].serial_no = serial_nos[0]
dn.insert()
self.assertRaises(SerialNoStatusError, dn.submit)
test_records = [ test_records = [
[ [

View File

@ -2,7 +2,7 @@
{ {
"creation": "2013-05-03 10:45:46", "creation": "2013-05-03 10:45:46",
"docstatus": 0, "docstatus": 0,
"modified": "2013-08-08 14:22:25", "modified": "2013-08-14 11:46:49",
"modified_by": "Administrator", "modified_by": "Administrator",
"owner": "Administrator" "owner": "Administrator"
}, },
@ -300,6 +300,14 @@
"read_only": 0, "read_only": 0,
"reqd": 1 "reqd": 1
}, },
{
"depends_on": "eval: doc.has_serial_no===\"Yes\"",
"description": "Example: ABCD.#####\nIf series is set and Serial No is not mentioned in transactions, then automatic serial number will be created based on this series. If you always want to explicitly mention Serial Nos for this item. leave this blank.",
"doctype": "DocField",
"fieldname": "serial_no_series",
"fieldtype": "Data",
"label": "Serial Number Series"
},
{ {
"depends_on": "eval:doc.is_stock_item==\"Yes\"", "depends_on": "eval:doc.is_stock_item==\"Yes\"",
"doctype": "DocField", "doctype": "DocField",

View File

@ -194,4 +194,25 @@ test_records = [
"is_sub_contracted_item": "No", "is_sub_contracted_item": "No",
"stock_uom": "_Test UOM" "stock_uom": "_Test UOM"
}], }],
[{
"doctype": "Item",
"item_code": "_Test Serialized Item With Series",
"item_name": "_Test Serialized Item With Series",
"description": "_Test Serialized Item",
"item_group": "_Test Item Group Desktops",
"is_stock_item": "Yes",
"default_warehouse": "_Test Warehouse - _TC",
"is_asset_item": "No",
"has_batch_no": "No",
"has_serial_no": "Yes",
"serial_no_series": "ABCD.#####",
"is_purchase_item": "Yes",
"is_sales_item": "Yes",
"is_service_item": "No",
"is_sample_item": "No",
"inspection_required": "No",
"is_pro_applicable": "No",
"is_sub_contracted_item": "No",
"stock_uom": "_Test UOM"
}],
] ]

View File

@ -127,8 +127,6 @@ class DocType(BuyingController):
self.validate_inspection() self.validate_inspection()
self.validate_uom_is_integer("uom", ["qty", "received_qty"]) self.validate_uom_is_integer("uom", ["qty", "received_qty"])
self.validate_uom_is_integer("stock_uom", "stock_qty") self.validate_uom_is_integer("stock_uom", "stock_qty")
get_obj('Stock Ledger').validate_serial_no(self, 'purchase_receipt_details')
self.validate_challan_no() self.validate_challan_no()
pc_obj = get_obj(dt='Purchase Common') pc_obj = get_obj(dt='Purchase Common')
@ -147,16 +145,6 @@ class DocType(BuyingController):
for d in getlist(self.doclist,'purchase_receipt_details'): for d in getlist(self.doclist,'purchase_receipt_details'):
d.rejected_warehouse = self.doc.rejected_warehouse d.rejected_warehouse = self.doc.rejected_warehouse
get_obj('Stock Ledger').scrub_serial_nos(self)
self.scrub_rejected_serial_nos()
def scrub_rejected_serial_nos(self):
for d in getlist(self.doclist, 'purchase_receipt_details'):
if d.rejected_serial_no:
d.rejected_serial_no = cstr(d.rejected_serial_no).strip().replace(',', '\n')
d.save()
def update_stock(self, is_submit): def update_stock(self, is_submit):
pc_obj = get_obj('Purchase Common') pc_obj = get_obj('Purchase Common')
self.values = [] self.values = []
@ -207,11 +195,6 @@ class DocType(BuyingController):
# make Stock Entry # make Stock Entry
def make_sl_entry(self, d, wh, qty, in_value, is_submit, rejected = 0): def make_sl_entry(self, d, wh, qty, in_value, is_submit, rejected = 0):
if rejected:
serial_no = cstr(d.rejected_serial_no).strip()
else:
serial_no = cstr(d.serial_no).strip()
self.values.append({ self.values.append({
'item_code' : d.fields.has_key('item_code') and d.item_code or d.rm_item_code, 'item_code' : d.fields.has_key('item_code') and d.item_code or d.rm_item_code,
'warehouse' : wh, 'warehouse' : wh,
@ -227,7 +210,7 @@ class DocType(BuyingController):
'fiscal_year' : self.doc.fiscal_year, 'fiscal_year' : self.doc.fiscal_year,
'is_cancelled' : (is_submit==1) and 'No' or 'Yes', 'is_cancelled' : (is_submit==1) and 'No' or 'Yes',
'batch_no' : cstr(d.batch_no).strip(), 'batch_no' : cstr(d.batch_no).strip(),
'serial_no' : serial_no, 'serial_no' : d.serial_no,
"project" : d.project_name "project" : d.project_name
}) })
@ -257,20 +240,34 @@ class DocType(BuyingController):
get_obj('Authorization Control').validate_approving_authority(self.doc.doctype, self.doc.company, self.doc.grand_total) get_obj('Authorization Control').validate_approving_authority(self.doc.doctype, self.doc.company, self.doc.grand_total)
# Set status as Submitted # Set status as Submitted
webnotes.conn.set(self.doc,'status', 'Submitted') webnotes.conn.set(self.doc, 'status', 'Submitted')
self.update_prevdoc_status() self.update_prevdoc_status()
# Update Serial Record
get_obj('Stock Ledger').update_serial_record(self, 'purchase_receipt_details', is_submit = 1, is_incoming = 1)
# Update Stock # Update Stock
self.update_stock(is_submit = 1) self.update_stock(is_submit = 1)
self.update_serial_nos()
# Update last purchase rate # Update last purchase rate
purchase_controller.update_last_purchase_rate(self, 1) purchase_controller.update_last_purchase_rate(self, 1)
self.make_gl_entries() self.make_gl_entries()
def update_serial_nos(self, cancel=False):
from stock.doctype.stock_ledger_entry.stock_ledger_entry import update_serial_nos_after_submit, get_serial_nos
update_serial_nos_after_submit(self, "Purchase Receipt", "purchase_receipt_details")
for d in self.doclist.get({"parentfield": "purchase_receipt_details"}):
for serial_no in get_serial_nos(d.serial_no):
sr = webnotes.bean("Serial No", serial_no)
if cancel:
sr.doc.supplier = None
sr.doc.supplier_name = None
else:
sr.doc.supplier = self.doc.supplier
sr.doc.supplier_name = self.doc.supplier_name
sr.save()
def check_next_docstatus(self): def check_next_docstatus(self):
submit_rv = sql("select t1.name from `tabPurchase Invoice` t1,`tabPurchase Invoice Item` t2 where t1.name = t2.parent and t2.purchase_receipt = '%s' and t1.docstatus = 1" % (self.doc.name)) submit_rv = sql("select t1.name from `tabPurchase Invoice` t1,`tabPurchase Invoice Item` t2 where t1.name = t2.parent and t2.purchase_receipt = '%s' and t1.docstatus = 1" % (self.doc.name))
@ -295,10 +292,10 @@ class DocType(BuyingController):
webnotes.conn.set(self.doc,'status','Cancelled') webnotes.conn.set(self.doc,'status','Cancelled')
# 3. Cancel Serial No # 3. Cancel Serial No
get_obj('Stock Ledger').update_serial_record(self, 'purchase_receipt_details', is_submit = 0, is_incoming = 1)
# 4.Update Bin # 4.Update Bin
self.update_stock(is_submit = 0) self.update_stock(is_submit = 0)
self.update_serial_nos(cancel=True)
self.update_prevdoc_status() self.update_prevdoc_status()

View File

@ -76,8 +76,31 @@ class TestPurchaseReceipt(unittest.TestCase):
self.assertEquals(pr.doclist[1].rm_supp_cost, 70000.0) self.assertEquals(pr.doclist[1].rm_supp_cost, 70000.0)
self.assertEquals(len(pr.doclist.get({"parentfield": "pr_raw_material_details"})), 2) self.assertEquals(len(pr.doclist.get({"parentfield": "pr_raw_material_details"})), 2)
def test_serial_no_supplier(self):
pr = webnotes.bean(copy=test_records[0])
pr.doclist[1].item_code = "_Test Serialized Item With Series"
pr.doclist[1].qty = 1
pr.doclist[1].received_qty = 1
pr.insert()
pr.submit()
self.assertEquals(webnotes.conn.get_value("Serial No", pr.doclist[1].serial_no,
"supplier"), pr.doc.supplier)
return pr
def test_serial_no_cancel(self):
pr = self.test_serial_no_supplier()
pr.cancel()
self.assertFalse(webnotes.conn.get_value("Serial No", pr.doclist[1].serial_no,
"warehouse"))
self.assertEqual(webnotes.conn.get_value("Serial No", pr.doclist[1].serial_no,
"status"), "Not Available")
test_dependencies = ["BOM"] test_dependencies = ["BOM"]
test_records = [ test_records = [

View File

@ -1,69 +1,3 @@
// Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. // Copyright (c) 2013, Web Notes Technologies Pvt. Ltd.
// License: GNU General Public License v3. See license.txt // License: GNU General Public License v3. See license.txt
cur_frm.cscript.onload = function(doc, cdt, cdn) {
if(!doc.status) set_multiple(cdt, cdn, {status:'In Store'});
if(doc.__islocal) hide_field(['supplier_name','address_display'])
}
cur_frm.cscript.refresh = function(doc, cdt, cdn) {
flds = ['status', 'item_code', 'warehouse', 'purchase_document_type',
'purchase_document_no', 'purchase_date', 'purchase_time', 'purchase_rate',
'supplier']
for(i=0;i<flds.length;i++) {
cur_frm.set_df_property(flds[i], 'read_only', doc.__islocal ? 0 : 1);
}
}
// item details
// -------------
cur_frm.add_fetch('item_code', 'item_name', 'item_name')
cur_frm.add_fetch('item_code', 'item_group', 'item_group')
cur_frm.add_fetch('item_code', 'brand', 'brand')
cur_frm.add_fetch('item_code', 'description', 'description')
cur_frm.add_fetch('item_code', 'warranty_period', 'warranty_period')
// customer
// ---------
cur_frm.add_fetch('customer', 'customer_name', 'customer_name')
cur_frm.add_fetch('customer', 'address', 'delivery_address')
cur_frm.add_fetch('customer', 'territory', 'territory')
// territory
// ----------
cur_frm.fields_dict['territory'].get_query = function(doc,cdt,cdn) {
return{
filters:{'is_group': "No"}
}
}
// Supplier
//-------------
cur_frm.cscript.supplier = function(doc,dt,dn) {
if(doc.supplier) return get_server_fields('get_default_supplier_address', JSON.stringify({supplier: doc.supplier}),'', doc, dt, dn, 1);
if(doc.supplier) unhide_field(['supplier_name','address_display']);
}
//item code
//----------
cur_frm.fields_dict['item_code'].get_query = function(doc,cdt,cdn) {
return{
query:"controllers.queries.item_query",
filters:{
'has_serial_no': 'Yes'
}
}
}
cur_frm.fields_dict.customer.get_query = function(doc,cdt,cdn) {
return{
query:"controllers.queries.customer_query"
}
}
cur_frm.fields_dict.supplier.get_query = function(doc,cdt,cdn) {
return{
query:"controllers.queries.supplier_query"
}
}

View File

@ -7,13 +7,25 @@ import webnotes
from webnotes.utils import cint, getdate, nowdate from webnotes.utils import cint, getdate, nowdate
import datetime import datetime
from webnotes import msgprint, _ from webnotes import msgprint, _
from controllers.stock_controller import StockController from controllers.stock_controller import StockController
class SerialNoCannotCreateDirectError(webnotes.ValidationError): pass
class DocType(StockController): class DocType(StockController):
def __init__(self, doc, doclist=[]): def __init__(self, doc, doclist=[]):
self.doc = doc self.doc = doc
self.doclist = doclist self.doclist = doclist
self.via_stock_ledger = False
def validate(self):
if self.doc.fields.get("__islocal") and not self.via_stock_ledger:
webnotes.throw(_("Serial No cannot be created directly"), SerialNoCannotCreateDirectError)
self.validate_warranty_status()
self.validate_amc_status()
self.validate_warehouse()
self.validate_item()
def validate_amc_status(self): def validate_amc_status(self):
""" """
@ -31,78 +43,40 @@ class DocType(StockController):
def validate_warehouse(self): def validate_warehouse(self):
if self.doc.status=='In Store' and not self.doc.warehouse: if not self.doc.fields.get("__islocal"):
msgprint("Warehouse is mandatory if this Serial No is <b>In Store</b>", raise_exception=1) item_code, warehouse = webnotes.conn.get_value("Serial No",
self.doc.name, ["item_code", "warehouse"])
if item_code != self.doc.item_code:
webnotes.throw(_("Item Code cannot be changed for Serial No."))
if not self.via_stock_ledger and warehouse != self.doc.warehouse:
webnotes.throw(_("Warehouse cannot be changed for Serial No."))
if not self.doc.warehouse and self.doc.status=="Available":
self.doc.status = "Not Available"
def validate_item(self): def validate_item(self):
""" """
Validate whether serial no is required for this item Validate whether serial no is required for this item
""" """
item = webnotes.conn.sql("select name, has_serial_no from tabItem where name = '%s'" % self.doc.item_code) item = webnotes.doc("Item", self.doc.item_code)
if not item: if item.has_serial_no!="Yes":
msgprint("Item is not exists in the system", raise_exception=1) webnotes.throw(_("Item must have 'Has Serial No' as 'Yes'") + ": " + self.doc.item_code)
elif item[0][1] == 'No':
msgprint("To proceed please select 'Yes' in 'Has Serial No' in Item master: '%s'" % self.doc.item_code, raise_exception=1)
self.doc.item_group = item.item_group
def validate(self): self.doc.description = item.description
self.validate_warranty_status() self.doc.item_name = item.item_name
self.validate_amc_status() self.doc.brand = item.brand
self.validate_warehouse() self.doc.warranty_period = item.warranty_period
self.validate_item()
def on_update(self):
if self.doc.warehouse and self.doc.status == 'In Store' \
and cint(self.doc.sle_exists) == 0 and \
not webnotes.conn.sql("""select name from `tabStock Ledger Entry`
where serial_no = %s and ifnull(is_cancelled, 'No') = 'No'""", self.doc.name):
self.make_stock_ledger_entry(1)
webnotes.conn.set(self.doc, 'sle_exists', 1)
self.make_gl_entries()
def make_stock_ledger_entry(self, qty):
from webnotes.model.code import get_obj
values = [{
'item_code' : self.doc.item_code,
'warehouse' : self.doc.warehouse,
'posting_date' : self.doc.purchase_date or (self.doc.creation and self.doc.creation.split(' ')[0]) or nowdate(),
'posting_time' : self.doc.purchase_time or '00:00',
'voucher_type' : 'Serial No',
'voucher_no' : self.doc.name,
'voucher_detail_no' : '',
'actual_qty' : qty,
'stock_uom' : webnotes.conn.get_value('Item', self.doc.item_code, 'stock_uom'),
'incoming_rate' : self.doc.purchase_rate,
'company' : self.doc.company,
'fiscal_year' : self.doc.fiscal_year,
'is_cancelled' : 'No', # is_cancelled is always 'No' because while deleted it can not find creation entry if it not created directly, voucher no != serial no
'batch_no' : '',
'serial_no' : self.doc.name
}]
get_obj('Stock Ledger').update_stock(values)
def on_trash(self): def on_trash(self):
if self.doc.status == 'Delivered': if self.doc.status == 'Delivered':
msgprint("Cannot trash Serial No : %s as it is already Delivered" % (self.doc.name), raise_exception = 1) msgprint("Cannot trash Serial No : %s as it is already Delivered" % (self.doc.name), raise_exception = 1)
elif self.doc.status == 'In Store': if self.doc.warehouse:
webnotes.conn.set(self.doc, 'status', 'Not in Use') webnotes.throw(_("Cannot delete Serial No in warehouse. First remove from warehouse, then delete.") + \
self.make_stock_ledger_entry(-1) ": " + self.doc.name)
if cint(webnotes.defaults.get_global_default("auto_inventory_accounting")) \
and webnotes.conn.sql("""select name from `tabGL Entry`
where voucher_type=%s and voucher_no=%s and ifnull(is_cancelled, 'No')='No'""",
(self.doc.doctype, self.doc.name)):
self.make_gl_entries(cancel=True)
def on_cancel(self): def on_cancel(self):
self.on_trash() self.on_trash()
def on_restore(self):
self.make_stock_ledger_entry(1)
self.make_gl_entries()
def on_rename(self, new, old, merge=False): def on_rename(self, new, old, merge=False):
"""rename serial_no text fields""" """rename serial_no text fields"""
@ -119,18 +93,3 @@ class DocType(StockController):
webnotes.conn.sql("""update `tab%s` set serial_no = %s webnotes.conn.sql("""update `tab%s` set serial_no = %s
where name=%s""" % (dt[0], '%s', '%s'), where name=%s""" % (dt[0], '%s', '%s'),
('\n'.join(serial_nos), item[0])) ('\n'.join(serial_nos), item[0]))
def make_gl_entries(self, cancel=False):
if not cint(webnotes.defaults.get_global_default("auto_inventory_accounting")):
return
from accounts.general_ledger import make_gl_entries
against_stock_account = self.get_company_default("stock_adjustment_account")
gl_entries = self.get_gl_entries_for_stock(against_stock_account, self.doc.purchase_rate)
for entry in gl_entries:
entry["posting_date"] = self.doc.purchase_date or (self.doc.creation and
self.doc.creation.split(' ')[0]) or nowdate()
if gl_entries:
make_gl_entries(gl_entries, cancel)

View File

@ -2,7 +2,7 @@
{ {
"creation": "2013-05-16 10:59:15", "creation": "2013-05-16 10:59:15",
"docstatus": 0, "docstatus": 0,
"modified": "2013-07-22 15:29:43", "modified": "2013-08-14 18:26:23",
"modified_by": "Administrator", "modified_by": "Administrator",
"owner": "Administrator" "owner": "Administrator"
}, },
@ -12,8 +12,9 @@
"autoname": "field:serial_no", "autoname": "field:serial_no",
"description": "Distinct unit of an Item", "description": "Distinct unit of an Item",
"doctype": "DocType", "doctype": "DocType",
"document_type": "Master", "document_type": "Other",
"icon": "icon-barcode", "icon": "icon-barcode",
"in_create": 1,
"module": "Stock", "module": "Stock",
"name": "__common__", "name": "__common__",
"search_fields": "item_code,status" "search_fields": "item_code,status"
@ -57,6 +58,7 @@
}, },
{ {
"default": "In Store", "default": "In Store",
"description": "Only Serial Nos with status \"In Store\" can be delivered.",
"doctype": "DocField", "doctype": "DocField",
"fieldname": "status", "fieldname": "status",
"fieldtype": "Select", "fieldtype": "Select",
@ -66,8 +68,8 @@
"no_copy": 1, "no_copy": 1,
"oldfieldname": "status", "oldfieldname": "status",
"oldfieldtype": "Select", "oldfieldtype": "Select",
"options": "\nIn Store\nDelivered\nNot in Use\nPurchase Returned", "options": "\nAvailable\nNot Available\nDelivered\nPurchase Returned\nSales Returned",
"read_only": 1, "read_only": 0,
"reqd": 1, "reqd": 1,
"search_index": 1 "search_index": 1
}, },
@ -94,10 +96,25 @@
"oldfieldname": "item_code", "oldfieldname": "item_code",
"oldfieldtype": "Link", "oldfieldtype": "Link",
"options": "Item", "options": "Item",
"read_only": 0, "read_only": 1,
"reqd": 1, "reqd": 1,
"search_index": 0 "search_index": 0
}, },
{
"doctype": "DocField",
"fieldname": "warehouse",
"fieldtype": "Link",
"in_filter": 1,
"in_list_view": 1,
"label": "Warehouse",
"no_copy": 1,
"oldfieldname": "warehouse",
"oldfieldtype": "Link",
"options": "Warehouse",
"read_only": 1,
"reqd": 0,
"search_index": 1
},
{ {
"doctype": "DocField", "doctype": "DocField",
"fieldname": "column_break1", "fieldname": "column_break1",
@ -134,7 +151,7 @@
"oldfieldtype": "Link", "oldfieldtype": "Link",
"options": "Item Group", "options": "Item Group",
"read_only": 1, "read_only": 1,
"reqd": 1, "reqd": 0,
"search_index": 0 "search_index": 0
}, },
{ {
@ -154,7 +171,7 @@
"doctype": "DocField", "doctype": "DocField",
"fieldname": "purchase_details", "fieldname": "purchase_details",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"label": "Purchase Details", "label": "Purchase / Manufacture Details",
"read_only": 0 "read_only": 0
}, },
{ {
@ -168,30 +185,30 @@
"doctype": "DocField", "doctype": "DocField",
"fieldname": "purchase_document_type", "fieldname": "purchase_document_type",
"fieldtype": "Select", "fieldtype": "Select",
"label": "Purchase Document Type", "label": "Creation Document Type",
"no_copy": 1, "no_copy": 1,
"options": "\nPurchase Receipt\nStock Entry", "options": "\nPurchase Receipt\nStock Entry",
"read_only": 0 "read_only": 1
}, },
{ {
"doctype": "DocField", "doctype": "DocField",
"fieldname": "purchase_document_no", "fieldname": "purchase_document_no",
"fieldtype": "Data", "fieldtype": "Data",
"hidden": 0, "hidden": 0,
"label": "Purchase Document No", "label": "Creation Document No",
"no_copy": 1, "no_copy": 1,
"read_only": 0 "read_only": 1
}, },
{ {
"doctype": "DocField", "doctype": "DocField",
"fieldname": "purchase_date", "fieldname": "purchase_date",
"fieldtype": "Date", "fieldtype": "Date",
"in_filter": 1, "in_filter": 1,
"label": "Purchase Date", "label": "Creation Date",
"no_copy": 1, "no_copy": 1,
"oldfieldname": "purchase_date", "oldfieldname": "purchase_date",
"oldfieldtype": "Date", "oldfieldtype": "Date",
"read_only": 0, "read_only": 1,
"reqd": 0, "reqd": 0,
"search_index": 0 "search_index": 0
}, },
@ -199,9 +216,9 @@
"doctype": "DocField", "doctype": "DocField",
"fieldname": "purchase_time", "fieldname": "purchase_time",
"fieldtype": "Time", "fieldtype": "Time",
"label": "Incoming Time", "label": "Creation Time",
"no_copy": 1, "no_copy": 1,
"read_only": 0, "read_only": 1,
"reqd": 1 "reqd": 1
}, },
{ {
@ -214,7 +231,7 @@
"oldfieldname": "purchase_rate", "oldfieldname": "purchase_rate",
"oldfieldtype": "Currency", "oldfieldtype": "Currency",
"options": "Company:company:default_currency", "options": "Company:company:default_currency",
"read_only": 0, "read_only": 1,
"reqd": 1, "reqd": 1,
"search_index": 0 "search_index": 0
}, },
@ -225,21 +242,6 @@
"read_only": 0, "read_only": 0,
"width": "50%" "width": "50%"
}, },
{
"doctype": "DocField",
"fieldname": "warehouse",
"fieldtype": "Link",
"in_filter": 1,
"in_list_view": 1,
"label": "Warehouse",
"no_copy": 1,
"oldfieldname": "warehouse",
"oldfieldtype": "Link",
"options": "Warehouse",
"read_only": 0,
"reqd": 0,
"search_index": 1
},
{ {
"doctype": "DocField", "doctype": "DocField",
"fieldname": "supplier", "fieldname": "supplier",
@ -248,7 +250,7 @@
"label": "Supplier", "label": "Supplier",
"no_copy": 1, "no_copy": 1,
"options": "Supplier", "options": "Supplier",
"read_only": 0 "read_only": 1
}, },
{ {
"doctype": "DocField", "doctype": "DocField",
@ -259,14 +261,6 @@
"no_copy": 1, "no_copy": 1,
"read_only": 1 "read_only": 1
}, },
{
"doctype": "DocField",
"fieldname": "address_display",
"fieldtype": "Text",
"label": "Supplier Address",
"no_copy": 1,
"read_only": 1
},
{ {
"doctype": "DocField", "doctype": "DocField",
"fieldname": "delivery_details", "fieldname": "delivery_details",
@ -275,13 +269,6 @@
"oldfieldtype": "Column Break", "oldfieldtype": "Column Break",
"read_only": 0 "read_only": 0
}, },
{
"doctype": "DocField",
"fieldname": "column_break4",
"fieldtype": "Column Break",
"read_only": 0,
"width": "50%"
},
{ {
"doctype": "DocField", "doctype": "DocField",
"fieldname": "delivery_document_type", "fieldname": "delivery_document_type",
@ -301,15 +288,6 @@
"no_copy": 1, "no_copy": 1,
"read_only": 1 "read_only": 1
}, },
{
"doctype": "DocField",
"fieldname": "customer_address",
"fieldtype": "Text",
"label": "Customer Address",
"oldfieldname": "customer_address",
"oldfieldtype": "Text",
"read_only": 1
},
{ {
"doctype": "DocField", "doctype": "DocField",
"fieldname": "delivery_date", "fieldname": "delivery_date",
@ -374,28 +352,6 @@
"read_only": 1, "read_only": 1,
"search_index": 0 "search_index": 0
}, },
{
"doctype": "DocField",
"fieldname": "delivery_address",
"fieldtype": "Text",
"label": "Delivery Address",
"no_copy": 1,
"read_only": 1
},
{
"doctype": "DocField",
"fieldname": "territory",
"fieldtype": "Link",
"in_filter": 1,
"label": "Territory",
"no_copy": 1,
"oldfieldname": "territory",
"oldfieldtype": "Link",
"options": "Territory",
"print_hide": 1,
"read_only": 1,
"report_hide": 0
},
{ {
"doctype": "DocField", "doctype": "DocField",
"fieldname": "warranty_amc_details", "fieldname": "warranty_amc_details",
@ -485,7 +441,7 @@
"in_filter": 1, "in_filter": 1,
"label": "Company", "label": "Company",
"options": "link:Company", "options": "link:Company",
"read_only": 0, "read_only": 1,
"reqd": 1, "reqd": 1,
"search_index": 1 "search_index": 1
}, },
@ -500,26 +456,6 @@
"reqd": 1, "reqd": 1,
"search_index": 1 "search_index": 1
}, },
{
"doctype": "DocField",
"fieldname": "trash_reason",
"fieldtype": "Small Text",
"label": "Trash Reason",
"oldfieldname": "trash_reason",
"oldfieldtype": "Small Text",
"read_only": 1
},
{
"doctype": "DocField",
"fieldname": "sle_exists",
"fieldtype": "Check",
"hidden": 1,
"label": "SLE Exists",
"no_copy": 1,
"print_hide": 1,
"read_only": 1,
"report_hide": 1
},
{ {
"cancel": 1, "cancel": 1,
"create": 1, "create": 1,

View File

@ -7,99 +7,14 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import webnotes, unittest import webnotes, unittest
class TestSerialNo(unittest.TestCase):
def test_aii_gl_entries_for_serial_no_in_store(self):
webnotes.defaults.set_global_default("auto_inventory_accounting", 1)
sr = webnotes.bean(copy=test_records[0])
sr.doc.serial_no = "_Test Serial No 1"
sr.insert()
stock_in_hand_account = webnotes.conn.get_value("Company", "_Test Company",
"stock_in_hand_account")
against_stock_account = webnotes.conn.get_value("Company", "_Test Company",
"stock_adjustment_account")
# check stock ledger entries
sle = webnotes.conn.sql("""select * from `tabStock Ledger Entry`
where voucher_type = 'Serial No' and voucher_no = %s""", sr.doc.name, as_dict=1)[0]
self.assertTrue(sle)
self.assertEquals([sle.item_code, sle.warehouse, sle.actual_qty],
["_Test Serialized Item", "_Test Warehouse - _TC", 1.0])
# check gl entries
gl_entries = webnotes.conn.sql("""select account, debit, credit
from `tabGL Entry` where voucher_type='Serial No' and voucher_no=%s
order by account desc""", sr.doc.name, as_dict=1)
self.assertTrue(gl_entries)
expected_values = [
[stock_in_hand_account, 1000.0, 0.0],
[against_stock_account, 0.0, 1000.0]
]
for i, gle in enumerate(gl_entries):
self.assertEquals(expected_values[i][0], gle.account)
self.assertEquals(expected_values[i][1], gle.debit)
self.assertEquals(expected_values[i][2], gle.credit)
sr.load_from_db()
self.assertEquals(sr.doc.sle_exists, 1)
# save again
sr.save()
gl_entries = webnotes.conn.sql("""select account, debit, credit
from `tabGL Entry` where voucher_type='Serial No' and voucher_no=%s
order by account desc""", sr.doc.name, as_dict=1)
for i, gle in enumerate(gl_entries):
self.assertEquals(expected_values[i][0], gle.account)
self.assertEquals(expected_values[i][1], gle.debit)
self.assertEquals(expected_values[i][2], gle.credit)
# trash/cancel
sr.submit()
sr.cancel()
gl_count = webnotes.conn.sql("""select count(name) from `tabGL Entry`
where voucher_type='Serial No' and voucher_no=%s and ifnull(is_cancelled, 'No') = 'Yes'
order by account asc, name asc""", sr.doc.name)
self.assertEquals(gl_count[0][0], 4)
webnotes.defaults.set_global_default("auto_inventory_accounting", 0)
def test_aii_gl_entries_for_serial_no_delivered(self):
webnotes.defaults.set_global_default("auto_inventory_accounting", 1)
sr = webnotes.bean(copy=test_records[0])
sr.doc.serial_no = "_Test Serial No 2"
sr.doc.status = "Delivered"
sr.insert()
gl_entries = webnotes.conn.sql("""select account, debit, credit
from `tabGL Entry` where voucher_type='Serial No' and voucher_no=%s
order by account desc""", sr.doc.name, as_dict=1)
self.assertFalse(gl_entries)
webnotes.defaults.set_global_default("auto_inventory_accounting", 0)
test_dependencies = ["Item"] test_dependencies = ["Item"]
test_records = [ test_records = []
[
{ from stock.doctype.serial_no.serial_no import *
"company": "_Test Company",
"doctype": "Serial No", class TestSerialNo(unittest.TestCase):
"serial_no": "_Test Serial No", def test_cannot_create_direct(self):
"status": "In Store", sr = webnotes.new_bean("Serial No")
"item_code": "_Test Serialized Item", sr.doc.item_code = "_Test Serialized Item"
"item_group": "_Test Item Group", sr.doc.purchase_rate = 10
"warehouse": "_Test Warehouse - _TC", self.assertRaises(SerialNoCannotCreateDirectError, sr.insert)
"purchase_rate": 1000.0,
"purchase_time": "11:37:39",
"purchase_date": "2013-02-26",
'fiscal_year': "_Test Fiscal Year 2013"
}
]
]

View File

@ -223,6 +223,10 @@ erpnext.stock.StockEntry = erpnext.stock.StockController.extend({
if(!row.s_warehouse) row.s_warehouse = this.frm.doc.from_warehouse; if(!row.s_warehouse) row.s_warehouse = this.frm.doc.from_warehouse;
if(!row.t_warehouse) row.t_warehouse = this.frm.doc.to_warehouse; if(!row.t_warehouse) row.t_warehouse = this.frm.doc.to_warehouse;
}, },
mtn_details_on_form_rendered: function(doc, grid_row) {
erpnext.setup_serial_no(grid_row)
}
}); });
cur_frm.script_manager.make(erpnext.stock.StockEntry); cur_frm.script_manager.make(erpnext.stock.StockEntry);

View File

@ -32,7 +32,6 @@ class DocType(StockController):
def validate(self): def validate(self):
self.validate_posting_time() self.validate_posting_time()
self.validate_purpose() self.validate_purpose()
self.validate_serial_nos()
pro_obj = self.doc.production_order and \ pro_obj = self.doc.production_order and \
get_obj('Production Order', self.doc.production_order) or None get_obj('Production Order', self.doc.production_order) or None
@ -52,14 +51,14 @@ class DocType(StockController):
self.set_total_amount() self.set_total_amount()
def on_submit(self): def on_submit(self):
self.update_serial_no(1)
self.update_stock_ledger(0) self.update_stock_ledger(0)
self.update_serial_no(1)
self.update_production_order(1) self.update_production_order(1)
self.make_gl_entries() self.make_gl_entries()
def on_cancel(self): def on_cancel(self):
self.update_serial_no(0)
self.update_stock_ledger(1) self.update_stock_ledger(1)
self.update_serial_no(0)
self.update_production_order(0) self.update_production_order(0)
self.make_cancel_gl_entries() self.make_cancel_gl_entries()
@ -74,11 +73,6 @@ class DocType(StockController):
if self.doc.purpose not in valid_purposes: if self.doc.purpose not in valid_purposes:
msgprint(_("Purpose must be one of ") + comma_or(valid_purposes), msgprint(_("Purpose must be one of ") + comma_or(valid_purposes),
raise_exception=True) raise_exception=True)
def validate_serial_nos(self):
sl_obj = get_obj("Stock Ledger")
sl_obj.scrub_serial_nos(self)
sl_obj.validate_serial_no(self, 'mtn_details')
def validate_item(self): def validate_item(self):
for item in self.doclist.get({"parentfield": "mtn_details"}): for item in self.doclist.get({"parentfield": "mtn_details"}):
@ -206,7 +200,7 @@ class DocType(StockController):
"posting_date": self.doc.posting_date, "posting_date": self.doc.posting_date,
"posting_time": self.doc.posting_time, "posting_time": self.doc.posting_time,
"qty": d.s_warehouse and -1*d.transfer_qty or d.transfer_qty, "qty": d.s_warehouse and -1*d.transfer_qty or d.transfer_qty,
"serial_no": cstr(d.serial_no).strip(), "serial_no": d.serial_no,
"bom_no": d.bom_no, "bom_no": d.bom_no,
}) })
# get actual stock at source warehouse # get actual stock at source warehouse
@ -317,27 +311,21 @@ class DocType(StockController):
def update_serial_no(self, is_submit): def update_serial_no(self, is_submit):
"""Create / Update Serial No""" """Create / Update Serial No"""
from stock.utils import get_valid_serial_nos
from stock.doctype.stock_ledger_entry.stock_ledger_entry import update_serial_nos_after_submit, get_serial_nos
sl_obj = get_obj('Stock Ledger') update_serial_nos_after_submit(self, "Stock Entry", "mtn_details")
if is_submit:
sl_obj.validate_serial_no_warehouse(self, 'mtn_details')
for d in getlist(self.doclist, 'mtn_details'): for d in getlist(self.doclist, 'mtn_details'):
if cstr(d.serial_no).strip(): for serial_no in get_serial_nos(d.serial_no):
for x in get_valid_serial_nos(d.serial_no): if self.doc.purpose == 'Purchase Return':
serial_no = x.strip() sr = webnotes.bean("Serial No", serial_no)
if d.s_warehouse: sr.doc.status = "Purchase Returned" if is_submit else "Available"
sl_obj.update_serial_delivery_details(self, d, serial_no, is_submit) sr.save()
if d.t_warehouse:
sl_obj.update_serial_purchase_details(self, d, serial_no, is_submit, if self.doc.purpose == "Sales Return":
self.doc.purpose) sr = webnotes.bean("Serial No", serial_no)
sr.doc.status = "Sales Returned" if is_submit else "Delivered"
if self.doc.purpose == 'Purchase Return': sr.save()
serial_doc = Document("Serial No", serial_no)
serial_doc.status = is_submit and 'Purchase Returned' or 'In Store'
serial_doc.docstatus = is_submit and 2 or 0
serial_doc.save()
def update_stock_ledger(self, is_cancelled=0): def update_stock_ledger(self, is_cancelled=0):
self.values = [] self.values = []

View File

@ -7,6 +7,7 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import webnotes, unittest import webnotes, unittest
from webnotes.utils import flt from webnotes.utils import flt
from stock.doctype.stock_ledger_entry.stock_ledger_entry import *
class TestStockEntry(unittest.TestCase): class TestStockEntry(unittest.TestCase):
def tearDown(self): def tearDown(self):
@ -180,6 +181,7 @@ class TestStockEntry(unittest.TestCase):
def _clear_stock(self): def _clear_stock(self):
webnotes.conn.sql("delete from `tabStock Ledger Entry`") webnotes.conn.sql("delete from `tabStock Ledger Entry`")
webnotes.conn.sql("""delete from `tabBin`""") webnotes.conn.sql("""delete from `tabBin`""")
webnotes.conn.sql("""delete from `tabSerial No`""")
self.old_default_company = webnotes.conn.get_default("company") self.old_default_company = webnotes.conn.get_default("company")
webnotes.conn.set_default("company", "_Test Company") webnotes.conn.set_default("company", "_Test Company")
@ -571,12 +573,139 @@ class TestStockEntry(unittest.TestCase):
return se, pr.doc.name return se, pr.doc.name
def test_serial_no_not_reqd(self):
se = webnotes.bean(copy=test_records[0])
se.doclist[1].serial_no = "ABCD"
se.insert()
self.assertRaises(SerialNoNotRequiredError, se.submit)
def test_serial_no_reqd(self):
se = webnotes.bean(copy=test_records[0])
se.doclist[1].item_code = "_Test Serialized Item"
se.doclist[1].qty = 2
se.doclist[1].transfer_qty = 2
se.insert()
self.assertRaises(SerialNoRequiredError, se.submit)
def test_serial_no_qty_more(self):
se = webnotes.bean(copy=test_records[0])
se.doclist[1].item_code = "_Test Serialized Item"
se.doclist[1].qty = 2
se.doclist[1].serial_no = "ABCD\nEFGH\nXYZ"
se.doclist[1].transfer_qty = 2
se.insert()
self.assertRaises(SerialNoQtyError, se.submit)
def test_serial_no_qty_less(self):
se = webnotes.bean(copy=test_records[0])
se.doclist[1].item_code = "_Test Serialized Item"
se.doclist[1].qty = 2
se.doclist[1].serial_no = "ABCD"
se.doclist[1].transfer_qty = 2
se.insert()
self.assertRaises(SerialNoQtyError, se.submit)
def test_serial_no_transfer_in(self):
se = webnotes.bean(copy=test_records[0])
se.doclist[1].item_code = "_Test Serialized Item"
se.doclist[1].qty = 2
se.doclist[1].serial_no = "ABCD\nEFGH"
se.doclist[1].transfer_qty = 2
se.insert()
se.submit()
self.assertTrue(webnotes.conn.exists("Serial No", "ABCD"))
self.assertTrue(webnotes.conn.exists("Serial No", "EFGH"))
def test_serial_no_not_exists(self):
se = webnotes.bean(copy=test_records[0])
se.doc.purpose = "Material Issue"
se.doclist[1].item_code = "_Test Serialized Item"
se.doclist[1].qty = 2
se.doclist[1].s_warehouse = "_Test Warehouse 1 - _TC"
se.doclist[1].t_warehouse = None
se.doclist[1].serial_no = "ABCD\nEFGH"
se.doclist[1].transfer_qty = 2
se.insert()
self.assertRaises(SerialNoNotExistsError, se.submit)
def test_serial_by_series(self):
se = make_serialized_item()
serial_nos = get_serial_nos(se.doclist[1].serial_no)
self.assertTrue(webnotes.conn.exists("Serial No", serial_nos[0]))
self.assertTrue(webnotes.conn.exists("Serial No", serial_nos[1]))
return se
def test_serial_item_error(self):
self.test_serial_by_series()
se = webnotes.bean(copy=test_records[0])
se.doc.purpose = "Material Transfer"
se.doclist[1].item_code = "_Test Serialized Item"
se.doclist[1].qty = 1
se.doclist[1].transfer_qty = 1
se.doclist[1].serial_no = "ABCD00001"
se.doclist[1].s_warehouse = "_Test Warehouse - _TC"
se.doclist[1].t_warehouse = "_Test Warehouse 1 - _TC"
se.insert()
self.assertRaises(SerialNoItemError, se.submit)
def test_serial_move(self):
se = make_serialized_item()
serial_no = get_serial_nos(se.doclist[1].serial_no)[0]
se = webnotes.bean(copy=test_records[0])
se.doc.purpose = "Material Transfer"
se.doclist[1].item_code = "_Test Serialized Item With Series"
se.doclist[1].qty = 1
se.doclist[1].transfer_qty = 1
se.doclist[1].serial_no = serial_no
se.doclist[1].s_warehouse = "_Test Warehouse - _TC"
se.doclist[1].t_warehouse = "_Test Warehouse 1 - _TC"
se.insert()
se.submit()
self.assertTrue(webnotes.conn.get_value("Serial No", serial_no, "warehouse"), "_Test Warehouse 1 - _TC")
def test_serial_warehouse_error(self):
make_serialized_item()
se = webnotes.bean(copy=test_records[0])
se.doc.purpose = "Material Transfer"
se.doclist[1].item_code = "_Test Serialized Item With Series"
se.doclist[1].qty = 1
se.doclist[1].transfer_qty = 1
se.doclist[1].serial_no = "ABCD00001"
se.doclist[1].s_warehouse = "_Test Warehouse 1 - _TC"
se.doclist[1].t_warehouse = "_Test Warehouse - _TC"
se.insert()
self.assertRaises(SerialNoWarehouseError, se.submit)
def test_serial_cancel(self):
se = self.test_serial_by_series()
se.cancel()
serial_no = get_serial_nos(se.doclist[1].serial_no)[0]
self.assertFalse(webnotes.conn.get_value("Serial No", serial_no, "warehouse"))
self.assertTrue(webnotes.conn.get_value("Serial No", serial_no, "status"), "Not Available")
def make_serialized_item():
se = webnotes.bean(copy=test_records[0])
se.doclist[1].item_code = "_Test Serialized Item With Series"
se.doclist[1].qty = 2
se.doclist[1].transfer_qty = 2
se.insert()
se.submit()
return se
test_records = [ test_records = [
[ [
{ {
"company": "_Test Company", "company": "_Test Company",
"doctype": "Stock Entry", "doctype": "Stock Entry",
"posting_date": "2013-01-25", "posting_date": "2013-01-01",
"posting_time": "17:14:24", "posting_time": "17:14:24",
"purpose": "Material Receipt", "purpose": "Material Receipt",
"fiscal_year": "_Test Fiscal Year 2013", "fiscal_year": "_Test Fiscal Year 2013",

View File

@ -17,174 +17,10 @@ class DocType:
def __init__(self, doc, doclist=[]): def __init__(self, doc, doclist=[]):
self.doc = doc self.doc = doc
self.doclist = doclist self.doclist = doclist
def scrub_serial_nos(self, obj, table_name = ''):
if not table_name:
table_name = obj.fname
for d in getlist(obj.doclist, table_name):
if d.serial_no:
serial_nos = cstr(d.serial_no).strip().replace(',', '\n').split('\n')
d.serial_no = "\n".join(map(lambda x: x.strip(), serial_nos))
d.save()
def validate_serial_no_warehouse(self, obj, fname):
for d in getlist(obj.doclist, fname):
wh = d.warehouse or d.s_warehouse
if cstr(d.serial_no).strip() and wh:
serial_nos = get_valid_serial_nos(d.serial_no)
for s in serial_nos:
s = s.strip()
sr_war = webnotes.conn.sql("select warehouse,name from `tabSerial No` where name = '%s'" % (s))
if not sr_war:
msgprint("Serial No %s does not exists"%s, raise_exception = 1)
elif not sr_war[0][0]:
msgprint("Warehouse not mentioned in the Serial No <b>%s</b>" % s, raise_exception = 1)
elif sr_war[0][0] != wh:
msgprint("Serial No : %s for Item : %s doesn't exists in Warehouse : %s" % (s, d.item_code, wh), raise_exception = 1)
def validate_serial_no(self, obj, fname):
"""check whether serial no is required"""
for d in getlist(obj.doclist, fname):
is_stock_item = webnotes.conn.get_value('Item', d.item_code, 'is_stock_item')
ar_required = webnotes.conn.get_value('Item', d.item_code, 'has_serial_no')
# [bug fix] need to strip serial nos of all spaces and new lines for validation
serial_no = cstr(d.serial_no).strip()
if serial_no:
if is_stock_item != 'Yes':
msgprint("Serial No is not required for non-stock item: %s" % d.item_code, raise_exception=1)
elif ar_required != 'Yes':
msgprint("If serial no required, please select 'Yes' in 'Has Serial No' in Item :" + d.item_code + \
', otherwise please remove serial no', raise_exception=1)
elif ar_required == 'Yes' and not serial_no and d.qty:
msgprint("Serial no is mandatory for item: "+ d.item_code, raise_exception = 1)
# validate rejected serial nos
if fname == 'purchase_receipt_details' and flt(d.rejected_qty) > 0 and ar_required == 'Yes' and not d.rejected_serial_no:
msgprint("Rejected serial no is mandatory for rejected qty of item: "+ d.item_code, raise_exception = 1)
def set_pur_serial_no_values(self, obj, serial_no, d, s, new_rec, rejected=None):
item_details = webnotes.conn.sql("""select item_group, warranty_period
from `tabItem` where name = '%s' and (ifnull(end_of_life,'')='' or
end_of_life = '0000-00-00' or end_of_life > now()) """ %(d.item_code), as_dict=1)
s.purchase_document_type = obj.doc.doctype
s.purchase_document_no = obj.doc.name
s.purchase_date = obj.doc.posting_date
s.purchase_time = obj.doc.posting_time
s.purchase_rate = d.valuation_rate or d.incoming_rate
s.item_code = d.item_code
s.item_name = d.item_name
s.brand = d.brand
s.description = d.description
s.item_group = item_details and item_details[0]['item_group'] or ''
s.warranty_period = item_details and item_details[0]['warranty_period'] or 0
s.supplier = obj.doc.supplier
s.supplier_name = obj.doc.supplier_name
s.address_display = obj.doc.address_display or obj.doc.supplier_address
s.warehouse = rejected and obj.doc.rejected_warehouse \
or d.warehouse or d.t_warehouse or ""
s.docstatus = 0
s.status = 'In Store'
s.modified = nowdate()
s.modified_by = session['user']
s.serial_no = serial_no
s.sle_exists = 1
s.company = obj.doc.company
s.save(new_rec)
def update_serial_purchase_details(self, obj, d, serial_no, is_submit, purpose = '', rejected=None):
exists = webnotes.conn.sql("select name, status, docstatus from `tabSerial No` where name = '%s'" % (serial_no))
if is_submit:
if exists and exists[0][2] != 2 and \
purpose not in ['Material Transfer', "Material Receipt", 'Sales Return']:
msgprint("Serial No: %s already %s" % (serial_no, exists and exists[0][1]), raise_exception = 1)
elif exists:
s = Document('Serial No', exists and exists[0][0])
self.set_pur_serial_no_values(obj, serial_no, d, s, new_rec = 0, rejected=rejected)
else:
s = Document('Serial No')
self.set_pur_serial_no_values(obj, serial_no, d, s, new_rec = 1, rejected=rejected)
else:
if exists and exists[0][1] == 'Delivered' and exists[0][2] != 2:
msgprint("Serial No: %s is already delivered, you can not cancel the document." % serial_no, raise_exception=1)
elif purpose == 'Material Transfer':
webnotes.conn.sql("update `tabSerial No` set status = 'In Store', purchase_document_type = '', purchase_document_no = '', warehouse = '%s' where name = '%s'" % (d.s_warehouse, serial_no))
elif purpose == 'Sales Return':
webnotes.conn.sql("update `tabSerial No` set status = 'Delivered', purchase_document_type = '', purchase_document_no = '' where name = '%s'" % serial_no)
else:
webnotes.conn.sql("update `tabSerial No` set docstatus = 2, status = 'Not in Use', purchase_document_type = '', purchase_document_no = '', purchase_date = null, purchase_rate = 0, supplier = null, supplier_name = '', address_display = '', warehouse = '' where name = '%s'" % serial_no)
def check_serial_no_exists(self, serial_no, item_code):
chk = webnotes.conn.sql("select name, status, docstatus, item_code from `tabSerial No` where name = %s", (serial_no), as_dict=1)
if not chk:
msgprint("Serial No: %s does not exists in the system" % serial_no, raise_exception=1)
elif chk and chk[0]['item_code'] != item_code:
msgprint("Serial No: %s not belong to item: %s" % (serial_no, item_code), raise_exception=1)
elif chk and chk[0]['docstatus'] == 2:
msgprint("Serial No: %s of Item : %s is trashed in the system" % (serial_no, item_code), raise_exception = 1)
elif chk and chk[0]['status'] == 'Delivered':
msgprint("Serial No: %s of Item : %s is already delivered." % (serial_no, item_code), raise_exception = 1)
def set_delivery_serial_no_values(self, obj, serial_no):
s = Document('Serial No', serial_no)
s.delivery_document_type = obj.doc.doctype
s.delivery_document_no = obj.doc.name
s.delivery_date = obj.doc.posting_date
s.delivery_time = obj.doc.posting_time
s.customer = obj.doc.customer
s.customer_name = obj.doc.customer_name
s.delivery_address = obj.doc.address_display
s.territory = obj.doc.territory
s.warranty_expiry_date = cint(s.warranty_period) and \
add_days(cstr(obj.doc.posting_date), cint(s.warranty_period)) or s.warranty_expiry_date
s.docstatus = 1
s.status = 'Delivered'
s.modified = nowdate()
s.modified_by = session['user']
s.save()
def update_serial_delivery_details(self, obj, d, serial_no, is_submit):
if is_submit:
self.check_serial_no_exists(serial_no, d.item_code)
self.set_delivery_serial_no_values(obj, serial_no)
else:
webnotes.conn.sql("update `tabSerial No` set docstatus = 0, status = 'In Store', delivery_document_type = '', delivery_document_no = '', delivery_date = null, customer = null, customer_name = '', delivery_address = '', territory = null where name = '%s'" % (serial_no))
def update_serial_record(self, obj, fname, is_submit = 1, is_incoming = 0):
for d in getlist(obj.doclist, fname):
if d.serial_no:
serial_nos = get_valid_serial_nos(d.serial_no)
for a in serial_nos:
serial_no = a.strip()
if is_incoming:
self.update_serial_purchase_details(obj, d, serial_no, is_submit)
else:
self.update_serial_delivery_details(obj, d, serial_no, is_submit)
if fname == 'purchase_receipt_details' and d.rejected_qty and d.rejected_serial_no:
serial_nos = get_valid_serial_nos(d.rejected_serial_no)
for a in serial_nos:
self.update_serial_purchase_details(obj, d, a, is_submit, rejected=True)
def update_stock(self, values, is_amended = 'No'): def update_stock(self, values, is_amended = 'No'):
for v in values: for v in values:
sle_id, valid_serial_nos = '', '' sle_id = ''
# get serial nos
if v.get("serial_no", "").strip():
valid_serial_nos = get_valid_serial_nos(v["serial_no"],
v['actual_qty'], v['item_code'])
v["serial_no"] = valid_serial_nos and "\n".join(valid_serial_nos) or ""
# reverse quantities for cancel # reverse quantities for cancel
if v.get('is_cancelled') == 'Yes': if v.get('is_cancelled') == 'Yes':

View File

@ -3,11 +3,21 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import webnotes import webnotes
from webnotes import _, msgprint from webnotes import _, msgprint, ValidationError
from webnotes.utils import cint, flt, getdate from webnotes.utils import cint, flt, getdate, cstr
from webnotes.model.controller import DocListController from webnotes.model.controller import DocListController
class InvalidWarehouseCompany(Exception): pass class InvalidWarehouseCompany(ValidationError): pass
class SerialNoNotRequiredError(ValidationError): pass
class SerialNoRequiredError(ValidationError): pass
class SerialNoQtyError(ValidationError): pass
class SerialNoItemError(ValidationError): pass
class SerialNoWarehouseError(ValidationError): pass
class SerialNoStatusError(ValidationError): pass
class SerialNoNotExistsError(ValidationError): pass
def get_serial_nos(serial_no):
return [s.strip() for s in cstr(serial_no).strip().replace(',', '\n').split('\n') if s.strip()]
class DocType(DocListController): class DocType(DocListController):
def __init__(self, doc, doclist=[]): def __init__(self, doc, doclist=[]):
@ -15,6 +25,9 @@ class DocType(DocListController):
self.doclist = doclist self.doclist = doclist
def validate(self): def validate(self):
if not hasattr(webnotes, "new_stock_ledger_entries"):
webnotes.new_stock_ledger_entries = []
webnotes.new_stock_ledger_entries.append(self.doc)
self.validate_mandatory() self.validate_mandatory()
self.validate_item() self.validate_item()
self.validate_warehouse_user() self.validate_warehouse_user()
@ -56,10 +69,10 @@ class DocType(DocListController):
self.doc.warehouse + ", " + self.doc.company +")", self.doc.warehouse + ", " + self.doc.company +")",
raise_exception=InvalidWarehouseCompany) raise_exception=InvalidWarehouseCompany)
def validate_mandatory(self): def validate_mandatory(self):
mandatory = ['warehouse','posting_date','voucher_type','voucher_no','actual_qty','company'] mandatory = ['warehouse','posting_date','voucher_type','voucher_no','actual_qty','company']
for k in mandatory: for k in mandatory:
if self.doc.fields.get(k)==None: if not self.doc.fields.get(k):
msgprint("Stock Ledger Entry: '%s' is mandatory" % k, raise_exception = 1) msgprint("Stock Ledger Entry: '%s' is mandatory" % k, raise_exception = 1)
elif k == 'warehouse': elif k == 'warehouse':
if not webnotes.conn.sql("select name from tabWarehouse where name = '%s'" % self.doc.fields.get(k)): if not webnotes.conn.sql("select name from tabWarehouse where name = '%s'" % self.doc.fields.get(k)):
@ -67,35 +80,102 @@ class DocType(DocListController):
def validate_item(self): def validate_item(self):
item_det = webnotes.conn.sql("""select name, has_batch_no, docstatus, item_det = webnotes.conn.sql("""select name, has_batch_no, docstatus,
ifnull(is_stock_item, 'No') from tabItem where name=%s""", is_stock_item, has_serial_no, serial_no_series
self.doc.item_code) from tabItem where name=%s""",
self.doc.item_code, as_dict=True)[0]
# check item exists if item_det.is_stock_item != 'Yes':
if item_det: webnotes.throw("""Item: "%s" is not a Stock Item.""" % self.doc.item_code)
item_det = item_det and item_det[0]
else:
msgprint("Item: '%s' does not exist in the system. Please check." % self.doc.item_code, raise_exception = 1)
if item_det[3]!='Yes':
webnotes.msgprint("""Item: "%s" is not a Stock Item.""" % self.doc.item_code,
raise_exception=1)
# check if item is trashed
if cint(item_det[2])==2:
msgprint("Item: '%s' is trashed, cannot make a stock transaction against a trashed item" % self.doc.item_code, raise_exception = 1)
# check if batch number is required # check if batch number is required
if item_det[1]=='Yes' and self.doc.voucher_type != 'Stock Reconciliation': if item_det.has_batch_no =='Yes' and self.doc.voucher_type != 'Stock Reconciliation':
if not self.doc.batch_no: if not self.doc.batch_no:
msgprint("Batch number is mandatory for Item '%s'" % self.doc.item_code, raise_exception = 1) webnotes.throw("Batch number is mandatory for Item '%s'" % self.doc.item_code)
raise Exception
# check if batch belongs to item # check if batch belongs to item
if not webnotes.conn.sql("select name from `tabBatch` where item='%s' and name ='%s' and docstatus != 2" % (self.doc.item_code, self.doc.batch_no)): if not webnotes.conn.sql("""select name from `tabBatch`
msgprint("'%s' is not a valid Batch Number for Item '%s'" % (self.doc.batch_no, self.doc.item_code), raise_exception = 1) where item='%s' and name ='%s' and docstatus != 2""" % (self.doc.item_code, self.doc.batch_no)):
webnotes.throw("'%s' is not a valid Batch Number for Item '%s'" % (self.doc.batch_no, self.doc.item_code))
# Nobody can do SL Entries where posting date is before freezing date except authorized person self.validate_serial_no(item_det)
#----------------------------------------------------------------------------------------------
def validate_serial_no(self, item_det):
if item_det.has_serial_no=="No":
if self.doc.serial_no:
webnotes.throw(_("Serial Number should be blank for Non Serialized Item" + ": " + self.doc.item),
SerialNoNotRequiredError)
else:
if self.doc.serial_no:
serial_nos = get_serial_nos(self.doc.serial_no)
if cint(self.doc.actual_qty) != flt(self.doc.actual_qty):
webnotes.throw(_("Serial No qty cannot be a fraction") + \
(": %s (%s)" % (self.doc.item_code, self.doc.actual_qty)))
if len(serial_nos) and len(serial_nos) != abs(cint(self.doc.actual_qty)):
webnotes.throw(_("Serial Nos do not match with qty") + \
(": %s (%s)" % (self.doc.item_code, self.doc.actual_qty)), SerialNoQtyError)
# check serial no exists, if yes then source
for serial_no in serial_nos:
if webnotes.conn.exists("Serial No", serial_no):
sr = webnotes.bean("Serial No", serial_no)
if sr.doc.item_code!=self.doc.item_code:
webnotes.throw(_("Serial No does not belong to Item") + \
(": %s (%s)" % (self.doc.item_code, serial_no)), SerialNoItemError)
sr.make_controller().via_stock_ledger = True
if self.doc.actual_qty < 0:
if sr.doc.warehouse!=self.doc.warehouse:
webnotes.throw(_("Warehouse does not belong to Item") + \
(": %s (%s)" % (self.doc.item_code, serial_no)), SerialNoWarehouseError)
if self.doc.voucher_type in ("Delivery Note", "Sales Invoice") \
and sr.doc.status != "Available":
webnotes.throw(_("Serial No status must be 'Available' to Deliver") + \
": " + serial_no, SerialNoStatusError)
sr.doc.warehouse = None
sr.save()
else:
sr.doc.warehouse = self.doc.warehouse
sr.save()
else:
if self.doc.actual_qty < 0:
# transfer out
webnotes.throw(_("Serial No must exist to transfer out.") + \
": " + serial_no, SerialNoNotExistsError)
else:
# transfer in
self.make_serial_no(serial_no)
else:
if item_det.serial_no_series:
from webnotes.model.doc import make_autoname
serial_nos = []
for i in xrange(cint(self.doc.actual_qty)):
serial_nos.append(self.make_serial_no(make_autoname(item_det.serial_no_series)))
self.doc.serial_no = "\n".join(serial_nos)
else:
webnotes.throw(_("Serial Number Required for Serialized Item" + ": " + self.doc.item),
SerialNoRequiredError)
def make_serial_no(self, serial_no):
sr = webnotes.new_bean("Serial No")
sr.doc.serial_no = serial_no
sr.doc.status = "Available"
sr.doc.item_code = self.doc.item_code
sr.doc.warehouse = self.doc.warehouse
sr.doc.purchase_rate = self.doc.incoming_rate
sr.doc.purchase_document_type = self.doc.voucher_type
sr.doc.purchase_document_no = self.doc.voucher_no
sr.doc.purchase_date = self.doc.posting_date
sr.doc.purchase_time = self.doc.posting_time
sr.make_controller().via_stock_ledger = True
sr.insert()
webnotes.msgprint(_("Serial No created") + ": " + sr.doc.name)
return sr.doc.name
def check_stock_frozen_date(self): def check_stock_frozen_date(self):
stock_frozen_upto = webnotes.conn.get_value('Stock Settings', None, 'stock_frozen_upto') or '' stock_frozen_upto = webnotes.conn.get_value('Stock Settings', None, 'stock_frozen_upto') or ''
if stock_frozen_upto: if stock_frozen_upto:
@ -107,6 +187,21 @@ class DocType(DocListController):
if not self.doc.posting_time or self.doc.posting_time == '00:0': if not self.doc.posting_time or self.doc.posting_time == '00:0':
self.doc.posting_time = '00:00' self.doc.posting_time = '00:00'
def update_serial_nos_after_submit(controller, parenttype, parentfield):
if not hasattr(webnotes, "new_stock_ledger_entries"):
return
for d in controller.doclist.get({"parentfield": parentfield}):
serial_no = None
for sle in webnotes.new_stock_ledger_entries:
if sle.voucher_detail_no==d.name:
serial_no = sle.serial_no
break
if d.serial_no != serial_no:
d.serial_no = serial_no
webnotes.conn.set_value(d.doctype, d.name, "serial_no", serial_no)
def on_doctype_update(): def on_doctype_update():
if not webnotes.conn.sql("""show index from `tabStock Ledger Entry` if not webnotes.conn.sql("""show index from `tabStock Ledger Entry`
where Key_name="posting_sort_index" """): where Key_name="posting_sort_index" """):

View File

@ -193,10 +193,6 @@ class DocType(TransactionBase):
if not chk1: if not chk1:
msgprint("Serial no "+x+" does not exist in system.") msgprint("Serial no "+x+" does not exist in system.")
raise Exception raise Exception
else:
if status=='In Store' or status=='Note in Use' or status=='Scrapped':
msgprint("Serial no "+x+" is '"+status+"'")
raise Exception
def validate(self): def validate(self):
self.validate_maintenance_detail() self.validate_maintenance_detail()

View File

@ -28,6 +28,7 @@ def make(reset=False):
webnotes.connect() webnotes.connect()
webnotes.print_messages = True webnotes.print_messages = True
webnotes.mute_emails = True webnotes.mute_emails = True
webnotes.rollback_on_exception = True
if reset: if reset:
setup() setup()