diff --git a/erpnext/controllers/item_variant.py b/erpnext/controllers/item_variant.py
index f40d519b92..821c81cad3 100644
--- a/erpnext/controllers/item_variant.py
+++ b/erpnext/controllers/item_variant.py
@@ -239,3 +239,20 @@ def make_variant_item_code(template_item_code, template_item_name, variant):
if abbreviations:
variant.item_code = "{0}-{1}".format(template_item_code, "-".join(abbreviations))
variant.item_name = "{0}-{1}".format(template_item_name, "-".join(abbreviations))
+
+@frappe.whitelist()
+def create_variant_doc_for_quick_entry(template, args):
+ variant_based_on = frappe.db.get_value("Item", template, "variant_based_on")
+ args = json.loads(args)
+ if variant_based_on == "Manufacturer":
+ variant = get_variant(template, **args)
+ else:
+ existing_variant = get_variant(template, args)
+ if existing_variant:
+ return existing_variant
+ else:
+ variant = create_variant(template, args=args)
+ variant.name = variant.item_code
+ validate_item_variant_attributes(variant, args)
+ return variant.as_dict()
+
diff --git a/erpnext/public/build.json b/erpnext/public/build.json
index 393e90c052..0730df96b2 100644
--- a/erpnext/public/build.json
+++ b/erpnext/public/build.json
@@ -32,7 +32,9 @@
"public/js/utils/item_selector.js",
"public/js/help_links.js",
"public/js/schools/student_button.html",
- "public/js/schools/assessment_result_tool.html"
+ "public/js/schools/assessment_result_tool.html",
+ "public/js/templates/item_quick_entry.html",
+ "public/js/utils/item_quick_entry.js"
],
"js/item-dashboard.min.js": [
"stock/dashboard/item_dashboard.html",
diff --git a/erpnext/public/js/templates/item_quick_entry.html b/erpnext/public/js/templates/item_quick_entry.html
new file mode 100644
index 0000000000..6a5f36da77
--- /dev/null
+++ b/erpnext/public/js/templates/item_quick_entry.html
@@ -0,0 +1,3 @@
+
{{ __("Variant Attributes") }}
+
+
\ No newline at end of file
diff --git a/erpnext/public/js/utils/item_quick_entry.js b/erpnext/public/js/utils/item_quick_entry.js
new file mode 100644
index 0000000000..ebb92dac8f
--- /dev/null
+++ b/erpnext/public/js/utils/item_quick_entry.js
@@ -0,0 +1,408 @@
+frappe.provide('frappe.ui.form');
+
+frappe.ui.form.ItemQuickEntryForm = frappe.ui.form.QuickEntryForm.extend({
+ init: function(doctype, after_insert) {
+ this._super(doctype, after_insert);
+ },
+
+ render_dialog: function() {
+ this.mandatory = this.get_variant_fields().concat(this.mandatory);
+ this.mandatory = this.mandatory.concat(this.get_attributes_fields());
+ this._super();
+ this.init_post_render_dialog_operations();
+ this.preset_fields_for_template();
+ this.dialog.$wrapper.find('.edit-full').text(__('Edit in full page for more options like assets, serial nos, batches etc.'))
+ },
+
+ init_post_render_dialog_operations: function() {
+ this.dialog.fields_dict.attribute_html.$wrapper.append(frappe.render_template("item_quick_entry"));
+ this.init_for_create_variant_trigger();
+ this.init_for_item_template_trigger();
+ // explicitly hide manufacturing fields as hidden not working.
+ this.toggle_manufacturer_fields();
+ this.dialog.get_field("item_template").df.hidden = 1;
+ this.dialog.get_field("item_template").refresh();
+ },
+
+ register_primary_action: function() {
+ var me = this;
+ this.dialog.set_primary_action(__('Save'), function() {
+ if (me.dialog.working) return;
+
+ var data = me.dialog.get_values();
+ var variant_values = {};
+
+ if (me.dialog.fields_dict.create_variant.$input.prop("checked")) {
+ variant_values = me.get_variant_doc();
+ if (!Object.keys(variant_values).length) {
+ data = null;
+ }
+ variant_values.stock_uom = me.template_doc.stock_uom;
+ variant_values.item_group = me.template_doc.item_group;
+ }
+
+ if (data) {
+ me.dialog.working = true;
+ var values = me.update_doc();
+ //patch for manufacturer type variants as extend is overwriting it.
+ if (variant_values['variant_based_on'] == "Manufacturer") {
+ values['variant_based_on'] = "Manufacturer";
+ }
+ $.extend(variant_values, values);
+ me.insert(variant_values);
+ }
+ });
+ },
+
+ insert: function(variant_values) {
+ let me = this;
+ return new Promise(resolve => {
+ frappe.call({
+ method: "frappe.client.insert",
+ args: {
+ doc: variant_values
+ },
+ callback: function(r) {
+ me.dialog.hide();
+ // delete the old doc
+ frappe.model.clear_doc(me.dialog.doc.doctype, me.dialog.doc.name);
+ me.dialog.doc = r.message;
+ if (frappe._from_link) {
+ frappe.ui.form.update_calling_link(me.dialog.doc);
+ } else {
+ if (me.after_insert) {
+ me.after_insert(me.dialog.doc);
+ } else {
+ me.open_from_if_not_list();
+ }
+ }
+ },
+ error: function() {
+ me.open_doc();
+ },
+ always: function() {
+ me.dialog.working = false;
+ resolve(me.dialog.doc);
+ },
+ freeze: true
+ });
+ });
+ },
+
+ open_doc: function() {
+ this.dialog.hide();
+ this.update_doc();
+ if (this.dialog.fields_dict.create_variant.$input.prop("checked")) {
+ var template = this.dialog.fields_dict.item_template.input.value;
+ if (template)
+ frappe.set_route("Form", this.doctype, template);
+ } else {
+ frappe.set_route('Form', this.doctype, this.doc.name);
+ }
+ },
+
+ get_variant_fields: function() {
+ var variant_fields = [{
+ fieldname: "create_variant",
+ fieldtype: "Check",
+ label: __("Create Variant")
+ },
+ {
+ fieldname: 'item_template',
+ label: __('Item Template'),
+ reqd: 0,
+ fieldtype: 'Link',
+ options: "Item",
+ get_query: function() {
+ return {
+ filters: {
+ "has_variants": 1
+ }
+ };
+ }
+ }];
+
+ return variant_fields;
+ },
+
+ get_manufacturing_fields: function() {
+ this.manufacturer_fields = [{
+ fieldtype: 'Link',
+ options: 'Manufacturer',
+ label: 'Manufacturer',
+ fieldname: "manufacturer",
+ hidden: 1,
+ reqd: 0
+ }, {
+ fieldtype: 'Data',
+ label: 'Manufacturer Part Number',
+ fieldname: 'manufacturer_part_no',
+ hidden: 1,
+ reqd: 0
+ }];
+ return this.manufacturer_fields;
+ },
+
+ get_attributes_fields: function() {
+ var attribute_fields = [{
+ fieldname: 'attribute_html',
+ fieldtype: 'HTML'
+ }]
+
+ attribute_fields = attribute_fields.concat(this.get_manufacturing_fields());
+ return attribute_fields;
+ },
+
+ init_for_create_variant_trigger: function() {
+ var me = this;
+
+ this.dialog.fields_dict.create_variant.$input.on("click", function() {
+ me.preset_fields_for_template();
+ me.init_post_template_trigger_operations(false, [], true);
+ });
+ },
+
+ preset_fields_for_template: function() {
+ var for_variant = this.dialog.get_value('create_variant');
+
+ // setup template field, seen and mandatory if variant
+ let template_field = this.dialog.get_field("item_template");
+ template_field.df.reqd = for_variant;
+ template_field.set_value('');
+ template_field.df.hidden = !for_variant;
+ template_field.refresh();
+
+ // hide properties for variant
+ ['item_code', 'item_name', 'item_group', 'stock_uom'].forEach((d) => {
+ let f = this.dialog.get_field(d);
+ f.df.hidden = for_variant;
+ f.refresh();
+ });
+
+ this.dialog.get_field('attribute_html').toggle(false);
+
+ // non mandatory for variants
+ ['item_code', 'stock_uom', 'item_group'].forEach((d) => {
+ let f = this.dialog.get_field(d);
+ f.df.reqd = !for_variant;
+ f.refresh();
+ });
+
+ },
+
+ init_for_item_template_trigger: function() {
+ var me = this;
+
+ me.dialog.fields_dict["item_template"].df.onchange = () => {
+ var template = me.dialog.fields_dict.item_template.input.value;
+ me.template_doc = null;
+ if (template) {
+ frappe.call({
+ method: "frappe.client.get",
+ args: {
+ doctype: "Item",
+ name: template
+ },
+ callback: function(r) {
+ me.template_doc = r.message;
+ me.is_manufacturer = false;
+
+ if (me.template_doc.variant_based_on === "Manufacturer") {
+ me.init_post_template_trigger_operations(true, [], true);
+ } else {
+
+ me.init_post_template_trigger_operations(false, me.template_doc.attributes, false);
+ me.render_attributes(me.template_doc.attributes);
+ }
+ }
+ });
+ } else {
+ me.dialog.get_field('attribute_html').toggle(false);
+ me.init_post_template_trigger_operations(false, [], true);
+ }
+ }
+ },
+
+ init_post_template_trigger_operations: function(is_manufacturer, attributes, attributes_flag) {
+ this.attributes = attributes;
+ this.attribute_values = {};
+ this.attributes_count = attributes.length;
+
+ this.dialog.fields_dict.attribute_html.$wrapper.find(".attributes").empty();
+ this.is_manufacturer = is_manufacturer;
+ this.toggle_manufacturer_fields();
+ this.dialog.fields_dict.attribute_html.$wrapper.find(".attributes").toggleClass("hide-control", attributes_flag);
+ this.dialog.fields_dict.attribute_html.$wrapper.find(".attributes-header").toggleClass("hide-control", attributes_flag);
+ },
+
+ toggle_manufacturer_fields: function() {
+ var me = this;
+ $.each(this.manufacturer_fields, function(i, dialog_field) {
+ me.dialog.get_field(dialog_field.fieldname).df.hidden = !me.is_manufacturer;
+ me.dialog.get_field(dialog_field.fieldname).df.reqd = dialog_field.fieldname == 'manufacturer' ? me.is_manufacturer : false;
+ me.dialog.get_field(dialog_field.fieldname).refresh();
+ });
+ },
+
+ initiate_render_attributes: function() {
+ this.dialog.fields_dict.attribute_html.$wrapper.find(".attributes").empty();
+ this.render_attributes(this.attributes);
+ },
+
+ render_attributes: function(attributes) {
+ var me = this;
+
+ this.dialog.get_field('attribute_html').toggle(true);
+
+ $.each(attributes, function(index, row) {
+ var desc = "";
+ var fieldtype = "Data";
+ if (row.numeric_values) {
+ fieldtype = "Float";
+ desc = "Min Value: " + row.from_range + " , Max Value: " + row.to_range + ", in Increments of: " + row.increment;
+ }
+
+ me.init_make_control(fieldtype, row);
+ me[row.attribute].set_value(me.attribute_values[row.attribute] || "");
+ me[row.attribute].$wrapper.toggleClass("has-error", me.attribute_values[row.attribute] ? false : true);
+
+ // Set Label explicitly as make_control is not displaying label
+ $(me[row.attribute].label_area).text(__(row.attribute));
+
+ if (desc) {
+ $(repl(`%(desc)s
`, {
+ "desc": desc
+ })).insertAfter(me[row.attribute].input_area);
+ }
+
+ if (!row.numeric_values) {
+ me.init_awesomplete_for_attribute(row);
+ } else {
+ me[row.attribute].$input.on("change", function() {
+ me.attribute_values[row.attribute] = $(this).val();
+ $(this).closest(".frappe-control").toggleClass("has-error", $(this).val() ? false : true);
+ });
+ }
+ });
+ },
+
+ init_make_control: function(fieldtype, row) {
+ this[row.attribute] = frappe.ui.form.make_control({
+ df: {
+ "fieldtype": fieldtype,
+ "label": row.attribute,
+ "fieldname": row.attribute,
+ "options": row.options || ""
+ },
+ parent: $(this.dialog.fields_dict.attribute_html.wrapper).find(".attributes"),
+ only_input: false
+ });
+ this[row.attribute].make_input();
+ },
+
+ init_awesomplete_for_attribute: function(row) {
+ var me = this;
+
+ this[row.attribute].input.awesomplete = new Awesomplete(this[row.attribute].input, {
+ minChars: 0,
+ maxItems: 99,
+ autoFirst: true,
+ list: [],
+ });
+
+ this[row.attribute].$input.on('input', function(e) {
+ frappe.call({
+ method: "frappe.client.get_list",
+ args: {
+ doctype: "Item Attribute Value",
+ filters: [
+ ["parent", "=", $(e.target).attr("data-fieldname")],
+ ["attribute_value", "like", e.target.value + "%"]
+ ],
+ fields: ["attribute_value"]
+ },
+ callback: function(r) {
+ if (r.message) {
+ e.target.awesomplete.list = r.message.map(function(d) {
+ return d.attribute_value;
+ });
+ }
+ }
+ });
+ }).on('focus', function(e) {
+ $(e.target).val('').trigger('input');
+ }).on("awesomplete-close", function (e) {
+ me.attribute_values[$(e.target).attr("data-fieldname")] = e.target.value;
+ $(e.target).closest(".frappe-control").toggleClass("has-error", e.target.value ? false : true);
+ });
+ },
+
+ get_variant_doc: function() {
+ var me = this;
+ var variant_doc = {};
+ var attribute = this.validate_mandatory_attributes();
+
+ if (Object.keys(attribute).length) {
+ frappe.call({
+ method: "erpnext.controllers.item_variant.create_variant_doc_for_quick_entry",
+ args: {
+ "template": me.dialog.fields_dict.item_template.$input.val(),
+ args: attribute
+ },
+ async: false,
+ callback: function(r) {
+ if (Object.prototype.toString.call(r.message) == "[object Object]") {
+ variant_doc = r.message;
+ } else {
+ var msgprint_dialog = frappe.msgprint(__("Item Variant {0} already exists with same attributes", [repl('%(item)s', {
+ item: r.message
+ })]));
+
+ msgprint_dialog.$wrapper.find(".variant-click").on("click", function() {
+ msgprint_dialog.hide();
+ me.dialog.hide();
+ if (frappe._from_link) {
+ frappe._from_link.set_value($(this).attr("data-item-code"));
+ } else {
+ frappe.set_route('Form', "Item", $(this).attr("data-item-code"));
+ }
+ });
+ }
+ }
+ })
+ }
+ return variant_doc;
+ },
+
+ validate_mandatory_attributes: function() {
+ var me = this;
+ var attribute = {};
+ var mandatory = [];
+
+ $.each(this.attributes, function(index, attr) {
+ var value = me.attribute_values[attr.attribute] || "";
+ if (value) {
+ attribute[attr.attribute] = attr.numeric_values ? flt(value) : value;
+ } else {
+ mandatory.push(attr.attribute);
+ }
+ })
+
+ if (mandatory.length) {
+ frappe.msgprint({
+ title: __('Missing Values Required'),
+ message: __('Following fields have missing values:') + '
- ' + mandatory.join('
- ') + '
',
+ indicator: 'orange'
+ });
+ return {};
+ }
+
+ if (this.is_manufacturer) {
+ $.each(this.manufacturer_fields, function(index, field) {
+ attribute[field.fieldname] = me.dialog.fields_dict[field.fieldname].input.value;
+ });
+ }
+ return attribute;
+ }
+});
\ No newline at end of file
diff --git a/erpnext/stock/dashboard/item_dashboard.js b/erpnext/stock/dashboard/item_dashboard.js
index 113a2efb50..3c334c46b6 100644
--- a/erpnext/stock/dashboard/item_dashboard.js
+++ b/erpnext/stock/dashboard/item_dashboard.js
@@ -80,7 +80,7 @@ erpnext.stock.ItemDashboard = Class.extend({
$(frappe.render_template('item_dashboard_list', context)).appendTo(this.result);
} else {
var message = __(" Currently no stock available in any warehouse")
- $(" "+message+"").appendTo(this.result);
+ $(""+message+"").appendTo(this.result);
}
},
get_item_dashboard_data: function(data, max_count, show_item) {
diff --git a/erpnext/stock/doctype/item/item.js b/erpnext/stock/doctype/item/item.js
index 6f3fce5ba2..656ee69020 100644
--- a/erpnext/stock/doctype/item/item.js
+++ b/erpnext/stock/doctype/item/item.js
@@ -74,19 +74,8 @@ frappe.ui.form.on("Item", {
}
erpnext.item.edit_prices_button(frm);
-
- // make sensitive fields(has_serial_no, is_stock_item, valuation_method, has_batch_no)
- // read only if any stock ledger entry exists
- if (!frm.doc.__islocal && frm.doc.is_stock_item) {
- frm.toggle_enable(['has_serial_no', 'is_stock_item', 'valuation_method', 'has_batch_no'],
- (frm.doc.__onload && frm.doc.__onload.sle_exists=="exists") ? false : true);
- }
-
erpnext.item.toggle_attributes(frm);
- frm.toggle_enable("is_fixed_asset", (frm.doc.__islocal || (!frm.doc.is_stock_item &&
- ((frm.doc.__onload && frm.doc.__onload.asset_exists) ? false : true))));
-
frm.add_custom_button(__('Duplicate'), function() {
var new_item = frappe.model.copy_doc(frm.doc);
if(new_item.item_name===new_item.item_code) {
@@ -103,25 +92,18 @@ frappe.ui.form.on("Item", {
frappe.set_route("Form", "Item Variant Settings");
}, __("View"));
}
-
- if(frm.doc.__onload && frm.doc.__onload.stock_exists) {
- // Hide variants section if stock exists
- frm.toggle_display("variants_section", 0);
- }
},
validate: function(frm){
erpnext.item.weight_to_validate(frm);
},
- image: function(frm) {
+ image: function() {
refresh_field("image_view");
},
is_fixed_asset: function(frm) {
- if (frm.doc.is_fixed_asset) {
- frm.set_value("is_stock_item", 0);
- }
+ frm.set_value("is_stock_item", frm.doc.is_fixed_asset ? 0 : 1);
},
page_name: frappe.utils.warn_page_name_change,
@@ -469,5 +451,6 @@ $.extend(erpnext.item, {
// nothing to do with attributes, hide it
frm.toggle_display("attributes", false);
}
+ frm.layout.refresh_sections();
}
});
diff --git a/erpnext/stock/doctype/item/item.json b/erpnext/stock/doctype/item/item.json
index b86ad46985..bfb4c6227e 100644
--- a/erpnext/stock/doctype/item/item.json
+++ b/erpnext/stock/doctype/item/item.json
@@ -138,7 +138,7 @@
"report_hide": 0,
"reqd": 0,
"search_index": 0,
- "set_only_once": 0,
+ "set_only_once": 1,
"unique": 0
},
{
@@ -358,7 +358,7 @@
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
- "bold": 0,
+ "bold": 1,
"collapsible": 0,
"columns": 0,
"default": "1",
@@ -384,9 +384,9 @@
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
- "reqd": 1,
+ "reqd": 0,
"search_index": 0,
- "set_only_once": 0,
+ "set_only_once": 1,
"unique": 0
},
{
@@ -508,7 +508,7 @@
"report_hide": 0,
"reqd": 0,
"search_index": 0,
- "set_only_once": 0,
+ "set_only_once": 1,
"unique": 0
},
{
@@ -891,7 +891,7 @@
"report_hide": 0,
"reqd": 0,
"search_index": 0,
- "set_only_once": 0,
+ "set_only_once": 1,
"unique": 0
},
{
@@ -1180,7 +1180,7 @@
"report_hide": 0,
"reqd": 0,
"search_index": 0,
- "set_only_once": 0,
+ "set_only_once": 1,
"unique": 0
},
{
@@ -1307,7 +1307,7 @@
"report_hide": 0,
"reqd": 0,
"search_index": 0,
- "set_only_once": 0,
+ "set_only_once": 1,
"unique": 0
},
{
@@ -1437,7 +1437,7 @@
"report_hide": 0,
"reqd": 0,
"search_index": 0,
- "set_only_once": 0,
+ "set_only_once": 1,
"unique": 0
},
{
@@ -1446,7 +1446,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
- "depends_on": "eval:doc.has_variants && doc.variant_based_on==='Item Attribute'",
+ "depends_on": "eval:(doc.has_variants || doc.variant_of) && doc.variant_based_on==='Item Attribute'",
"fieldname": "attributes",
"fieldtype": "Table",
"hidden": 1,
@@ -1469,7 +1469,7 @@
"report_hide": 0,
"reqd": 0,
"search_index": 0,
- "set_only_once": 0,
+ "set_only_once": 1,
"unique": 0
},
{
@@ -3360,7 +3360,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 1,
- "modified": "2017-11-01 11:53:52.060505",
+ "modified": "2017-11-13 15:49:13.213990",
"modified_by": "Administrator",
"module": "Stock",
"name": "Item",
diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py
index 6a6af3d583..d6c4b7d1db 100644
--- a/erpnext/stock/doctype/item/item.py
+++ b/erpnext/stock/doctype/item/item.py
@@ -30,14 +30,11 @@ class Item(WebsiteGenerator):
def onload(self):
super(Item, self).onload()
- self.set_onload('sle_exists', self.check_if_sle_exists())
+ self.set_onload('stock_exists', self.stock_ledger_created())
if self.is_fixed_asset:
asset = frappe.db.get_all("Asset", filters={"item_code": self.name, "docstatus": 1}, limit=1)
self.set_onload("asset_exists", True if asset else False)
- if frappe.db.get_value('Stock Ledger Entry', {'item_code': self.name}):
- self.set_onload('stock_exists', True)
-
def autoname(self):
if frappe.db.get_default("item_naming_by")=="Naming Series":
if self.variant_of:
@@ -60,6 +57,9 @@ class Item(WebsiteGenerator):
if self.is_sales_item and not self.get('is_item_from_hub'):
self.publish_in_hub = 1
+ def before_save(self):
+ self.get_doc_before_save()
+
def after_insert(self):
'''set opening stock and item price'''
if self.standard_rate:
@@ -89,7 +89,6 @@ class Item(WebsiteGenerator):
self.fill_customer_code()
self.check_item_tax()
self.validate_barcode()
- self.cant_change()
self.validate_warehouse_for_reorder()
self.update_bom_item_desc()
self.synced_with_hub = 0
@@ -247,6 +246,14 @@ class Item(WebsiteGenerator):
if not self.asset_category:
frappe.throw(_("Asset Category is mandatory for Fixed Asset item"))
+ if self.stock_ledger_created():
+ frappe.throw(_("Cannot be a fixed asset item as Stock Ledger is created."))
+
+ if not self.is_fixed_asset:
+ asset = frappe.db.get_all("Asset", filters={"item_code": self.name, "docstatus": 1}, limit=1)
+ if asset:
+ frappe.throw(_('"Is Fixed Asset" cannot be unchecked, as Asset record exists against the item'))
+
def get_context(self, context):
context.show_search=True
context.search_link = '/product_search'
@@ -456,62 +463,27 @@ class Item(WebsiteGenerator):
if duplicate:
frappe.throw(_("Barcode {0} already used in Item {1}").format(self.barcode, duplicate[0][0]))
- def cant_change(self):
- if not self.get("__islocal"):
- to_check = ("has_serial_no", "is_stock_item",
- "valuation_method", "has_batch_no", "is_fixed_asset")
-
- vals = frappe.db.get_value("Item", self.name, to_check, as_dict=True)
- if not vals.get('valuation_method') and self.get('valuation_method'):
- vals['valuation_method'] = frappe.db.get_single_value("Stock Settings", "valuation_method") or "FIFO"
-
- if vals:
- for key in to_check:
- if cstr(self.get(key)) != cstr(vals.get(key)):
- if not self.check_if_linked_document_exists(key):
- break # no linked document, allowed
- else:
- frappe.throw(_("As there are existing transactions against item {0}, you can not change the value of {1}").format(self.name, frappe.bold(self.meta.get_label(key))))
-
- if vals and not self.is_fixed_asset and self.is_fixed_asset != vals.is_fixed_asset:
- asset = frappe.db.get_all("Asset", filters={"item_code": self.name, "docstatus": 1}, limit=1)
- if asset:
- frappe.throw(_('"Is Fixed Asset" cannot be unchecked, as Asset record exists against the item'))
-
- def check_if_linked_document_exists(self, key):
- linked_doctypes = ["Delivery Note Item", "Sales Invoice Item", "Purchase Receipt Item",
- "Purchase Invoice Item", "Stock Entry Detail", "Stock Reconciliation Item"]
-
- # For "Is Stock Item", following doctypes is important
- # because reserved_qty, ordered_qty and requested_qty updated from these doctypes
- if key == "is_stock_item":
- linked_doctypes += ["Sales Order Item", "Purchase Order Item", "Material Request Item"]
-
- for doctype in linked_doctypes:
- if frappe.db.get_value(doctype, filters={"item_code": self.name, "docstatus": 1}) or \
- frappe.db.get_value("Production Order",
- filters={"production_item": self.name, "docstatus": 1}):
- return True
+ def validate_warehouse_for_reorder(self):
+ '''Validate Reorder level table for duplicate and conditional mandatory'''
+ warehouse = []
for d in self.get("reorder_levels"):
+ if not d.warehouse_group:
+ d.warehouse_group = d.warehouse
+ if d.get("warehouse") and d.get("warehouse") not in warehouse:
+ warehouse += [d.get("warehouse")]
+ else:
+ frappe.throw(_("Row {0}: An Reorder entry already exists for this warehouse {1}")
+ .format(d.idx, d.warehouse), DuplicateReorderRows)
+
if d.warehouse_reorder_level and not d.warehouse_reorder_qty:
frappe.throw(_("Row #{0}: Please set reorder quantity").format(d.idx))
- def validate_warehouse_for_reorder(self):
- warehouse = []
- for i in self.get("reorder_levels"):
- if not i.warehouse_group:
- i.warehouse_group = i.warehouse
- if i.get("warehouse") and i.get("warehouse") not in warehouse:
- warehouse += [i.get("warehouse")]
- else:
- frappe.throw(_("Row {0}: An Reorder entry already exists for this warehouse {1}")
- .format(i.idx, i.warehouse), DuplicateReorderRows)
-
- def check_if_sle_exists(self):
- sle = frappe.db.sql("""select name from `tabStock Ledger Entry`
- where item_code = %s""", self.name)
- return sle and 'exists' or 'not exists'
+ def stock_ledger_created(self):
+ if not hasattr(self, '_stock_ledger_created'):
+ self._stock_ledger_created = len(frappe.db.sql("""select name from `tabStock Ledger Entry`
+ where item_code = %s limit 1""", self.name))
+ return self._stock_ledger_created
def validate_name_with_item_group(self):
# causes problem with tree build
@@ -638,19 +610,19 @@ class Item(WebsiteGenerator):
template_item.save()
def update_variants(self):
- if self.flags.dont_update_variants or \
- frappe.db.get_single_value('Item Variant Settings', 'do_not_update_variants'):
- return
- if self.has_variants:
- updated = []
- variants = frappe.db.get_all("Item", fields=["item_code"], filters={"variant_of": self.name })
- for d in variants:
- variant = frappe.get_doc("Item", d)
- copy_attributes_to_variant(self, variant)
- variant.save()
- updated.append(d.item_code)
- if updated:
- frappe.msgprint(_("Item Variants {0} updated").format(", ".join(updated)))
+ if self.flags.dont_update_variants or \
+ frappe.db.get_single_value('Item Variant Settings', 'do_not_update_variants'):
+ return
+ if self.has_variants:
+ updated = []
+ variants = frappe.db.get_all("Item", fields=["item_code"], filters={"variant_of": self.name })
+ for d in variants:
+ variant = frappe.get_doc("Item", d)
+ copy_attributes_to_variant(self, variant)
+ variant.save()
+ updated.append(d.item_code)
+ if updated:
+ frappe.msgprint(_("Item Variants {0} updated").format(", ".join(updated)))
def validate_has_variants(self):
if not self.has_variants and frappe.db.get_value("Item", self.name, "has_variants"):
@@ -658,10 +630,15 @@ class Item(WebsiteGenerator):
frappe.throw(_("Item has variants."))
def validate_stock_exists_for_template_item(self):
- if self.has_variants and \
- frappe.db.get_value('Stock Ledger Entry', {'item_code': self.name}):
- frappe.throw(_("As stock exists against an item {0}, you can not enable has variants property")
- .format(self.name), StockExistsForTemplate)
+ if self.stock_ledger_created():
+ if (self._doc_before_save.has_variants != self.has_variants or
+ self.variant_of != self._doc_before_save.variant_of):
+ frappe.throw(_("Cannot change Variant properties after stock transction. You will have to make a new Item to do this.").format(self.name),
+ StockExistsForTemplate)
+
+ if self.has_variants or self.variant_of:
+ if not self.is_child_table_same('attributes'):
+ frappe.throw(_('Cannot change Attributes after stock transaction. Make a new Item and transfer stock to the new Item'))
def validate_uom(self):
if not self.get("__islocal"):
diff --git a/erpnext/crm/doctype/item/test_item.js b/erpnext/stock/doctype/item/tests/test_item.js
similarity index 99%
rename from erpnext/crm/doctype/item/test_item.js
rename to erpnext/stock/doctype/item/tests/test_item.js
index c9b14ca1a9..5e3524e5b6 100644
--- a/erpnext/crm/doctype/item/test_item.js
+++ b/erpnext/stock/doctype/item/tests/test_item.js
@@ -1,3 +1,4 @@
+QUnit.module('stock');
QUnit.test("test: item", function (assert) {
assert.expect(6);
let done = assert.async();
diff --git a/erpnext/stock/doctype/warehouse/warehouse.py b/erpnext/stock/doctype/warehouse/warehouse.py
index 058828cc3d..5186408721 100644
--- a/erpnext/stock/doctype/warehouse/warehouse.py
+++ b/erpnext/stock/doctype/warehouse/warehouse.py
@@ -57,11 +57,11 @@ class Warehouse(NestedSet):
def check_if_sle_exists(self):
return frappe.db.sql("""select name from `tabStock Ledger Entry`
- where warehouse = %s""", self.name)
+ where warehouse = %s limit 1""", self.name)
def check_if_child_exists(self):
return frappe.db.sql("""select name from `tabWarehouse`
- where parent_warehouse = %s""", self.name)
+ where parent_warehouse = %s limit 1""", self.name)
def before_rename(self, old_name, new_name, merge=False):
super(Warehouse, self).before_rename(old_name, new_name, merge)
diff --git a/erpnext/tests/ui/tests.txt b/erpnext/tests/ui/tests.txt
index 6cf379472c..cbaaf10762 100644
--- a/erpnext/tests/ui/tests.txt
+++ b/erpnext/tests/ui/tests.txt
@@ -9,7 +9,7 @@ erpnext/accounts/doctype/shipping_rule/test_shipping_rule.js
erpnext/crm/doctype/lead/test_lead.js
erpnext/crm/doctype/opportunity/test_opportunity.js
erpnext/setup/doctype/company/tests/test_company_production.js
-erpnext/crm/doctype/item/test_item.js
+erpnext/stock/doctype/item/tests/test_item.js
erpnext/selling/doctype/quotation/tests/test_quotation_with_discount_on_grand_total.js
erpnext/selling/doctype/quotation/tests/test_quotation_with_item_wise_discount.js
erpnext/selling/doctype/quotation/tests/test_quotation_with_multi_uom.js