[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
diff --git a/accounts/doctype/sales_invoice/sales_invoice.js b/accounts/doctype/sales_invoice/sales_invoice.js
index 8db8a72..b571813 100644
--- a/accounts/doctype/sales_invoice/sales_invoice.js
+++ b/accounts/doctype/sales_invoice/sales_invoice.js
@@ -181,7 +181,12 @@
set_dynamic_labels: function() {
this._super();
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
diff --git a/accounts/doctype/sales_invoice/sales_invoice.py b/accounts/doctype/sales_invoice/sales_invoice.py
index c9059cc..e4adcea 100644
--- a/accounts/doctype/sales_invoice/sales_invoice.py
+++ b/accounts/doctype/sales_invoice/sales_invoice.py
@@ -65,9 +65,6 @@
self.validate_write_off_account()
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.update_current_stock()
self.validate_delivery_note()
@@ -84,15 +81,9 @@
"delivery_note_details")
def on_submit(self):
- 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)
-
+ if cint(self.doc.update_stock) == 1:
self.update_stock_ledger(update_stock=1)
+ self.update_serial_nos()
else:
# Check for Approving Authority
if not self.doc.recurring_id:
@@ -120,11 +111,8 @@
def on_cancel(self):
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_serial_nos(cancel = True)
sales_com_obj = get_obj(dt = 'Sales Common')
sales_com_obj.check_stop_sales_order(self)
@@ -489,10 +477,6 @@
def make_packing_list(self):
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):
if cint(self.doc.update_stock) == 1:
diff --git a/accounts/doctype/sales_invoice/test_sales_invoice.py b/accounts/doctype/sales_invoice/test_sales_invoice.py
index cdd61b9..5976ce4 100644
--- a/accounts/doctype/sales_invoice/test_sales_invoice.py
+++ b/accounts/doctype/sales_invoice/test_sales_invoice.py
@@ -644,7 +644,61 @@
count = no_of_months == 12 and 3 or 13
for i in xrange(count):
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_records = [
diff --git a/buying/doctype/quality_inspection/quality_inspection.js b/buying/doctype/quality_inspection/quality_inspection.js
index 0c865a5..8c7c932 100644
--- a/buying/doctype/quality_inspection/quality_inspection.js
+++ b/buying/doctype/quality_inspection/quality_inspection.js
@@ -46,10 +46,10 @@
if (doc.item_code) {
filter = {
'item_code': doc.item_code,
- 'status': "In Store"
+ 'status': "Available"
}
} else
- filter = { 'status': "In Store" }
+ filter = { 'status': "Available" }
return { filters: filter }
}
\ No newline at end of file
diff --git a/controllers/selling_controller.py b/controllers/selling_controller.py
index fb762af..2e1f1ea 100644
--- a/controllers/selling_controller.py
+++ b/controllers/selling_controller.py
@@ -271,3 +271,32 @@
msgprint(_(self.meta.get_label("order_type")) + " " +
_("must be one of") + ": " + comma_or(valid_types),
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()
diff --git a/patches/april_2012/serial_no_fixes.py b/patches/april_2012/serial_no_fixes.py
deleted file mode 100644
index 4fef016..0000000
--- a/patches/april_2012/serial_no_fixes.py
+++ /dev/null
@@ -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")
diff --git a/patches/august_2013/p05_update_serial_no_status.py b/patches/august_2013/p05_update_serial_no_status.py
new file mode 100644
index 0000000..49ab411
--- /dev/null
+++ b/patches/august_2013/p05_update_serial_no_status.py
@@ -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'""")
\ No newline at end of file
diff --git a/patches/july_2012/packing_list_cleanup_and_serial_no.py b/patches/july_2012/packing_list_cleanup_and_serial_no.py
index ad3863e..b91ef81 100644
--- a/patches/july_2012/packing_list_cleanup_and_serial_no.py
+++ b/patches/july_2012/packing_list_cleanup_and_serial_no.py
@@ -39,7 +39,7 @@
status = 'Not in Use'
if sle and flt(sle[0]['actual_qty']) > 0:
- status = 'In Store'
+ status = 'Available'
elif sle and flt(sle[0]['actual_qty']) < 0:
status = 'Delivered'
diff --git a/patches/patch_list.py b/patches/patch_list.py
index 9e6938b..99514d2 100644
--- a/patches/patch_list.py
+++ b/patches/patch_list.py
@@ -22,7 +22,6 @@
"patches.april_2012.update_role_in_address",
"patches.april_2012.update_permlevel_in_address",
"patches.april_2012.update_appraisal_permission",
- "patches.april_2012.serial_no_fixes",
"patches.april_2012.repost_stock_for_posting_time",
"patches.may_2012.cleanup_property_setter",
"patches.may_2012.rename_prev_doctype",
@@ -255,4 +254,5 @@
"patches.august_2013.p02_rename_price_list",
"patches.august_2013.p03_pos_setting_replace_customer_account",
"patches.august_2013.p04_employee_birthdays",
+ "patches.august_2013.p05_update_serial_no_status",
]
\ No newline at end of file
diff --git a/public/js/utils.js b/public/js/utils.js
index aee55ba..9d4ea14 100644
--- a/public/js/utils.js
+++ b/public/js/utils.js
@@ -36,4 +36,46 @@
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();
+ });
+ }
});
\ No newline at end of file
diff --git a/selling/doctype/installation_note/installation_note.py b/selling/doctype/installation_note/installation_note.py
index 93a1bc1..ca47043 100644
--- a/selling/doctype/installation_note/installation_note.py
+++ b/selling/doctype/installation_note/installation_note.py
@@ -105,7 +105,6 @@
msgprint("Please fetch items from Delivery Note selected", raise_exception=1)
def on_update(self):
- get_obj("Stock Ledger").scrub_serial_nos(self, 'installed_item_details')
webnotes.conn.set(self.doc, 'status', 'Draft')
def on_submit(self):
diff --git a/selling/utils.py b/selling/utils.py
index 7ccad6a..86b1b9d 100644
--- a/selling/utils.py
+++ b/selling/utils.py
@@ -70,9 +70,22 @@
if cint(args.is_pos):
pos_settings = get_pos_settings(args.company)
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
-
+
+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):
item_code = webnotes.conn.sql_list("""select name from `tabItem` where barcode=%s""", barcode)
diff --git a/stock/doctype/delivery_note/delivery_note.js b/stock/doctype/delivery_note/delivery_note.js
index 6d601c2..063b258 100644
--- a/stock/doctype/delivery_note/delivery_note.js
+++ b/stock/doctype/delivery_note/delivery_note.js
@@ -74,6 +74,10 @@
tc_name: function() {
this.get_terms();
},
+
+ delivery_note_details_on_form_rendered: function(doc, grid_row) {
+ erpnext.setup_serial_no(grid_row)
+ }
});
diff --git a/stock/doctype/delivery_note/delivery_note.py b/stock/doctype/delivery_note/delivery_note.py
index 679d743..6e44f04 100644
--- a/stock/doctype/delivery_note/delivery_note.py
+++ b/stock/doctype/delivery_note/delivery_note.py
@@ -175,9 +175,6 @@
def on_update(self):
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):
self.validate_packed_qty()
@@ -185,22 +182,12 @@
# Check for Approving Authority
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
self.update_prevdoc_status()
# create stock ledger entry
self.update_stock_ledger(update_stock = 1)
+ self.update_serial_nos()
self.credit_limit()
@@ -211,6 +198,50 @@
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):
"""
Validate that if packed qty exists, it should be equal to qty
@@ -232,26 +263,6 @@
+ ", Packed: " + cstr(d[2])) for d in packing_error_list])
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):
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:
@@ -263,7 +274,6 @@
msgprint("Installation Note : "+cstr(submit_in[0][0]) +" has already been submitted !")
raise Exception , "Validation Error."
-
def cancel_packing_slips(self):
"""
Cancel submitted packing slips related to this delivery note
diff --git a/stock/doctype/delivery_note/test_delivery_note.py b/stock/doctype/delivery_note/test_delivery_note.py
index c1f09dd..89690fe 100644
--- a/stock/doctype/delivery_note/test_delivery_note.py
+++ b/stock/doctype/delivery_note/test_delivery_note.py
@@ -96,6 +96,59 @@
self.assertEquals(bal, prev_bal - 375.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 = [
[
diff --git a/stock/doctype/item/item.txt b/stock/doctype/item/item.txt
index 7ceeb4b..8b17aee 100644
--- a/stock/doctype/item/item.txt
+++ b/stock/doctype/item/item.txt
@@ -2,7 +2,7 @@
{
"creation": "2013-05-03 10:45:46",
"docstatus": 0,
- "modified": "2013-08-08 14:22:25",
+ "modified": "2013-08-14 11:46:49",
"modified_by": "Administrator",
"owner": "Administrator"
},
@@ -301,6 +301,14 @@
"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\"",
"doctype": "DocField",
"fieldname": "warranty_period",
diff --git a/stock/doctype/item/test_item.py b/stock/doctype/item/test_item.py
index a1d7852..b9b67e2 100644
--- a/stock/doctype/item/test_item.py
+++ b/stock/doctype/item/test_item.py
@@ -194,4 +194,25 @@
"is_sub_contracted_item": "No",
"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"
+ }],
]
\ No newline at end of file
diff --git a/stock/doctype/purchase_receipt/purchase_receipt.py b/stock/doctype/purchase_receipt/purchase_receipt.py
index c78e759..f7cfcff 100644
--- a/stock/doctype/purchase_receipt/purchase_receipt.py
+++ b/stock/doctype/purchase_receipt/purchase_receipt.py
@@ -127,8 +127,6 @@
self.validate_inspection()
self.validate_uom_is_integer("uom", ["qty", "received_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()
pc_obj = get_obj(dt='Purchase Common')
@@ -147,16 +145,6 @@
for d in getlist(self.doclist,'purchase_receipt_details'):
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):
pc_obj = get_obj('Purchase Common')
self.values = []
@@ -207,11 +195,6 @@
# make Stock Entry
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({
'item_code' : d.fields.has_key('item_code') and d.item_code or d.rm_item_code,
'warehouse' : wh,
@@ -227,7 +210,7 @@
'fiscal_year' : self.doc.fiscal_year,
'is_cancelled' : (is_submit==1) and 'No' or 'Yes',
'batch_no' : cstr(d.batch_no).strip(),
- 'serial_no' : serial_no,
+ 'serial_no' : d.serial_no,
"project" : d.project_name
})
@@ -257,20 +240,34 @@
get_obj('Authorization Control').validate_approving_authority(self.doc.doctype, self.doc.company, self.doc.grand_total)
# Set status as Submitted
- webnotes.conn.set(self.doc,'status', 'Submitted')
+ webnotes.conn.set(self.doc, 'status', 'Submitted')
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
self.update_stock(is_submit = 1)
+ self.update_serial_nos()
+
# Update last purchase rate
purchase_controller.update_last_purchase_rate(self, 1)
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):
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 @@
webnotes.conn.set(self.doc,'status','Cancelled')
# 3. Cancel Serial No
- get_obj('Stock Ledger').update_serial_record(self, 'purchase_receipt_details', is_submit = 0, is_incoming = 1)
# 4.Update Bin
self.update_stock(is_submit = 0)
+ self.update_serial_nos(cancel=True)
self.update_prevdoc_status()
diff --git a/stock/doctype/purchase_receipt/test_purchase_receipt.py b/stock/doctype/purchase_receipt/test_purchase_receipt.py
index c871b36..e303d79 100644
--- a/stock/doctype/purchase_receipt/test_purchase_receipt.py
+++ b/stock/doctype/purchase_receipt/test_purchase_receipt.py
@@ -76,8 +76,31 @@
self.assertEquals(pr.doclist[1].rm_supp_cost, 70000.0)
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_records = [
diff --git a/stock/doctype/serial_no/serial_no.js b/stock/doctype/serial_no/serial_no.js
index 8d2f210..5b2cb0f 100644
--- a/stock/doctype/serial_no/serial_no.js
+++ b/stock/doctype/serial_no/serial_no.js
@@ -1,69 +1,3 @@
// Copyright (c) 2013, Web Notes Technologies Pvt. Ltd.
// 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"
- }
-}
\ No newline at end of file
diff --git a/stock/doctype/serial_no/serial_no.py b/stock/doctype/serial_no/serial_no.py
index 7e59d64..2ea7359 100644
--- a/stock/doctype/serial_no/serial_no.py
+++ b/stock/doctype/serial_no/serial_no.py
@@ -7,13 +7,25 @@
from webnotes.utils import cint, getdate, nowdate
import datetime
from webnotes import msgprint, _
-
+
from controllers.stock_controller import StockController
+class SerialNoCannotCreateDirectError(webnotes.ValidationError): pass
+
class DocType(StockController):
def __init__(self, doc, doclist=[]):
self.doc = doc
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):
"""
@@ -31,78 +43,40 @@
def validate_warehouse(self):
- if self.doc.status=='In Store' and not self.doc.warehouse:
- msgprint("Warehouse is mandatory if this Serial No is <b>In Store</b>", raise_exception=1)
+ if not self.doc.fields.get("__islocal"):
+ 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):
"""
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)
- if not item:
- msgprint("Item is not exists in the system", raise_exception=1)
- 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)
+ item = webnotes.doc("Item", self.doc.item_code)
+ if item.has_serial_no!="Yes":
+ webnotes.throw(_("Item must have 'Has Serial No' as 'Yes'") + ": " + self.doc.item_code)
-
- def validate(self):
- self.validate_warranty_status()
- self.validate_amc_status()
- self.validate_warehouse()
- 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.doc.item_group = item.item_group
+ self.doc.description = item.description
+ self.doc.item_name = item.item_name
+ self.doc.brand = item.brand
+ self.doc.warranty_period = item.warranty_period
- 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):
if self.doc.status == 'Delivered':
msgprint("Cannot trash Serial No : %s as it is already Delivered" % (self.doc.name), raise_exception = 1)
- elif self.doc.status == 'In Store':
- webnotes.conn.set(self.doc, 'status', 'Not in Use')
- self.make_stock_ledger_entry(-1)
-
- 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)
-
+ if self.doc.warehouse:
+ webnotes.throw(_("Cannot delete Serial No in warehouse. First remove from warehouse, then delete.") + \
+ ": " + self.doc.name)
def on_cancel(self):
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):
"""rename serial_no text fields"""
@@ -119,18 +93,3 @@
webnotes.conn.sql("""update `tab%s` set serial_no = %s
where name=%s""" % (dt[0], '%s', '%s'),
('\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)
\ No newline at end of file
diff --git a/stock/doctype/serial_no/serial_no.txt b/stock/doctype/serial_no/serial_no.txt
index efa35f5..f7d340e 100644
--- a/stock/doctype/serial_no/serial_no.txt
+++ b/stock/doctype/serial_no/serial_no.txt
@@ -2,7 +2,7 @@
{
"creation": "2013-05-16 10:59:15",
"docstatus": 0,
- "modified": "2013-07-22 15:29:43",
+ "modified": "2013-08-14 18:26:23",
"modified_by": "Administrator",
"owner": "Administrator"
},
@@ -12,8 +12,9 @@
"autoname": "field:serial_no",
"description": "Distinct unit of an Item",
"doctype": "DocType",
- "document_type": "Master",
+ "document_type": "Other",
"icon": "icon-barcode",
+ "in_create": 1,
"module": "Stock",
"name": "__common__",
"search_fields": "item_code,status"
@@ -57,6 +58,7 @@
},
{
"default": "In Store",
+ "description": "Only Serial Nos with status \"In Store\" can be delivered.",
"doctype": "DocField",
"fieldname": "status",
"fieldtype": "Select",
@@ -66,8 +68,8 @@
"no_copy": 1,
"oldfieldname": "status",
"oldfieldtype": "Select",
- "options": "\nIn Store\nDelivered\nNot in Use\nPurchase Returned",
- "read_only": 1,
+ "options": "\nAvailable\nNot Available\nDelivered\nPurchase Returned\nSales Returned",
+ "read_only": 0,
"reqd": 1,
"search_index": 1
},
@@ -94,12 +96,27 @@
"oldfieldname": "item_code",
"oldfieldtype": "Link",
"options": "Item",
- "read_only": 0,
+ "read_only": 1,
"reqd": 1,
"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",
"fieldname": "column_break1",
"fieldtype": "Column Break",
"read_only": 0
@@ -134,7 +151,7 @@
"oldfieldtype": "Link",
"options": "Item Group",
"read_only": 1,
- "reqd": 1,
+ "reqd": 0,
"search_index": 0
},
{
@@ -154,7 +171,7 @@
"doctype": "DocField",
"fieldname": "purchase_details",
"fieldtype": "Section Break",
- "label": "Purchase Details",
+ "label": "Purchase / Manufacture Details",
"read_only": 0
},
{
@@ -168,30 +185,30 @@
"doctype": "DocField",
"fieldname": "purchase_document_type",
"fieldtype": "Select",
- "label": "Purchase Document Type",
+ "label": "Creation Document Type",
"no_copy": 1,
"options": "\nPurchase Receipt\nStock Entry",
- "read_only": 0
+ "read_only": 1
},
{
"doctype": "DocField",
"fieldname": "purchase_document_no",
"fieldtype": "Data",
"hidden": 0,
- "label": "Purchase Document No",
+ "label": "Creation Document No",
"no_copy": 1,
- "read_only": 0
+ "read_only": 1
},
{
"doctype": "DocField",
"fieldname": "purchase_date",
"fieldtype": "Date",
"in_filter": 1,
- "label": "Purchase Date",
+ "label": "Creation Date",
"no_copy": 1,
"oldfieldname": "purchase_date",
"oldfieldtype": "Date",
- "read_only": 0,
+ "read_only": 1,
"reqd": 0,
"search_index": 0
},
@@ -199,9 +216,9 @@
"doctype": "DocField",
"fieldname": "purchase_time",
"fieldtype": "Time",
- "label": "Incoming Time",
+ "label": "Creation Time",
"no_copy": 1,
- "read_only": 0,
+ "read_only": 1,
"reqd": 1
},
{
@@ -214,7 +231,7 @@
"oldfieldname": "purchase_rate",
"oldfieldtype": "Currency",
"options": "Company:company:default_currency",
- "read_only": 0,
+ "read_only": 1,
"reqd": 1,
"search_index": 0
},
@@ -227,28 +244,13 @@
},
{
"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",
"fieldname": "supplier",
"fieldtype": "Link",
"in_filter": 1,
"label": "Supplier",
"no_copy": 1,
"options": "Supplier",
- "read_only": 0
+ "read_only": 1
},
{
"doctype": "DocField",
@@ -261,14 +263,6 @@
},
{
"doctype": "DocField",
- "fieldname": "address_display",
- "fieldtype": "Text",
- "label": "Supplier Address",
- "no_copy": 1,
- "read_only": 1
- },
- {
- "doctype": "DocField",
"fieldname": "delivery_details",
"fieldtype": "Section Break",
"label": "Delivery Details",
@@ -277,13 +271,6 @@
},
{
"doctype": "DocField",
- "fieldname": "column_break4",
- "fieldtype": "Column Break",
- "read_only": 0,
- "width": "50%"
- },
- {
- "doctype": "DocField",
"fieldname": "delivery_document_type",
"fieldtype": "Select",
"in_filter": 1,
@@ -303,15 +290,6 @@
},
{
"doctype": "DocField",
- "fieldname": "customer_address",
- "fieldtype": "Text",
- "label": "Customer Address",
- "oldfieldname": "customer_address",
- "oldfieldtype": "Text",
- "read_only": 1
- },
- {
- "doctype": "DocField",
"fieldname": "delivery_date",
"fieldtype": "Date",
"label": "Delivery Date",
@@ -376,28 +354,6 @@
},
{
"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",
"fieldname": "warranty_amc_details",
"fieldtype": "Section Break",
"label": "Warranty / AMC Details",
@@ -485,7 +441,7 @@
"in_filter": 1,
"label": "Company",
"options": "link:Company",
- "read_only": 0,
+ "read_only": 1,
"reqd": 1,
"search_index": 1
},
@@ -501,26 +457,6 @@
"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,
"create": 1,
"doctype": "DocPerm",
diff --git a/stock/doctype/serial_no/test_serial_no.py b/stock/doctype/serial_no/test_serial_no.py
index 1898b2f..8d84930 100644
--- a/stock/doctype/serial_no/test_serial_no.py
+++ b/stock/doctype/serial_no/test_serial_no.py
@@ -7,99 +7,14 @@
from __future__ import unicode_literals
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_records = [
- [
- {
- "company": "_Test Company",
- "doctype": "Serial No",
- "serial_no": "_Test Serial No",
- "status": "In Store",
- "item_code": "_Test Serialized Item",
- "item_group": "_Test Item Group",
- "warehouse": "_Test Warehouse - _TC",
- "purchase_rate": 1000.0,
- "purchase_time": "11:37:39",
- "purchase_date": "2013-02-26",
- 'fiscal_year': "_Test Fiscal Year 2013"
- }
- ]
-]
\ No newline at end of file
+test_records = []
+
+from stock.doctype.serial_no.serial_no import *
+
+class TestSerialNo(unittest.TestCase):
+ def test_cannot_create_direct(self):
+ sr = webnotes.new_bean("Serial No")
+ sr.doc.item_code = "_Test Serialized Item"
+ sr.doc.purchase_rate = 10
+ self.assertRaises(SerialNoCannotCreateDirectError, sr.insert)
\ No newline at end of file
diff --git a/stock/doctype/stock_entry/stock_entry.js b/stock/doctype/stock_entry/stock_entry.js
index 53998f8..917e53d 100644
--- a/stock/doctype/stock_entry/stock_entry.js
+++ b/stock/doctype/stock_entry/stock_entry.js
@@ -223,6 +223,10 @@
if(!row.s_warehouse) row.s_warehouse = this.frm.doc.from_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);
diff --git a/stock/doctype/stock_entry/stock_entry.py b/stock/doctype/stock_entry/stock_entry.py
index b702316..151a5b4 100644
--- a/stock/doctype/stock_entry/stock_entry.py
+++ b/stock/doctype/stock_entry/stock_entry.py
@@ -32,7 +32,6 @@
def validate(self):
self.validate_posting_time()
self.validate_purpose()
- self.validate_serial_nos()
pro_obj = self.doc.production_order and \
get_obj('Production Order', self.doc.production_order) or None
@@ -52,14 +51,14 @@
self.set_total_amount()
def on_submit(self):
- self.update_serial_no(1)
self.update_stock_ledger(0)
+ self.update_serial_no(1)
self.update_production_order(1)
self.make_gl_entries()
def on_cancel(self):
- self.update_serial_no(0)
self.update_stock_ledger(1)
+ self.update_serial_no(0)
self.update_production_order(0)
self.make_cancel_gl_entries()
@@ -74,11 +73,6 @@
if self.doc.purpose not in valid_purposes:
msgprint(_("Purpose must be one of ") + comma_or(valid_purposes),
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):
for item in self.doclist.get({"parentfield": "mtn_details"}):
@@ -206,7 +200,7 @@
"posting_date": self.doc.posting_date,
"posting_time": self.doc.posting_time,
"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,
})
# get actual stock at source warehouse
@@ -317,27 +311,21 @@
def update_serial_no(self, is_submit):
"""Create / Update Serial No"""
- from stock.utils import get_valid_serial_nos
-
- sl_obj = get_obj('Stock Ledger')
- if is_submit:
- sl_obj.validate_serial_no_warehouse(self, 'mtn_details')
+
+ from stock.doctype.stock_ledger_entry.stock_ledger_entry import update_serial_nos_after_submit, get_serial_nos
+ update_serial_nos_after_submit(self, "Stock Entry", "mtn_details")
for d in getlist(self.doclist, 'mtn_details'):
- if cstr(d.serial_no).strip():
- for x in get_valid_serial_nos(d.serial_no):
- serial_no = x.strip()
- if d.s_warehouse:
- sl_obj.update_serial_delivery_details(self, d, serial_no, is_submit)
- if d.t_warehouse:
- sl_obj.update_serial_purchase_details(self, d, serial_no, is_submit,
- self.doc.purpose)
-
- if self.doc.purpose == 'Purchase Return':
- 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()
+ for serial_no in get_serial_nos(d.serial_no):
+ if self.doc.purpose == 'Purchase Return':
+ sr = webnotes.bean("Serial No", serial_no)
+ sr.doc.status = "Purchase Returned" if is_submit else "Available"
+ sr.save()
+
+ if self.doc.purpose == "Sales Return":
+ sr = webnotes.bean("Serial No", serial_no)
+ sr.doc.status = "Sales Returned" if is_submit else "Delivered"
+ sr.save()
def update_stock_ledger(self, is_cancelled=0):
self.values = []
diff --git a/stock/doctype/stock_entry/test_stock_entry.py b/stock/doctype/stock_entry/test_stock_entry.py
index 6438116..f33cfa3 100644
--- a/stock/doctype/stock_entry/test_stock_entry.py
+++ b/stock/doctype/stock_entry/test_stock_entry.py
@@ -7,6 +7,7 @@
from __future__ import unicode_literals
import webnotes, unittest
from webnotes.utils import flt
+from stock.doctype.stock_ledger_entry.stock_ledger_entry import *
class TestStockEntry(unittest.TestCase):
def tearDown(self):
@@ -180,6 +181,7 @@
def _clear_stock(self):
webnotes.conn.sql("delete from `tabStock Ledger Entry`")
webnotes.conn.sql("""delete from `tabBin`""")
+ webnotes.conn.sql("""delete from `tabSerial No`""")
self.old_default_company = webnotes.conn.get_default("company")
webnotes.conn.set_default("company", "_Test Company")
@@ -571,12 +573,139 @@
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 = [
[
{
"company": "_Test Company",
"doctype": "Stock Entry",
- "posting_date": "2013-01-25",
+ "posting_date": "2013-01-01",
"posting_time": "17:14:24",
"purpose": "Material Receipt",
"fiscal_year": "_Test Fiscal Year 2013",
diff --git a/stock/doctype/stock_ledger/stock_ledger.py b/stock/doctype/stock_ledger/stock_ledger.py
index 0af18b9..10fb761 100644
--- a/stock/doctype/stock_ledger/stock_ledger.py
+++ b/stock/doctype/stock_ledger/stock_ledger.py
@@ -17,174 +17,10 @@
def __init__(self, doc, doclist=[]):
self.doc = doc
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'):
for v in values:
- sle_id, valid_serial_nos = '', ''
- # 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 ""
+ sle_id = ''
# reverse quantities for cancel
if v.get('is_cancelled') == 'Yes':
diff --git a/stock/doctype/stock_ledger_entry/stock_ledger_entry.py b/stock/doctype/stock_ledger_entry/stock_ledger_entry.py
index 3fda3ba..6d193ef 100644
--- a/stock/doctype/stock_ledger_entry/stock_ledger_entry.py
+++ b/stock/doctype/stock_ledger_entry/stock_ledger_entry.py
@@ -3,11 +3,21 @@
from __future__ import unicode_literals
import webnotes
-from webnotes import _, msgprint
-from webnotes.utils import cint, flt, getdate
+from webnotes import _, msgprint, ValidationError
+from webnotes.utils import cint, flt, getdate, cstr
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):
def __init__(self, doc, doclist=[]):
@@ -15,6 +25,9 @@
self.doclist = doclist
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_item()
self.validate_warehouse_user()
@@ -56,10 +69,10 @@
self.doc.warehouse + ", " + self.doc.company +")",
raise_exception=InvalidWarehouseCompany)
- def validate_mandatory(self):
+ def validate_mandatory(self):
mandatory = ['warehouse','posting_date','voucher_type','voucher_no','actual_qty','company']
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)
elif k == 'warehouse':
if not webnotes.conn.sql("select name from tabWarehouse where name = '%s'" % self.doc.fields.get(k)):
@@ -67,35 +80,102 @@
def validate_item(self):
item_det = webnotes.conn.sql("""select name, has_batch_no, docstatus,
- ifnull(is_stock_item, 'No') from tabItem where name=%s""",
- self.doc.item_code)
+ is_stock_item, has_serial_no, serial_no_series
+ from tabItem where name=%s""",
+ self.doc.item_code, as_dict=True)[0]
- # check item exists
- if item_det:
- 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)
+ if item_det.is_stock_item != 'Yes':
+ webnotes.throw("""Item: "%s" is not a Stock Item.""" % self.doc.item_code)
# 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:
- msgprint("Batch number is mandatory for Item '%s'" % self.doc.item_code, raise_exception = 1)
- raise Exception
+ webnotes.throw("Batch number is mandatory for Item '%s'" % self.doc.item_code)
# 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)):
- msgprint("'%s' is not a valid Batch Number for Item '%s'" % (self.doc.batch_no, self.doc.item_code), raise_exception = 1)
+ 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)):
+ 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):
stock_frozen_upto = webnotes.conn.get_value('Stock Settings', None, 'stock_frozen_upto') or ''
if stock_frozen_upto:
@@ -107,6 +187,21 @@
if not self.doc.posting_time or self.doc.posting_time == '00:0':
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():
if not webnotes.conn.sql("""show index from `tabStock Ledger Entry`
where Key_name="posting_sort_index" """):
diff --git a/support/doctype/maintenance_schedule/maintenance_schedule.py b/support/doctype/maintenance_schedule/maintenance_schedule.py
index 8659b9f..59d3f8e 100644
--- a/support/doctype/maintenance_schedule/maintenance_schedule.py
+++ b/support/doctype/maintenance_schedule/maintenance_schedule.py
@@ -193,10 +193,6 @@
if not chk1:
msgprint("Serial no "+x+" does not exist in system.")
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):
self.validate_maintenance_detail()
diff --git a/utilities/make_demo.py b/utilities/make_demo.py
index 461bbef..600faaa 100644
--- a/utilities/make_demo.py
+++ b/utilities/make_demo.py
@@ -28,6 +28,7 @@
webnotes.connect()
webnotes.print_messages = True
webnotes.mute_emails = True
+ webnotes.rollback_on_exception = True
if reset:
setup()