Fixed conflict

This commit is contained in:
Nabin Hait 2015-09-03 16:04:11 +05:30
commit 11a746d363
23 changed files with 1194 additions and 1086 deletions

View File

@ -49,6 +49,20 @@ DocTypes are easy to create but hard to maintain. If you find that there is a an
Tabs!
#### Release Checklist
- Describe, in detail, what is in the pull request
- How to use the new feature?
- Test cases
- Change log
- Manual Pull Request Link
- Screencast. Should include:
- New Forms
- Linked Forms
- Linked Reports
- Print Views
### Copyright
Please see README.md

View File

@ -1,2 +1,2 @@
from __future__ import unicode_literals
__version__ = '5.8.2'
__version__ = '6.0.1'

View File

@ -1,2 +1,4 @@
- Added Calendar and Gantt Views for Sales Order based on Delivery Date
- Most transaction forms will now have collapsibe sections so that forms appear to be more organized and you can easily locate parts to be edited.
- The document title on most transactions can be edited so that you can set meaningful titles to all transactions like Sales Invoice and also edit it to denote status.
- Allow user to disable warnings for "Multiple Items" and "Multiple Sales Order against a Customer's Purchase Order" via Sales and Purchase Settings. Sponsored by: **[McLean Images](http://www.mcleanimages.com.au/)**

View File

@ -0,0 +1,168 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
import frappe
from frappe import _
from frappe.utils import cstr, flt
import json
class ItemVariantExistsError(frappe.ValidationError): pass
class InvalidItemAttributeValueError(frappe.ValidationError): pass
class ItemTemplateCannotHaveStock(frappe.ValidationError): pass
@frappe.whitelist()
def get_variant(item, args):
"""Validates Attributes and their Values, then looks for an exactly matching Item Variant
:param item: Template Item
:param args: A dictionary with "Attribute" as key and "Attribute Value" as value
"""
if isinstance(args, basestring):
args = json.loads(args)
if not args:
frappe.throw(_("Please specify at least one attribute in the Attributes table"))
validate_item_variant_attributes(item, args)
return find_variant(item, args)
def validate_item_variant_attributes(item, args):
attribute_values = {}
for t in frappe.get_all("Item Attribute Value", fields=["parent", "attribute_value"],
filters={"parent": ["in", args.keys()]}):
(attribute_values.setdefault(t.parent, [])).append(t.attribute_value)
numeric_attributes = frappe._dict((t.attribute, t) for t in \
frappe.db.sql("""select attribute, from_range, to_range, increment from `tabItem Variant Attribute`
where parent = %s and numeric_values=1""", (item), as_dict=1))
for attribute, value in args.items():
if attribute in numeric_attributes:
numeric_attribute = numeric_attributes[attribute]
from_range = numeric_attribute.from_range
to_range = numeric_attribute.to_range
increment = numeric_attribute.increment
if increment == 0:
# defensive validation to prevent ZeroDivisionError
frappe.throw(_("Increment for Attribute {0} cannot be 0").format(attribute))
is_in_range = from_range <= flt(value) <= to_range
precision = len(cstr(increment).split(".")[-1].rstrip("0"))
#avoid precision error by rounding the remainder
remainder = flt((flt(value) - from_range) % increment, precision)
is_incremental = remainder==0 or remainder==0 or remainder==increment
if not (is_in_range and is_incremental):
frappe.throw(_("Value for Attribute {0} must be within the range of {1} to {2} in the increments of {3}")\
.format(attribute, from_range, to_range, increment), InvalidItemAttributeValueError)
elif value not in attribute_values[attribute]:
frappe.throw(_("Value {0} for Attribute {1} does not exist in the list of valid Item Attribute Values").format(
value, attribute))
def find_variant(item, args):
conditions = ["""(iv_attribute.attribute="{0}" and iv_attribute.attribute_value="{1}")"""\
.format(frappe.db.escape(key), frappe.db.escape(cstr(value))) for key, value in args.items()]
conditions = " or ".join(conditions)
# use approximate match and shortlist possible variant matches
# it is approximate because we are matching using OR condition
# and it need not be exact match at this stage
# this uses a simpler query instead of using multiple exists conditions
possible_variants = frappe.db.sql_list("""select name from `tabItem` item
where variant_of=%s and exists (
select name from `tabItem Variant Attribute` iv_attribute
where iv_attribute.parent=item.name
and ({conditions})
)""".format(conditions=conditions), item)
for variant in possible_variants:
variant = frappe.get_doc("Item", variant)
if len(args.keys()) == len(variant.get("attributes")):
# has the same number of attributes and values
# assuming no duplication as per the validation in Item
match_count = 0
for attribute, value in args.items():
for row in variant.attributes:
if row.attribute==attribute and row.attribute_value== cstr(value):
# this row matches
match_count += 1
break
if match_count == len(args.keys()):
return variant.name
@frappe.whitelist()
def create_variant(item, args):
if isinstance(args, basestring):
args = json.loads(args)
template = frappe.get_doc("Item", item)
variant = frappe.new_doc("Item")
variant_attributes = []
for d in template.attributes:
variant_attributes.append({
"attribute": d.attribute,
"attribute_value": args.get(d.attribute)
})
variant.set("attributes", variant_attributes)
copy_attributes_to_variant(template, variant)
make_variant_item_code(template, variant)
return variant
def copy_attributes_to_variant(item, variant):
from frappe.model import no_value_fields
for field in item.meta.fields:
if field.fieldtype not in no_value_fields and (not field.no_copy)\
and field.fieldname not in ("item_code", "item_name"):
if variant.get(field.fieldname) != item.get(field.fieldname):
variant.set(field.fieldname, item.get(field.fieldname))
variant.variant_of = item.name
variant.has_variants = 0
variant.show_in_website = 0
if variant.attributes:
variant.description += "\n"
for d in variant.attributes:
variant.description += "<p>" + d.attribute + ": " + cstr(d.attribute_value) + "</p>"
def make_variant_item_code(template, variant):
"""Uses template's item code and abbreviations to make variant's item code"""
if variant.item_code:
return
abbreviations = []
for attr in variant.attributes:
item_attribute = frappe.db.sql("""select i.numeric_values, v.abbr
from `tabItem Attribute` i left join `tabItem Attribute Value` v
on (i.name=v.parent)
where i.name=%(attribute)s and v.attribute_value=%(attribute_value)s""", {
"attribute": attr.attribute,
"attribute_value": attr.attribute_value
}, as_dict=True)
if not item_attribute:
# somehow an invalid item attribute got used
return
if item_attribute[0].numeric_values:
# don't generate item code if one of the attributes is numeric
return
abbreviations.append(item_attribute[0].abbr)
if abbreviations:
variant.item_code = "{0}-{1}".format(template.item_code, "-".join(abbreviations))
if variant.item_code:
variant.item_name = variant.item_code

View File

@ -268,7 +268,7 @@ def get_batch_no(doctype, txt, searchfield, start, page_len, filters):
{0}
{match_conditions}
order by expiry_date, name desc
limit %(start)s, %(page_len)s""".format(cond, match_conditions=get_match_cond(doctype)), args, debug=1)
limit %(start)s, %(page_len)s""".format(cond, match_conditions=get_match_cond(doctype)), args)
def get_account_list(doctype, txt, searchfield, start, page_len, filters):
filter_list = []

File diff suppressed because it is too large Load Diff

View File

@ -27,7 +27,7 @@ blogs.
"""
app_icon = "icon-th"
app_color = "#e74c3c"
app_version = "5.8.2"
app_version = "6.0.1"
github_link = "https://github.com/frappe/erpnext"
error_report_email = "support@erpnext.com"
@ -51,7 +51,7 @@ my_account_context = "erpnext.shopping_cart.utils.update_my_account_context"
email_append_to = ["Job Applicant", "Opportunity", "Issue"]
calendars = ["Task", "Production Order", "Time Log", "Leave Application"]
calendars = ["Task", "Production Order", "Time Log", "Leave Application", "Sales Order"]
website_generators = ["Item Group", "Item", "Sales Partner"]

View File

@ -329,15 +329,15 @@ class ProductionOrder(Document):
frappe.throw(_("Production Order cannot be raised against a Item Template"), ItemHasVariantError)
validate_end_of_life(self.production_item)
def validate_qty(self):
if not self.qty > 0:
frappe.throw(_("Quantity to Manufacture must be greater than 0."))
def validate_operation_time(self):
for d in self.operations:
if not d.time_in_mins > 0:
frappe.throw(_("Operation Time must be greater than 0 for Operation {0}".format(d.operation)))
frappe.throw(_("Operation Time must be greater than 0 for Operation {0}".format(d.operation)))
@frappe.whitelist()
def get_item_details(item):
@ -381,19 +381,17 @@ def make_stock_entry(production_order_id, purpose, qty=None):
@frappe.whitelist()
def get_events(start, end, filters=None):
from frappe.desk.reportview import build_match_conditions
if not frappe.has_permission("Production Order"):
frappe.msgprint(_("No Permission"), raise_exception=1)
"""Returns events for Gantt / Calendar view rendering.
conditions = build_match_conditions("Production Order")
conditions = conditions and (" and " + conditions) or ""
if filters:
filters = json.loads(filters)
for key in filters:
if filters[key]:
conditions += " and " + key + ' = "' + filters[key].replace('"', '\"') + '"'
:param start: Start date-time.
:param end: End date-time.
:param filters: Filters (JSON).
"""
from frappe.desk.calendar import get_event_conditions
conditions = get_event_conditions("Production Order", filters)
data = frappe.db.sql("""select name, production_item, planned_start_date, planned_end_date
data = frappe.db.sql("""select name, production_item, planned_start_date,
planned_end_date, status
from `tabProduction Order`
where ((ifnull(planned_start_date, '0000-00-00')!= '0000-00-00') \
and (planned_start_date between %(start)s and %(end)s) \
@ -427,4 +425,4 @@ def make_time_log(name, operation, from_time=None, to_time=None, qty=None, proj
def get_default_warehouse():
wip_warehouse = frappe.db.get_single_value("Manufacturing Settings", "default_wip_warehouse")
fg_warehouse = frappe.db.get_single_value("Manufacturing Settings", "default_fg_warehouse")
return {"wip_warehouse": wip_warehouse, "fg_warehouse": fg_warehouse}
return {"wip_warehouse": wip_warehouse, "fg_warehouse": fg_warehouse}

View File

@ -10,6 +10,15 @@ frappe.views.calendar["Production Order"] = {
"allDay": "allDay"
},
gantt: true,
get_css_class: function(data) {
if(data.status==="Completed") {
return "success";
} else if(data.status==="In Process") {
return "warning";
} else {
return "danger";
}
},
filters: [
{
"fieldtype": "Link",

View File

@ -200,5 +200,5 @@ erpnext.patches.v5_8.add_credit_note_print_heading
execute:frappe.delete_doc_if_exists("Print Format", "Credit Note - Negative Invoice")
# V6.0
erpnext.patches.v6_0.set_default_title
erpnext.patches.v6_0.set_default_title # 2015-09-03
erpnext.patches.v6_0.multi_currency

View File

@ -42,7 +42,7 @@ class Task(Document):
for d in self.depends_on:
if frappe.db.get_value("Task", d.task, "status") != "Closed":
frappe.throw(_("Cannot close task as its dependant task {0} is not closed.").format(d.task))
from frappe.desk.form.assign_to import clear
clear(self.doctype, self.name)
@ -107,18 +107,14 @@ class Task(Document):
@frappe.whitelist()
def get_events(start, end, filters=None):
from frappe.desk.reportview import build_match_conditions
if not frappe.has_permission("Task"):
frappe.msgprint(_("No Permission"), raise_exception=1)
"""Returns events for Gantt / Calendar view rendering.
conditions = build_match_conditions("Task")
conditions = conditions and (" and " + conditions) or ""
if filters:
filters = json.loads(filters)
for key in filters:
if filters[key]:
conditions += " and " + key + ' = "' + filters[key].replace('"', '\"') + '"'
:param start: Start date-time.
:param end: End date-time.
:param filters: Filters (JSON).
"""
from frappe.desk.calendar import get_event_conditions
conditions = get_event_conditions("Task", filters)
data = frappe.db.sql("""select name, exp_start_date, exp_end_date,
subject, status, project from `tabTask`

View File

@ -2,7 +2,7 @@
# License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
import frappe, json
import frappe
from frappe import _
from frappe.utils import cstr, flt, get_datetime, get_time, getdate
from dateutil.relativedelta import relativedelta
@ -248,17 +248,8 @@ def get_events(start, end, filters=None):
:param end: End date-time.
:param filters: Filters like workstation, project etc.
"""
from frappe.desk.reportview import build_match_conditions
if not frappe.has_permission("Time Log"):
frappe.msgprint(_("No Permission"), raise_exception=1)
conditions = build_match_conditions("Time Log")
conditions = conditions and (" and " + conditions) or ""
if filters:
filters = json.loads(filters)
for key in filters:
if filters[key]:
conditions += " and " + key + ' = "' + filters[key].replace('"', '\"') + '"'
from frappe.desk.calendar import get_event_conditions
conditions = get_event_conditions("Time Log", filters)
data = frappe.db.sql("""select name, from_time, to_time,
activity_type, task, project, production_order, workstation from `tabTime Log`

View File

@ -471,3 +471,24 @@ def make_maintenance_visit(source_name, target_doc=None):
}, target_doc)
return doclist
@frappe.whitelist()
def get_events(start, end, filters=None):
"""Returns events for Gantt / Calendar view rendering.
:param start: Start date-time.
:param end: End date-time.
:param filters: Filters (JSON).
"""
from frappe.desk.calendar import get_event_conditions
conditions = get_event_conditions("Sales Order", filters)
data = frappe.db.sql("""select name, customer_name, delivery_status, billing_status, delivery_date
from `tabSales Order`
where (ifnull(delivery_date, '0000-00-00')!= '0000-00-00') \
and (delivery_date between %(start)s and %(end)s) {conditions}
""".format(conditions=conditions), {
"start": start,
"end": end
}, as_dict=True, update={"allDay": 0})
return data

View File

@ -0,0 +1,45 @@
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
// License: GNU General Public License v3. See license.txt
frappe.views.calendar["Sales Order"] = {
field_map: {
"start": "delivery_date",
"end": "delivery_date",
"id": "name",
"title": "customer_name",
"allDay": "allDay"
},
gantt: true,
filters: [
{
"fieldtype": "Link",
"fieldname": "customer",
"options": "Customer",
"label": __("Customer")
},
{
"fieldtype": "Select",
"fieldname": "delivery_status",
"options": "Not Delivered\nFully Delivered\nPartly Delivered\nClosed\nNot Applicable",
"label": __("Delivery Status")
},
{
"fieldtype": "Select",
"fieldname": "billing_status",
"options": "Not Billed\nFully Billed\nPartly Billed\nClosed",
"label": __("Billing Status")
},
],
get_events_method: "erpnext.selling.doctype.sales_order.sales_order.get_events",
get_css_class: function(data) {
if(data.status=="Stopped") {
return "";
} if(data.delivery_status=="Not Delivered") {
return "danger";
} else if(data.delivery_status=="Partly Delivered") {
return "warning";
} else if(data.delivery_status=="Fully Delivered") {
return "success";
}
}
}

View File

@ -4,7 +4,7 @@
# For license information, please see license.txt
from __future__ import unicode_literals
from frappe.utils import get_site_path
from frappe.utils import get_site_path, cint
from frappe.utils.data import convert_utc_to_user_timezone
import os
import datetime
@ -42,7 +42,7 @@ def take_backups_weekly():
take_backups_if("Weekly")
def take_backups_if(freq):
if frappe.db.get_value("Backup Manager", None, "send_backups_to_dropbox"):
if cint(frappe.db.get_value("Backup Manager", None, "send_backups_to_dropbox")):
if frappe.db.get_value("Backup Manager", None, "upload_backups_to_dropbox")==freq:
take_backups_dropbox()

View File

@ -6,6 +6,7 @@ import frappe
from frappe.utils import cint
from frappe import _
from frappe.desk.notifications import clear_notifications
@frappe.whitelist()
def delete_company_transactions(company_name):
@ -16,11 +17,16 @@ def delete_company_transactions(company_name):
frappe.throw(_("Transactions can only be deleted by the creator of the Company"), frappe.PermissionError)
delete_bins(company_name)
delete_time_logs(company_name)
for doctype in frappe.db.sql_list("""select parent from
tabDocField where fieldtype='Link' and options='Company'"""):
if doctype not in ("Account", "Cost Center", "Warehouse", "Budget Detail", "Party Account"):
if doctype not in ("Account", "Cost Center", "Warehouse", "Budget Detail", "Party Account", "Employee"):
delete_for_doctype(doctype, company_name)
# Clear notification counts
clear_notifications()
def delete_for_doctype(doctype, company_name):
meta = frappe.get_meta(doctype)
@ -60,3 +66,20 @@ def delete_for_doctype(doctype, company_name):
def delete_bins(company_name):
frappe.db.sql("""delete from tabBin where warehouse in
(select name from tabWarehouse where company=%s)""", company_name)
def delete_time_logs(company_name):
# Delete Time Logs as it is linked to Production Order / Project / Task, which are linked to company
frappe.db.sql("""
delete from `tabTime Log`
where
(ifnull(project, '') != ''
and exists(select name from `tabProject` where name=`tabTime Log`.project and company=%(company)s))
or (ifnull(task, '') != ''
and exists(select name from `tabTask` where name=`tabTime Log`.task and company=%(company)s))
or (ifnull(production_order, '') != ''
and exists(select name from `tabProduction Order`
where name=`tabTime Log`.production_order and company=%(company)s))
or (ifnull(sales_invoice, '') != ''
and exists(select name from `tabSales Invoice`
where name=`tabTime Log`.sales_invoice and company=%(company)s))
""", {"company": company_name})

View File

@ -231,7 +231,7 @@ $.extend(erpnext.item, {
args = d.get_values();
if(!args) return;
frappe.call({
method:"erpnext.stock.doctype.item.item.get_variant",
method:"erpnext.controllers.item_variant.get_variant",
args: {
"item": cur_frm.doc.name,
"args": d.get_values()
@ -253,7 +253,7 @@ $.extend(erpnext.item, {
} else {
d.hide();
frappe.call({
method:"erpnext.stock.doctype.item.item.create_variant",
method:"erpnext.controllers.item_variant.create_variant",
args: {
"item": cur_frm.doc.name,
"args": d.get_values()

View File

@ -3,18 +3,15 @@
from __future__ import unicode_literals
import frappe
import json
from frappe import msgprint, _
from frappe.utils import cstr, flt, cint, getdate, now_datetime, formatdate
from frappe.website.website_generator import WebsiteGenerator
from erpnext.setup.doctype.item_group.item_group import invalidate_cache_for, get_parent_item_groups
from frappe.website.render import clear_cache
from frappe.website.doctype.website_slideshow.website_slideshow import get_slideshow
from erpnext.controllers.item_variant import get_variant, copy_attributes_to_variant, ItemVariantExistsError
class WarehouseNotSet(frappe.ValidationError): pass
class ItemTemplateCannotHaveStock(frappe.ValidationError): pass
class ItemVariantExistsError(frappe.ValidationError): pass
class InvalidItemAttributeValueError(frappe.ValidationError): pass
class Item(WebsiteGenerator):
website = frappe._dict(
@ -63,8 +60,8 @@ class Item(WebsiteGenerator):
self.validate_warehouse_for_reorder()
self.update_item_desc()
self.synced_with_hub = 0
self.validate_has_variants()
# self.validate_stock_for_template_must_be_zero()
self.validate_attributes()
self.validate_variant_attributes()
@ -314,14 +311,6 @@ class Item(WebsiteGenerator):
if frappe.db.exists("Item", {"variant_of": self.name}):
frappe.throw(_("Item has variants."))
def validate_stock_for_template_must_be_zero(self):
if self.has_variants:
stock_in = frappe.db.sql_list("""select warehouse from tabBin
where item_code=%s and (ifnull(actual_qty, 0) > 0 or ifnull(ordered_qty, 0) > 0
or ifnull(reserved_qty, 0) > 0 or ifnull(indented_qty, 0) > 0 or ifnull(planned_qty, 0) > 0)""", self.name)
if stock_in:
frappe.throw(_("Item Template cannot have stock or Open Sales/Purchase/Production Orders."), ItemTemplateCannotHaveStock)
def validate_uom(self):
if not self.get("__islocal"):
check_stock_uom_with_bin(self.name, self.stock_uom)
@ -490,158 +479,3 @@ def check_stock_uom_with_bin(item, stock_uom):
you have already made some transaction(s) with another UOM. To change default UOM, \
use 'UOM Replace Utility' tool under Stock module.").format(item))
@frappe.whitelist()
def get_variant(item, args):
"""Validates Attributes and their Values, then looks for an exactly matching Item Variant
:param item: Template Item
:param args: A dictionary with "Attribute" as key and "Attribute Value" as value
"""
if isinstance(args, basestring):
args = json.loads(args)
if not args:
frappe.throw(_("Please specify at least one attribute in the Attributes table"))
validate_item_variant_attributes(item, args)
return find_variant(item, args)
def validate_item_variant_attributes(item, args):
attribute_values = {}
for t in frappe.get_all("Item Attribute Value", fields=["parent", "attribute_value"],
filters={"parent": ["in", args.keys()]}):
(attribute_values.setdefault(t.parent, [])).append(t.attribute_value)
numeric_attributes = frappe._dict((t.attribute, t) for t in \
frappe.db.sql("""select attribute, from_range, to_range, increment from `tabItem Variant Attribute`
where parent = %s and numeric_values=1""", (item), as_dict=1))
for attribute, value in args.items():
if attribute in numeric_attributes:
numeric_attribute = numeric_attributes[attribute]
from_range = numeric_attribute.from_range
to_range = numeric_attribute.to_range
increment = numeric_attribute.increment
if increment == 0:
# defensive validation to prevent ZeroDivisionError
frappe.throw(_("Increment for Attribute {0} cannot be 0").format(attribute))
is_in_range = from_range <= flt(value) <= to_range
precision = len(cstr(increment).split(".")[-1].rstrip("0"))
#avoid precision error by rounding the remainder
remainder = flt((flt(value) - from_range) % increment, precision)
is_incremental = remainder==0 or remainder==0 or remainder==increment
if not (is_in_range and is_incremental):
frappe.throw(_("Value for Attribute {0} must be within the range of {1} to {2} in the increments of {3}")\
.format(attribute, from_range, to_range, increment), InvalidItemAttributeValueError)
elif value not in attribute_values[attribute]:
frappe.throw(_("Value {0} for Attribute {1} does not exist in the list of valid Item Attribute Values").format(
value, attribute))
def find_variant(item, args):
conditions = ["""(iv_attribute.attribute="{0}" and iv_attribute.attribute_value="{1}")"""\
.format(frappe.db.escape(key), frappe.db.escape(cstr(value))) for key, value in args.items()]
conditions = " or ".join(conditions)
# use approximate match and shortlist possible variant matches
# it is approximate because we are matching using OR condition
# and it need not be exact match at this stage
# this uses a simpler query instead of using multiple exists conditions
possible_variants = frappe.db.sql_list("""select name from `tabItem` item
where variant_of=%s and exists (
select name from `tabItem Variant Attribute` iv_attribute
where iv_attribute.parent=item.name
and ({conditions})
)""".format(conditions=conditions), item)
for variant in possible_variants:
variant = frappe.get_doc("Item", variant)
if len(args.keys()) == len(variant.get("attributes")):
# has the same number of attributes and values
# assuming no duplication as per the validation in Item
match_count = 0
for attribute, value in args.items():
for row in variant.attributes:
if row.attribute==attribute and row.attribute_value== cstr(value):
# this row matches
match_count += 1
break
if match_count == len(args.keys()):
return variant.name
@frappe.whitelist()
def create_variant(item, args):
if isinstance(args, basestring):
args = json.loads(args)
variant = frappe.new_doc("Item")
variant_attributes = []
for d in args:
variant_attributes.append({
"attribute": d,
"attribute_value": args[d]
})
variant.set("attributes", variant_attributes)
template = frappe.get_doc("Item", item)
copy_attributes_to_variant(template, variant)
make_variant_item_code(template, variant)
return variant
def copy_attributes_to_variant(item, variant):
from frappe.model import no_value_fields
for field in item.meta.fields:
if field.fieldtype not in no_value_fields and (not field.no_copy)\
and field.fieldname not in ("item_code", "item_name"):
if variant.get(field.fieldname) != item.get(field.fieldname):
variant.set(field.fieldname, item.get(field.fieldname))
variant.variant_of = item.name
variant.has_variants = 0
variant.show_in_website = 0
if variant.attributes:
variant.description += "\n"
for d in variant.attributes:
variant.description += "<p>" + d.attribute + ": " + cstr(d.attribute_value) + "</p>"
def make_variant_item_code(template, variant):
"""Uses template's item code and abbreviations to make variant's item code"""
if variant.item_code:
return
abbreviations = []
for attr in variant.attributes:
item_attribute = frappe.db.sql("""select i.numeric_values, v.abbr
from `tabItem Attribute` i left join `tabItem Attribute Value` v
on (i.name=v.parent)
where i.name=%(attribute)s and v.attribute_value=%(attribute_value)s""", {
"attribute": attr.attribute,
"attribute_value": attr.attribute_value
}, as_dict=True)
if not item_attribute:
# somehow an invalid item attribute got used
return
if item_attribute[0].numeric_values:
# don't generate item code if one of the attributes is numeric
return
abbreviations.append(item_attribute[0].abbr)
if abbreviations:
variant.item_code = "{0}-{1}".format(template.item_code, "-".join(abbreviations))
if variant.item_code:
variant.item_name = variant.item_code

View File

@ -6,8 +6,8 @@ import unittest
import frappe
from frappe.test_runner import make_test_records
from erpnext.stock.doctype.item.item import (WarehouseNotSet, create_variant,
ItemVariantExistsError, InvalidItemAttributeValueError)
from erpnext.stock.doctype.item.item import WarehouseNotSet
from erpnext.controllers.item_variant import create_variant, ItemVariantExistsError, InvalidItemAttributeValueError
test_ignore = ["BOM"]
test_dependencies = ["Warehouse"]
@ -132,7 +132,7 @@ class TestItem(unittest.TestCase):
"attribute": "Test Size"
},
{
"attribute": "Test Item Length",
"attribute": "Test Item Length",
"numeric_values": 1,
"from_range": 0.0,
"to_range": 100.0,

View File

@ -41,6 +41,10 @@ class ItemAttribute(Document):
abbrs.append(d.abbr)
def validate_attribute_values(self):
# don't validate numeric values
if self.numeric_values:
return
attribute_values = []
for d in self.item_attribute_values:
attribute_values.append(d.attribute_value)

View File

@ -8,7 +8,7 @@ from frappe import _
from frappe.utils import flt, getdate, add_days, formatdate
from frappe.model.document import Document
from datetime import date
from erpnext.stock.doctype.item.item import ItemTemplateCannotHaveStock
from erpnext.controllers.item_variant import ItemTemplateCannotHaveStock
class StockFreezeError(frappe.ValidationError): pass

View File

@ -212,7 +212,7 @@ def get_price_list_rate(args, item_doc, out):
price_list_rate = get_price_list_rate_for(args, item_doc.name)
if not price_list_rate and item_doc.variant_of:
price_list_rate = get_price_list_rate_for(args, item_doc.variant_of)
if not price_list_rate:
if args.price_list and args.rate:
insert_item_price(args)
@ -231,12 +231,16 @@ def insert_item_price(args):
if frappe.db.get_value("Price List", args.price_list, "currency") == args.currency \
and cint(frappe.db.get_single_value("Stock Settings", "auto_insert_price_list_rate_if_missing")):
if frappe.has_permission("Item Price", "write"):
price_list_rate = args.rate / args.conversion_factor \
if args.get("conversion_factor") else args.rate
item_price = frappe.get_doc({
"doctype": "Item Price",
"price_list": args.price_list,
"item_code": args.item_code,
"currency": args.currency,
"price_list_rate": args.rate
"price_list_rate": price_list_rate
})
item_price.insert()
frappe.msgprint("Item Price added for {0} in Price List {1}".format(args.item_code,

View File

@ -1,6 +1,6 @@
from setuptools import setup, find_packages
version = "5.8.2"
version = "6.0.1"
with open("requirements.txt", "r") as f:
install_requires = f.readlines()