mirror of
https://github.com/frappe/erpnext.git
synced 2026-05-26 00:14:50 +00:00
Fixed conflict
This commit is contained in:
@@ -49,6 +49,20 @@ DocTypes are easy to create but hard to maintain. If you find that there is a an
|
|||||||
|
|
||||||
Tabs!
|
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
|
### Copyright
|
||||||
|
|
||||||
Please see README.md
|
Please see README.md
|
||||||
|
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
__version__ = '5.8.2'
|
__version__ = '6.0.1'
|
||||||
|
|||||||
@@ -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.
|
- 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.
|
- 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/)**
|
||||||
168
erpnext/controllers/item_variant.py
Normal file
168
erpnext/controllers/item_variant.py
Normal 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
|
||||||
@@ -268,7 +268,7 @@ def get_batch_no(doctype, txt, searchfield, start, page_len, filters):
|
|||||||
{0}
|
{0}
|
||||||
{match_conditions}
|
{match_conditions}
|
||||||
order by expiry_date, name desc
|
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):
|
def get_account_list(doctype, txt, searchfield, start, page_len, filters):
|
||||||
filter_list = []
|
filter_list = []
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -27,7 +27,7 @@ blogs.
|
|||||||
"""
|
"""
|
||||||
app_icon = "icon-th"
|
app_icon = "icon-th"
|
||||||
app_color = "#e74c3c"
|
app_color = "#e74c3c"
|
||||||
app_version = "5.8.2"
|
app_version = "6.0.1"
|
||||||
github_link = "https://github.com/frappe/erpnext"
|
github_link = "https://github.com/frappe/erpnext"
|
||||||
|
|
||||||
error_report_email = "support@erpnext.com"
|
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"]
|
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"]
|
website_generators = ["Item Group", "Item", "Sales Partner"]
|
||||||
|
|
||||||
|
|||||||
@@ -329,15 +329,15 @@ class ProductionOrder(Document):
|
|||||||
frappe.throw(_("Production Order cannot be raised against a Item Template"), ItemHasVariantError)
|
frappe.throw(_("Production Order cannot be raised against a Item Template"), ItemHasVariantError)
|
||||||
|
|
||||||
validate_end_of_life(self.production_item)
|
validate_end_of_life(self.production_item)
|
||||||
|
|
||||||
def validate_qty(self):
|
def validate_qty(self):
|
||||||
if not self.qty > 0:
|
if not self.qty > 0:
|
||||||
frappe.throw(_("Quantity to Manufacture must be greater than 0."))
|
frappe.throw(_("Quantity to Manufacture must be greater than 0."))
|
||||||
|
|
||||||
def validate_operation_time(self):
|
def validate_operation_time(self):
|
||||||
for d in self.operations:
|
for d in self.operations:
|
||||||
if not d.time_in_mins > 0:
|
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()
|
@frappe.whitelist()
|
||||||
def get_item_details(item):
|
def get_item_details(item):
|
||||||
@@ -381,19 +381,17 @@ def make_stock_entry(production_order_id, purpose, qty=None):
|
|||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_events(start, end, filters=None):
|
def get_events(start, end, filters=None):
|
||||||
from frappe.desk.reportview import build_match_conditions
|
"""Returns events for Gantt / Calendar view rendering.
|
||||||
if not frappe.has_permission("Production Order"):
|
|
||||||
frappe.msgprint(_("No Permission"), raise_exception=1)
|
|
||||||
|
|
||||||
conditions = build_match_conditions("Production Order")
|
:param start: Start date-time.
|
||||||
conditions = conditions and (" and " + conditions) or ""
|
:param end: End date-time.
|
||||||
if filters:
|
:param filters: Filters (JSON).
|
||||||
filters = json.loads(filters)
|
"""
|
||||||
for key in filters:
|
from frappe.desk.calendar import get_event_conditions
|
||||||
if filters[key]:
|
conditions = get_event_conditions("Production Order", filters)
|
||||||
conditions += " and " + key + ' = "' + filters[key].replace('"', '\"') + '"'
|
|
||||||
|
|
||||||
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`
|
from `tabProduction Order`
|
||||||
where ((ifnull(planned_start_date, '0000-00-00')!= '0000-00-00') \
|
where ((ifnull(planned_start_date, '0000-00-00')!= '0000-00-00') \
|
||||||
and (planned_start_date between %(start)s and %(end)s) \
|
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():
|
def get_default_warehouse():
|
||||||
wip_warehouse = frappe.db.get_single_value("Manufacturing Settings", "default_wip_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")
|
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}
|
||||||
|
|||||||
@@ -10,6 +10,15 @@ frappe.views.calendar["Production Order"] = {
|
|||||||
"allDay": "allDay"
|
"allDay": "allDay"
|
||||||
},
|
},
|
||||||
gantt: true,
|
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: [
|
filters: [
|
||||||
{
|
{
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
|
|||||||
@@ -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")
|
execute:frappe.delete_doc_if_exists("Print Format", "Credit Note - Negative Invoice")
|
||||||
|
|
||||||
# V6.0
|
# 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
|
erpnext.patches.v6_0.multi_currency
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ class Task(Document):
|
|||||||
for d in self.depends_on:
|
for d in self.depends_on:
|
||||||
if frappe.db.get_value("Task", d.task, "status") != "Closed":
|
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))
|
frappe.throw(_("Cannot close task as its dependant task {0} is not closed.").format(d.task))
|
||||||
|
|
||||||
from frappe.desk.form.assign_to import clear
|
from frappe.desk.form.assign_to import clear
|
||||||
clear(self.doctype, self.name)
|
clear(self.doctype, self.name)
|
||||||
|
|
||||||
@@ -107,18 +107,14 @@ class Task(Document):
|
|||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_events(start, end, filters=None):
|
def get_events(start, end, filters=None):
|
||||||
from frappe.desk.reportview import build_match_conditions
|
"""Returns events for Gantt / Calendar view rendering.
|
||||||
if not frappe.has_permission("Task"):
|
|
||||||
frappe.msgprint(_("No Permission"), raise_exception=1)
|
|
||||||
|
|
||||||
conditions = build_match_conditions("Task")
|
:param start: Start date-time.
|
||||||
conditions = conditions and (" and " + conditions) or ""
|
:param end: End date-time.
|
||||||
|
:param filters: Filters (JSON).
|
||||||
if filters:
|
"""
|
||||||
filters = json.loads(filters)
|
from frappe.desk.calendar import get_event_conditions
|
||||||
for key in filters:
|
conditions = get_event_conditions("Task", filters)
|
||||||
if filters[key]:
|
|
||||||
conditions += " and " + key + ' = "' + filters[key].replace('"', '\"') + '"'
|
|
||||||
|
|
||||||
data = frappe.db.sql("""select name, exp_start_date, exp_end_date,
|
data = frappe.db.sql("""select name, exp_start_date, exp_end_date,
|
||||||
subject, status, project from `tabTask`
|
subject, status, project from `tabTask`
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
# License: GNU General Public License v3. See license.txt
|
# License: GNU General Public License v3. See license.txt
|
||||||
|
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
import frappe, json
|
import frappe
|
||||||
from frappe import _
|
from frappe import _
|
||||||
from frappe.utils import cstr, flt, get_datetime, get_time, getdate
|
from frappe.utils import cstr, flt, get_datetime, get_time, getdate
|
||||||
from dateutil.relativedelta import relativedelta
|
from dateutil.relativedelta import relativedelta
|
||||||
@@ -248,17 +248,8 @@ def get_events(start, end, filters=None):
|
|||||||
:param end: End date-time.
|
:param end: End date-time.
|
||||||
:param filters: Filters like workstation, project etc.
|
:param filters: Filters like workstation, project etc.
|
||||||
"""
|
"""
|
||||||
from frappe.desk.reportview import build_match_conditions
|
from frappe.desk.calendar import get_event_conditions
|
||||||
if not frappe.has_permission("Time Log"):
|
conditions = get_event_conditions("Time Log", filters)
|
||||||
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('"', '\"') + '"'
|
|
||||||
|
|
||||||
data = frappe.db.sql("""select name, from_time, to_time,
|
data = frappe.db.sql("""select name, from_time, to_time,
|
||||||
activity_type, task, project, production_order, workstation from `tabTime Log`
|
activity_type, task, project, production_order, workstation from `tabTime Log`
|
||||||
|
|||||||
@@ -471,3 +471,24 @@ def make_maintenance_visit(source_name, target_doc=None):
|
|||||||
}, target_doc)
|
}, target_doc)
|
||||||
|
|
||||||
return doclist
|
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
|
||||||
|
|||||||
45
erpnext/selling/doctype/sales_order/sales_order_calendar.js
Normal file
45
erpnext/selling/doctype/sales_order/sales_order_calendar.js
Normal 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";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
# For license information, please see license.txt
|
# For license information, please see license.txt
|
||||||
|
|
||||||
from __future__ import unicode_literals
|
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
|
from frappe.utils.data import convert_utc_to_user_timezone
|
||||||
import os
|
import os
|
||||||
import datetime
|
import datetime
|
||||||
@@ -42,7 +42,7 @@ def take_backups_weekly():
|
|||||||
take_backups_if("Weekly")
|
take_backups_if("Weekly")
|
||||||
|
|
||||||
def take_backups_if(freq):
|
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:
|
if frappe.db.get_value("Backup Manager", None, "upload_backups_to_dropbox")==freq:
|
||||||
take_backups_dropbox()
|
take_backups_dropbox()
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import frappe
|
|||||||
|
|
||||||
from frappe.utils import cint
|
from frappe.utils import cint
|
||||||
from frappe import _
|
from frappe import _
|
||||||
|
from frappe.desk.notifications import clear_notifications
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def delete_company_transactions(company_name):
|
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)
|
frappe.throw(_("Transactions can only be deleted by the creator of the Company"), frappe.PermissionError)
|
||||||
|
|
||||||
delete_bins(company_name)
|
delete_bins(company_name)
|
||||||
|
|
||||||
|
delete_time_logs(company_name)
|
||||||
|
|
||||||
for doctype in frappe.db.sql_list("""select parent from
|
for doctype in frappe.db.sql_list("""select parent from
|
||||||
tabDocField where fieldtype='Link' and options='Company'"""):
|
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)
|
delete_for_doctype(doctype, company_name)
|
||||||
|
|
||||||
|
# Clear notification counts
|
||||||
|
clear_notifications()
|
||||||
|
|
||||||
def delete_for_doctype(doctype, company_name):
|
def delete_for_doctype(doctype, company_name):
|
||||||
meta = frappe.get_meta(doctype)
|
meta = frappe.get_meta(doctype)
|
||||||
@@ -60,3 +66,20 @@ def delete_for_doctype(doctype, company_name):
|
|||||||
def delete_bins(company_name):
|
def delete_bins(company_name):
|
||||||
frappe.db.sql("""delete from tabBin where warehouse in
|
frappe.db.sql("""delete from tabBin where warehouse in
|
||||||
(select name from tabWarehouse where company=%s)""", company_name)
|
(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})
|
||||||
@@ -231,7 +231,7 @@ $.extend(erpnext.item, {
|
|||||||
args = d.get_values();
|
args = d.get_values();
|
||||||
if(!args) return;
|
if(!args) return;
|
||||||
frappe.call({
|
frappe.call({
|
||||||
method:"erpnext.stock.doctype.item.item.get_variant",
|
method:"erpnext.controllers.item_variant.get_variant",
|
||||||
args: {
|
args: {
|
||||||
"item": cur_frm.doc.name,
|
"item": cur_frm.doc.name,
|
||||||
"args": d.get_values()
|
"args": d.get_values()
|
||||||
@@ -253,7 +253,7 @@ $.extend(erpnext.item, {
|
|||||||
} else {
|
} else {
|
||||||
d.hide();
|
d.hide();
|
||||||
frappe.call({
|
frappe.call({
|
||||||
method:"erpnext.stock.doctype.item.item.create_variant",
|
method:"erpnext.controllers.item_variant.create_variant",
|
||||||
args: {
|
args: {
|
||||||
"item": cur_frm.doc.name,
|
"item": cur_frm.doc.name,
|
||||||
"args": d.get_values()
|
"args": d.get_values()
|
||||||
|
|||||||
@@ -3,18 +3,15 @@
|
|||||||
|
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
import frappe
|
import frappe
|
||||||
import json
|
|
||||||
from frappe import msgprint, _
|
from frappe import msgprint, _
|
||||||
from frappe.utils import cstr, flt, cint, getdate, now_datetime, formatdate
|
from frappe.utils import cstr, flt, cint, getdate, now_datetime, formatdate
|
||||||
from frappe.website.website_generator import WebsiteGenerator
|
from frappe.website.website_generator import WebsiteGenerator
|
||||||
from erpnext.setup.doctype.item_group.item_group import invalidate_cache_for, get_parent_item_groups
|
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.render import clear_cache
|
||||||
from frappe.website.doctype.website_slideshow.website_slideshow import get_slideshow
|
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 WarehouseNotSet(frappe.ValidationError): pass
|
||||||
class ItemTemplateCannotHaveStock(frappe.ValidationError): pass
|
|
||||||
class ItemVariantExistsError(frappe.ValidationError): pass
|
|
||||||
class InvalidItemAttributeValueError(frappe.ValidationError): pass
|
|
||||||
|
|
||||||
class Item(WebsiteGenerator):
|
class Item(WebsiteGenerator):
|
||||||
website = frappe._dict(
|
website = frappe._dict(
|
||||||
@@ -63,8 +60,8 @@ class Item(WebsiteGenerator):
|
|||||||
self.validate_warehouse_for_reorder()
|
self.validate_warehouse_for_reorder()
|
||||||
self.update_item_desc()
|
self.update_item_desc()
|
||||||
self.synced_with_hub = 0
|
self.synced_with_hub = 0
|
||||||
|
|
||||||
self.validate_has_variants()
|
self.validate_has_variants()
|
||||||
# self.validate_stock_for_template_must_be_zero()
|
|
||||||
self.validate_attributes()
|
self.validate_attributes()
|
||||||
self.validate_variant_attributes()
|
self.validate_variant_attributes()
|
||||||
|
|
||||||
@@ -314,14 +311,6 @@ class Item(WebsiteGenerator):
|
|||||||
if frappe.db.exists("Item", {"variant_of": self.name}):
|
if frappe.db.exists("Item", {"variant_of": self.name}):
|
||||||
frappe.throw(_("Item has variants."))
|
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):
|
def validate_uom(self):
|
||||||
if not self.get("__islocal"):
|
if not self.get("__islocal"):
|
||||||
check_stock_uom_with_bin(self.name, self.stock_uom)
|
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, \
|
you have already made some transaction(s) with another UOM. To change default UOM, \
|
||||||
use 'UOM Replace Utility' tool under Stock module.").format(item))
|
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
|
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ import unittest
|
|||||||
import frappe
|
import frappe
|
||||||
|
|
||||||
from frappe.test_runner import make_test_records
|
from frappe.test_runner import make_test_records
|
||||||
from erpnext.stock.doctype.item.item import (WarehouseNotSet, create_variant,
|
from erpnext.stock.doctype.item.item import WarehouseNotSet
|
||||||
ItemVariantExistsError, InvalidItemAttributeValueError)
|
from erpnext.controllers.item_variant import create_variant, ItemVariantExistsError, InvalidItemAttributeValueError
|
||||||
|
|
||||||
test_ignore = ["BOM"]
|
test_ignore = ["BOM"]
|
||||||
test_dependencies = ["Warehouse"]
|
test_dependencies = ["Warehouse"]
|
||||||
@@ -132,7 +132,7 @@ class TestItem(unittest.TestCase):
|
|||||||
"attribute": "Test Size"
|
"attribute": "Test Size"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"attribute": "Test Item Length",
|
"attribute": "Test Item Length",
|
||||||
"numeric_values": 1,
|
"numeric_values": 1,
|
||||||
"from_range": 0.0,
|
"from_range": 0.0,
|
||||||
"to_range": 100.0,
|
"to_range": 100.0,
|
||||||
|
|||||||
@@ -41,6 +41,10 @@ class ItemAttribute(Document):
|
|||||||
abbrs.append(d.abbr)
|
abbrs.append(d.abbr)
|
||||||
|
|
||||||
def validate_attribute_values(self):
|
def validate_attribute_values(self):
|
||||||
|
# don't validate numeric values
|
||||||
|
if self.numeric_values:
|
||||||
|
return
|
||||||
|
|
||||||
attribute_values = []
|
attribute_values = []
|
||||||
for d in self.item_attribute_values:
|
for d in self.item_attribute_values:
|
||||||
attribute_values.append(d.attribute_value)
|
attribute_values.append(d.attribute_value)
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ from frappe import _
|
|||||||
from frappe.utils import flt, getdate, add_days, formatdate
|
from frappe.utils import flt, getdate, add_days, formatdate
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
from datetime import date
|
from datetime import date
|
||||||
from erpnext.stock.doctype.item.item import ItemTemplateCannotHaveStock
|
from erpnext.controllers.item_variant import ItemTemplateCannotHaveStock
|
||||||
|
|
||||||
class StockFreezeError(frappe.ValidationError): pass
|
class StockFreezeError(frappe.ValidationError): pass
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
price_list_rate = get_price_list_rate_for(args, item_doc.name)
|
||||||
if not price_list_rate and item_doc.variant_of:
|
if not price_list_rate and item_doc.variant_of:
|
||||||
price_list_rate = get_price_list_rate_for(args, item_doc.variant_of)
|
price_list_rate = get_price_list_rate_for(args, item_doc.variant_of)
|
||||||
|
|
||||||
if not price_list_rate:
|
if not price_list_rate:
|
||||||
if args.price_list and args.rate:
|
if args.price_list and args.rate:
|
||||||
insert_item_price(args)
|
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 \
|
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")):
|
and cint(frappe.db.get_single_value("Stock Settings", "auto_insert_price_list_rate_if_missing")):
|
||||||
if frappe.has_permission("Item Price", "write"):
|
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({
|
item_price = frappe.get_doc({
|
||||||
"doctype": "Item Price",
|
"doctype": "Item Price",
|
||||||
"price_list": args.price_list,
|
"price_list": args.price_list,
|
||||||
"item_code": args.item_code,
|
"item_code": args.item_code,
|
||||||
"currency": args.currency,
|
"currency": args.currency,
|
||||||
"price_list_rate": args.rate
|
"price_list_rate": price_list_rate
|
||||||
})
|
})
|
||||||
item_price.insert()
|
item_price.insert()
|
||||||
frappe.msgprint("Item Price added for {0} in Price List {1}".format(args.item_code,
|
frappe.msgprint("Item Price added for {0} in Price List {1}".format(args.item_code,
|
||||||
|
|||||||
2
setup.py
2
setup.py
@@ -1,6 +1,6 @@
|
|||||||
from setuptools import setup, find_packages
|
from setuptools import setup, find_packages
|
||||||
|
|
||||||
version = "5.8.2"
|
version = "6.0.1"
|
||||||
|
|
||||||
with open("requirements.txt", "r") as f:
|
with open("requirements.txt", "r") as f:
|
||||||
install_requires = f.readlines()
|
install_requires = f.readlines()
|
||||||
|
|||||||
Reference in New Issue
Block a user