[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:
parent
ffe64db8f7
commit
62030e05cc
@ -181,7 +181,12 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte
|
||||
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
|
||||
|
@ -65,9 +65,6 @@ class DocType(SellingController):
|
||||
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()
|
||||
@ -85,14 +82,8 @@ class DocType(SellingController):
|
||||
|
||||
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)
|
||||
|
||||
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 @@ class DocType(SellingController):
|
||||
|
||||
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 @@ class DocType(SellingController):
|
||||
|
||||
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:
|
||||
|
@ -645,6 +645,60 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
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 = [
|
||||
|
@ -46,10 +46,10 @@ cur_frm.fields_dict['item_serial_no'].get_query = function(doc, cdt, cdn) {
|
||||
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 }
|
||||
}
|
@ -271,3 +271,32 @@ class SellingController(StockController):
|
||||
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()
|
||||
|
@ -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")
|
5
patches/august_2013/p05_update_serial_no_status.py
Normal file
5
patches/august_2013/p05_update_serial_no_status.py
Normal 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'""")
|
@ -39,7 +39,7 @@ def execute():
|
||||
|
||||
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'
|
||||
|
||||
|
@ -22,7 +22,6 @@ patch_list = [
|
||||
"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 @@ patch_list = [
|
||||
"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",
|
||||
]
|
@ -36,4 +36,46 @@ $.extend(erpnext, {
|
||||
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();
|
||||
});
|
||||
}
|
||||
});
|
@ -105,7 +105,6 @@ class DocType(TransactionBase):
|
||||
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):
|
||||
|
@ -71,8 +71,21 @@ def get_item_details(args):
|
||||
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)
|
||||
|
||||
|
@ -75,6 +75,10 @@ erpnext.stock.DeliveryNoteController = erpnext.selling.SellingController.extend(
|
||||
this.get_terms();
|
||||
},
|
||||
|
||||
delivery_note_details_on_form_rendered: function(doc, grid_row) {
|
||||
erpnext.setup_serial_no(grid_row)
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
// for backward compatibility: combine new and previous states
|
||||
|
@ -175,9 +175,6 @@ class DocType(SellingController):
|
||||
|
||||
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 @@ class DocType(SellingController):
|
||||
# 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 @@ class DocType(SellingController):
|
||||
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 @@ class DocType(SellingController):
|
||||
+ ", 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 @@ class DocType(SellingController):
|
||||
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
|
||||
|
@ -97,6 +97,59 @@ class TestDeliveryNote(unittest.TestCase):
|
||||
|
||||
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 = [
|
||||
[
|
||||
{
|
||||
|
@ -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"
|
||||
},
|
||||
@ -300,6 +300,14 @@
|
||||
"read_only": 0,
|
||||
"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",
|
||||
|
@ -194,4 +194,25 @@ test_records = [
|
||||
"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"
|
||||
}],
|
||||
]
|
@ -127,8 +127,6 @@ class DocType(BuyingController):
|
||||
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 @@ class DocType(BuyingController):
|
||||
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 @@ class DocType(BuyingController):
|
||||
|
||||
# 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 @@ class DocType(BuyingController):
|
||||
'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,21 +240,35 @@ class DocType(BuyingController):
|
||||
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))
|
||||
if submit_rv:
|
||||
@ -295,10 +292,10 @@ class DocType(BuyingController):
|
||||
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()
|
||||
|
||||
|
@ -77,6 +77,29 @@ class TestPurchaseReceipt(unittest.TestCase):
|
||||
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"]
|
||||
|
||||
|
@ -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"
|
||||
}
|
||||
}
|
@ -10,10 +10,22 @@ 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,79 +43,41 @@ class DocType(StockController):
|
||||
|
||||
|
||||
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)
|
||||
|
||||
|
||||
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.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)
|
||||
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)
|
||||
|
||||
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
|
||||
|
||||
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"""
|
||||
if merge:
|
||||
@ -119,18 +93,3 @@ class DocType(StockController):
|
||||
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)
|
@ -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,10 +96,25 @@
|
||||
"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",
|
||||
@ -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
|
||||
},
|
||||
@ -225,21 +242,6 @@
|
||||
"read_only": 0,
|
||||
"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",
|
||||
"fieldname": "supplier",
|
||||
@ -248,7 +250,7 @@
|
||||
"label": "Supplier",
|
||||
"no_copy": 1,
|
||||
"options": "Supplier",
|
||||
"read_only": 0
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"doctype": "DocField",
|
||||
@ -259,14 +261,6 @@
|
||||
"no_copy": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"doctype": "DocField",
|
||||
"fieldname": "address_display",
|
||||
"fieldtype": "Text",
|
||||
"label": "Supplier Address",
|
||||
"no_copy": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"doctype": "DocField",
|
||||
"fieldname": "delivery_details",
|
||||
@ -275,13 +269,6 @@
|
||||
"oldfieldtype": "Column Break",
|
||||
"read_only": 0
|
||||
},
|
||||
{
|
||||
"doctype": "DocField",
|
||||
"fieldname": "column_break4",
|
||||
"fieldtype": "Column Break",
|
||||
"read_only": 0,
|
||||
"width": "50%"
|
||||
},
|
||||
{
|
||||
"doctype": "DocField",
|
||||
"fieldname": "delivery_document_type",
|
||||
@ -301,15 +288,6 @@
|
||||
"no_copy": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"doctype": "DocField",
|
||||
"fieldname": "customer_address",
|
||||
"fieldtype": "Text",
|
||||
"label": "Customer Address",
|
||||
"oldfieldname": "customer_address",
|
||||
"oldfieldtype": "Text",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"doctype": "DocField",
|
||||
"fieldname": "delivery_date",
|
||||
@ -374,28 +352,6 @@
|
||||
"read_only": 1,
|
||||
"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",
|
||||
"fieldname": "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
|
||||
},
|
||||
@ -500,26 +456,6 @@
|
||||
"reqd": 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,
|
||||
"create": 1,
|
||||
|
@ -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"
|
||||
}
|
||||
]
|
||||
]
|
||||
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)
|
@ -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.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);
|
||||
|
@ -32,7 +32,6 @@ class DocType(StockController):
|
||||
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 @@ class DocType(StockController):
|
||||
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()
|
||||
|
||||
@ -75,11 +74,6 @@ class DocType(StockController):
|
||||
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"}):
|
||||
if item.item_code not in self.stock_items:
|
||||
@ -206,7 +200,7 @@ class DocType(StockController):
|
||||
"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 @@ class DocType(StockController):
|
||||
|
||||
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)
|
||||
|
||||
for serial_no in get_serial_nos(d.serial_no):
|
||||
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()
|
||||
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 = []
|
||||
|
@ -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 @@ class TestStockEntry(unittest.TestCase):
|
||||
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 @@ class TestStockEntry(unittest.TestCase):
|
||||
|
||||
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",
|
||||
|
@ -18,173 +18,9 @@ class DocType:
|
||||
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':
|
||||
|
@ -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 @@ class DocType(DocListController):
|
||||
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()
|
||||
@ -59,7 +72,7 @@ class DocType(DocListController):
|
||||
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 @@ class DocType(DocListController):
|
||||
|
||||
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))
|
||||
|
||||
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
|
||||
|
||||
# Nobody can do SL Entries where posting date is before freezing date except authorized person
|
||||
#----------------------------------------------------------------------------------------------
|
||||
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 @@ class DocType(DocListController):
|
||||
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" """):
|
||||
|
@ -193,10 +193,6 @@ class DocType(TransactionBase):
|
||||
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()
|
||||
|
@ -28,6 +28,7 @@ def make(reset=False):
|
||||
webnotes.connect()
|
||||
webnotes.print_messages = True
|
||||
webnotes.mute_emails = True
|
||||
webnotes.rollback_on_exception = True
|
||||
|
||||
if reset:
|
||||
setup()
|
||||
|
Loading…
Reference in New Issue
Block a user