+ data-batch-no="${batch_no}" title="Item: ${item.item_name} Available Qty: ${item.actual_qty} ${item.stock_uom}">
${item.item_name}
diff --git a/erpnext/selling/page/point_of_sale/point_of_sale.py b/erpnext/selling/page/point_of_sale/point_of_sale.py
index a9d2be5fffc..8e130ba4246 100644
--- a/erpnext/selling/page/point_of_sale/point_of_sale.py
+++ b/erpnext/selling/page/point_of_sale/point_of_sale.py
@@ -37,20 +37,33 @@ def get_items(start, page_length, price_list, item_group, search_value="", pos_p
lft, rgt = frappe.db.get_value('Item Group', item_group, ['lft', 'rgt'])
# locate function is used to sort by closest match from the beginning of the value
-
result = []
- items_data = frappe.db.sql(""" SELECT name as item_code,
- item_name, image as item_image, idx as idx,is_stock_item
+ items_data = frappe.db.sql("""
+ SELECT
+ name AS item_code,
+ item_name,
+ stock_uom,
+ image AS item_image,
+ idx AS idx,
+ is_stock_item
FROM
`tabItem`
WHERE
- disabled = 0 and has_variants = 0 and is_sales_item = 1
- and item_group in (select name from `tabItem Group` where lft >= {lft} and rgt <= {rgt})
- and {condition} order by idx desc limit {start}, {page_length}"""
+ disabled = 0
+ AND has_variants = 0
+ AND is_sales_item = 1
+ AND item_group in (SELECT name FROM `tabItem Group` WHERE lft >= {lft} AND rgt <= {rgt})
+ AND {condition}
+ ORDER BY
+ idx desc
+ LIMIT
+ {start}, {page_length}"""
.format(
- start=start, page_length=page_length,
- lft=lft, rgt=rgt,
+ start=start,
+ page_length=page_length,
+ lft=lft,
+ rgt=rgt,
condition=condition
), as_dict=1)
@@ -64,30 +77,40 @@ def get_items(start, page_length, price_list, item_group, search_value="", pos_p
for d in item_prices_data:
item_prices[d.item_code] = d
-
+ # prepare filter for bin query
+ bin_filters = {'item_code': ['in', items]}
+ if warehouse:
+ bin_filters['warehouse'] = warehouse
if display_items_in_stock:
- filters = {'actual_qty': [">", 0], 'item_code': ['in', items]}
+ bin_filters['actual_qty'] = [">", 0]
- if warehouse:
- filters['warehouse'] = warehouse
+ # query item bin
+ bin_data = frappe.get_all(
+ 'Bin', fields=['item_code', 'sum(actual_qty) as actual_qty'],
+ filters=bin_filters, group_by='item_code'
+ )
- bin_data = frappe._dict(
- frappe.get_all("Bin", fields = ["item_code", "sum(actual_qty) as actual_qty"],
- filters = filters, group_by = "item_code")
- )
+ # convert list of dict into dict as {item_code: actual_qty}
+ bin_dict = {}
+ for b in bin_data:
+ bin_dict[b.get('item_code')] = b.get('actual_qty')
for item in items_data:
- row = {}
+ item_code = item.item_code
+ item_price = item_prices.get(item_code) or {}
+ item_stock_qty = bin_dict.get(item_code)
- row.update(item)
- item_price = item_prices.get(item.item_code) or {}
- row.update({
- 'price_list_rate': item_price.get('price_list_rate'),
- 'currency': item_price.get('currency'),
- 'actual_qty': bin_data.get('actual_qty')
- })
-
- result.append(row)
+ if display_items_in_stock and not item_stock_qty:
+ pass
+ else:
+ row = {}
+ row.update(item)
+ row.update({
+ 'price_list_rate': item_price.get('price_list_rate'),
+ 'currency': item_price.get('currency'),
+ 'actual_qty': item_stock_qty,
+ })
+ result.append(row)
res = {
'items': result
@@ -144,6 +167,8 @@ def get_item_group_condition(pos_profile):
return cond % tuple(item_groups)
+@frappe.whitelist()
+@frappe.validate_and_sanitize_search_inputs
def item_group_query(doctype, txt, searchfield, start, page_len, filters):
item_groups = []
cond = "1=1"
diff --git a/erpnext/selling/page/sales_funnel/sales_funnel.js b/erpnext/selling/page/sales_funnel/sales_funnel.js
index 85c0cd8bf0c..e3d0a55c3a0 100644
--- a/erpnext/selling/page/sales_funnel/sales_funnel.js
+++ b/erpnext/selling/page/sales_funnel/sales_funnel.js
@@ -90,6 +90,10 @@ erpnext.SalesFunnel = class SalesFunnel {
get_data(btn) {
var me = this;
+ if (!this.company) {
+ frappe.throw(__("Please Select a Company."));
+ }
+
const method_map = {
"sales_funnel": "erpnext.selling.page.sales_funnel.sales_funnel.get_funnel_data",
"opp_by_lead_source": "erpnext.selling.page.sales_funnel.sales_funnel.get_opp_by_lead_source",
diff --git a/erpnext/selling/page/sales_funnel/sales_funnel.py b/erpnext/selling/page/sales_funnel/sales_funnel.py
index d62e2093c69..dba24ef5b00 100644
--- a/erpnext/selling/page/sales_funnel/sales_funnel.py
+++ b/erpnext/selling/page/sales_funnel/sales_funnel.py
@@ -8,14 +8,23 @@ from frappe import _
from erpnext.accounts.report.utils import convert
import pandas as pd
+def validate_filters(from_date, to_date, company):
+ if from_date and to_date and (from_date >= to_date):
+ frappe.throw(_("To Date must be greater than From Date"))
+
+ if not company:
+ frappe.throw(_("Please Select a Company"))
+
@frappe.whitelist()
def get_funnel_data(from_date, to_date, company):
+ validate_filters(from_date, to_date, company)
+
active_leads = frappe.db.sql("""select count(*) from `tabLead`
where (date(`modified`) between %s and %s)
and status != "Do Not Contact" and company=%s""", (from_date, to_date, company))[0][0]
active_leads += frappe.db.sql("""select count(distinct contact.name) from `tabContact` contact
- left join `tabDynamic Link` dl on (dl.parent=contact.name) where dl.link_doctype='Customer'
+ left join `tabDynamic Link` dl on (dl.parent=contact.name) where dl.link_doctype='Customer'
and (date(contact.modified) between %s and %s) and status != "Passive" """, (from_date, to_date))[0][0]
opportunities = frappe.db.sql("""select count(*) from `tabOpportunity`
@@ -38,6 +47,8 @@ def get_funnel_data(from_date, to_date, company):
@frappe.whitelist()
def get_opp_by_lead_source(from_date, to_date, company):
+ validate_filters(from_date, to_date, company)
+
opportunities = frappe.get_all("Opportunity", filters=[['status', 'in', ['Open', 'Quotation', 'Replied']], ['company', '=', company], ['transaction_date', 'Between', [from_date, to_date]]], fields=['currency', 'sales_stage', 'opportunity_amount', 'probability', 'source'])
if opportunities:
@@ -68,11 +79,13 @@ def get_opp_by_lead_source(from_date, to_date, company):
@frappe.whitelist()
def get_pipeline_data(from_date, to_date, company):
+ validate_filters(from_date, to_date, company)
+
opportunities = frappe.get_all("Opportunity", filters=[['status', 'in', ['Open', 'Quotation', 'Replied']], ['company', '=', company], ['transaction_date', 'Between', [from_date, to_date]]], fields=['currency', 'sales_stage', 'opportunity_amount', 'probability'])
if opportunities:
default_currency = frappe.get_cached_value('Global Defaults', 'None', 'default_currency')
-
+
cp_opportunities = [dict(x, **{'compound_amount': (convert(x['opportunity_amount'], x['currency'], default_currency, to_date) * x['probability']/100)}) for x in opportunities]
df = pd.DataFrame(cp_opportunities).groupby(['sales_stage'], as_index=True).agg({'compound_amount': 'sum'}).to_dict()
diff --git a/erpnext/selling/report/customer_acquisition_and_loyalty/customer_acquisition_and_loyalty.py b/erpnext/selling/report/customer_acquisition_and_loyalty/customer_acquisition_and_loyalty.py
index 4509904249f..aa57665a815 100644
--- a/erpnext/selling/report/customer_acquisition_and_loyalty/customer_acquisition_and_loyalty.py
+++ b/erpnext/selling/report/customer_acquisition_and_loyalty/customer_acquisition_and_loyalty.py
@@ -4,7 +4,7 @@
from __future__ import unicode_literals
import frappe
from frappe import _
-from frappe.utils import getdate, cint
+from frappe.utils import getdate, cint, cstr
import calendar
def execute(filters=None):
@@ -48,15 +48,16 @@ def execute(filters=None):
new = new_customers_in.get(key, [0,0.0])
repeat = repeat_customers_in.get(key, [0,0.0])
- out.append([year, calendar.month_name[month],
+ out.append([cstr(year), calendar.month_name[month],
new[0], repeat[0], new[0] + repeat[0],
new[1], repeat[1], new[1] + repeat[1]])
return [
- _("Year"), _("Month"),
- _("New Customers") + ":Int",
- _("Repeat Customers") + ":Int",
- _("Total") + ":Int",
+ _("Year") + "::100",
+ _("Month") + "::100",
+ _("New Customers") + ":Int:100",
+ _("Repeat Customers") + ":Int:100",
+ _("Total") + ":Int:100",
_("New Customer Revenue") + ":Currency:150",
_("Repeat Customer Revenue") + ":Currency:150",
_("Total Revenue") + ":Currency:150"
diff --git a/erpnext/selling/report/item_wise_sales_history/item_wise_sales_history.py b/erpnext/selling/report/item_wise_sales_history/item_wise_sales_history.py
index 1fc3663bed7..f1b8bc34efb 100644
--- a/erpnext/selling/report/item_wise_sales_history/item_wise_sales_history.py
+++ b/erpnext/selling/report/item_wise_sales_history/item_wise_sales_history.py
@@ -9,6 +9,9 @@ from frappe.utils.nestedset import get_descendants_of
def execute(filters=None):
filters = frappe._dict(filters or {})
+ if filters.from_date > filters.to_date:
+ frappe.throw(_('From Date cannot be greater than To Date'))
+
columns = get_columns(filters)
data = get_data(filters)
return columns, data
@@ -96,7 +99,7 @@ def get_columns(filters):
"label": _("Customer Group"),
"fieldtype": "Link",
"fieldname": "customer_group",
- "options": "customer Group",
+ "options": "Customer Group",
"width": 120
},
{
@@ -121,8 +124,8 @@ def get_columns(filters):
},
{
"label": _("Billed Amount"),
- "fieldname": "rate",
- "options": "billed_amount",
+ "fieldtype": "currency",
+ "fieldname": "billed_amount",
"width": 120
},
{
diff --git a/erpnext/selling/report/pending_so_items_for_purchase_request/pending_so_items_for_purchase_request.py b/erpnext/selling/report/pending_so_items_for_purchase_request/pending_so_items_for_purchase_request.py
index 14d80315823..e89c45182fd 100644
--- a/erpnext/selling/report/pending_so_items_for_purchase_request/pending_so_items_for_purchase_request.py
+++ b/erpnext/selling/report/pending_so_items_for_purchase_request/pending_so_items_for_purchase_request.py
@@ -158,7 +158,7 @@ def get_data():
}
pending_so.append(so_record)
else:
- for item in bundled_item_map.get((so.name, so.item_code)):
+ for item in bundled_item_map.get((so.name, so.item_code), []):
material_requests_against_so = materials_request_dict.get((so.name, item.item_code)) or {}
if flt(item.qty) > flt(material_requests_against_so.get('qty')):
so_record = {
diff --git a/erpnext/selling/report/sales_analytics/sales_analytics.json b/erpnext/selling/report/sales_analytics/sales_analytics.json
index 71932610a6a..de5c3a6b2a7 100644
--- a/erpnext/selling/report/sales_analytics/sales_analytics.json
+++ b/erpnext/selling/report/sales_analytics/sales_analytics.json
@@ -1,31 +1,31 @@
{
- "add_total_row": 0,
- "creation": "2018-09-21 12:46:29.451048",
- "disable_prepared_report": 0,
- "disabled": 0,
- "docstatus": 0,
- "doctype": "Report",
- "idx": 0,
- "is_standard": "Yes",
- "modified": "2019-05-24 05:37:02.866139",
- "modified_by": "Administrator",
- "module": "Selling",
- "name": "Sales Analytics",
- "owner": "Administrator",
- "prepared_report": 0,
- "ref_doctype": "Sales Order",
- "report_name": "Sales Analytics",
- "report_type": "Script Report",
+ "add_total_row": 1,
+ "creation": "2018-09-21 12:46:29.451048",
+ "disable_prepared_report": 0,
+ "disabled": 0,
+ "docstatus": 0,
+ "doctype": "Report",
+ "idx": 0,
+ "is_standard": "Yes",
+ "modified": "2020-06-19 17:41:03.132101",
+ "modified_by": "Administrator",
+ "module": "Selling",
+ "name": "Sales Analytics",
+ "owner": "Administrator",
+ "prepared_report": 0,
+ "ref_doctype": "Sales Order",
+ "report_name": "Sales Analytics",
+ "report_type": "Script Report",
"roles": [
{
"role": "Stock User"
- },
+ },
{
"role": "Maintenance User"
- },
+ },
{
"role": "Accounts User"
- },
+ },
{
"role": "Sales Manager"
}
diff --git a/erpnext/selling/report/sales_analytics/sales_analytics.py b/erpnext/selling/report/sales_analytics/sales_analytics.py
index f1726ab8bf7..4d113c8e9e9 100644
--- a/erpnext/selling/report/sales_analytics/sales_analytics.py
+++ b/erpnext/selling/report/sales_analytics/sales_analytics.py
@@ -23,7 +23,14 @@ class Analytics(object):
self.get_columns()
self.get_data()
self.get_chart_data()
- return self.columns, self.data, None, self.chart
+
+ # Skipping total row for tree-view reports
+ skip_total_row = 0
+
+ if self.filters.tree_type in ["Supplier Group", "Item Group", "Customer Group", "Territory"]:
+ skip_total_row = 1
+
+ return self.columns, self.data, None, self.chart, None, skip_total_row
def get_columns(self):
self.columns = [{
@@ -232,8 +239,10 @@ class Analytics(object):
self.entity_periodic_data.setdefault(d.parent, frappe._dict()).setdefault(period, 0.0)
self.entity_periodic_data[d.parent][period] += amount
total += amount
+
row["total"] = total
out = [row] + out
+
self.data = out
def get_periodic_data(self):
diff --git a/erpnext/selling/sales_common.js b/erpnext/selling/sales_common.js
index 8278745a804..15a9a1ab882 100644
--- a/erpnext/selling/sales_common.js
+++ b/erpnext/selling/sales_common.js
@@ -228,9 +228,18 @@ erpnext.selling.SellingController = erpnext.TransactionController.extend({
warehouse: function(doc, cdt, cdn) {
var me = this;
var item = frappe.get_doc(cdt, cdn);
+
+ let serial_no_count = item.serial_no
+ ? item.serial_no.split(`\n`).filter(d => d).length : 0;
+
+ if (item.serial_no && item.qty === serial_no_count) {
+ return;
+ }
+
if (item.serial_no && !item.batch_no) {
item.serial_no = null;
}
+
var has_batch_no;
frappe.db.get_value('Item', {'item_code': item.item_code}, 'has_batch_no', (r) => {
has_batch_no = r && r.has_batch_no;
@@ -413,15 +422,20 @@ erpnext.selling.SellingController = erpnext.TransactionController.extend({
*/
set_batch_number: function(cdt, cdn) {
const doc = frappe.get_doc(cdt, cdn);
- if (doc && doc.has_batch_no) {
+ if (doc && doc.has_batch_no && doc.warehouse) {
this._set_batch_number(doc);
}
},
_set_batch_number: function(doc) {
+ let args = {'item_code': doc.item_code, 'warehouse': doc.warehouse, 'qty': flt(doc.qty) * flt(doc.conversion_factor)};
+ if (doc.has_serial_no && doc.serial_no) {
+ args['serial_no'] = doc.serial_no
+ }
+
return frappe.call({
method: 'erpnext.stock.doctype.batch.batch.get_batch_no',
- args: {'item_code': doc.item_code, 'warehouse': doc.warehouse, 'qty': flt(doc.qty) * flt(doc.conversion_factor)},
+ args: args,
callback: function(r) {
if(r.message) {
frappe.model.set_value(doc.doctype, doc.name, 'batch_no', r.message);
@@ -478,13 +492,18 @@ frappe.ui.form.on(cur_frm.doctype, {
var dialog = new frappe.ui.Dialog({
title: __("Set as Lost"),
fields: [
- {"fieldtype": "Table MultiSelect",
- "label": __("Lost Reasons"),
- "fieldname": "lost_reason",
- "options": "Lost Reason Detail",
- "reqd": 1},
-
- {"fieldtype": "Text", "label": __("Detailed Reason"), "fieldname": "detailed_reason"},
+ {
+ "fieldtype": "Table MultiSelect",
+ "label": __("Lost Reasons"),
+ "fieldname": "lost_reason",
+ "options": frm.doctype === 'Opportunity' ? 'Opportunity Lost Reason Detail': 'Quotation Lost Reason Detail',
+ "reqd": 1
+ },
+ {
+ "fieldtype": "Text",
+ "label": __("Detailed Reason"),
+ "fieldname": "detailed_reason"
+ },
],
primary_action: function() {
var values = dialog.get_values();
diff --git a/erpnext/setup/doctype/company/company.js b/erpnext/setup/doctype/company/company.js
index be736d2d9d1..14cacf37bb6 100644
--- a/erpnext/setup/doctype/company/company.js
+++ b/erpnext/setup/doctype/company/company.js
@@ -202,8 +202,6 @@ cur_frm.cscript.change_abbr = function() {
if(r.exc) {
frappe.msgprint(__("There were errors."));
return;
- } else {
- cur_frm.set_value("abbr", args.new_abbr);
}
dialog.hide();
cur_frm.refresh();
diff --git a/erpnext/setup/doctype/company/company.json b/erpnext/setup/doctype/company/company.json
index dd602eca103..3eb8949a5fa 100644
--- a/erpnext/setup/doctype/company/company.json
+++ b/erpnext/setup/doctype/company/company.json
@@ -1,4 +1,5 @@
{
+ "actions": [],
"allow_import": 1,
"allow_rename": 1,
"autoname": "field:company_name",
@@ -156,6 +157,7 @@
{
"fieldname": "parent_company",
"fieldtype": "Link",
+ "ignore_user_permissions": 1,
"in_list_view": 1,
"label": "Parent Company",
"options": "Company"
@@ -276,6 +278,7 @@
"depends_on": "eval:doc.create_chart_of_accounts_based_on===\"Existing Company\"",
"fieldname": "existing_company",
"fieldtype": "Link",
+ "ignore_user_permissions": 1,
"label": "Existing Company ",
"no_copy": 1,
"options": "Company"
@@ -725,10 +728,13 @@
"icon": "fa fa-building",
"idx": 1,
"image_field": "company_logo",
- "modified": "2019-11-22 13:04:47.470768",
+ "is_tree": 1,
+ "links": [],
+ "modified": "2020-03-22 18:26:03.145312",
"modified_by": "Administrator",
"module": "Setup",
"name": "Company",
+ "nsm_parent_field": "parent_company",
"owner": "Administrator",
"permissions": [
{
diff --git a/erpnext/setup/doctype/company/company.py b/erpnext/setup/doctype/company/company.py
index ff3515485c4..335cad3598b 100644
--- a/erpnext/setup/doctype/company/company.py
+++ b/erpnext/setup/doctype/company/company.py
@@ -400,8 +400,6 @@ def replace_abbr(company, old, new):
for dt in ["Warehouse", "Account", "Cost Center", "Department",
"Sales Taxes and Charges Template", "Purchase Taxes and Charges Template"]:
_rename_records(dt)
- frappe.db.commit()
-
def get_name_with_abbr(name, company):
company_abbr = frappe.get_cached_value('Company', company, "abbr")
@@ -416,8 +414,13 @@ def install_country_fixtures(company):
company_doc = frappe.get_doc("Company", company)
path = frappe.get_app_path('erpnext', 'regional', frappe.scrub(company_doc.country))
if os.path.exists(path.encode("utf-8")):
- frappe.get_attr("erpnext.regional.{0}.setup.setup"
- .format(frappe.scrub(company_doc.country)))(company_doc, False)
+ try:
+ module_name = "erpnext.regional.{0}.setup.setup".format(frappe.scrub(company_doc.country))
+ frappe.get_attr(module_name)(company_doc, False)
+ except Exception as e:
+ frappe.log_error(title=str(e), message=frappe.get_traceback())
+ frappe.throw(_("Failed to setup defaults for country {0}. Please contact support@erpnext.com").format(frappe.bold(company_doc.country)))
+
def update_company_current_month_sales(company):
current_month_year = formatdate(today(), "MM-yyyy")
@@ -585,4 +588,4 @@ def get_default_company_address(name, sort_key='is_primary_address', existing_ad
if out:
return sorted(out, key = functools.cmp_to_key(lambda x,y: cmp(y[1], x[1])))[0][0]
else:
- return None
\ No newline at end of file
+ return None
diff --git a/erpnext/setup/doctype/company/delete_company_transactions.py b/erpnext/setup/doctype/company/delete_company_transactions.py
index 3052191943c..9979bc51e3a 100644
--- a/erpnext/setup/doctype/company/delete_company_transactions.py
+++ b/erpnext/setup/doctype/company/delete_company_transactions.py
@@ -106,11 +106,11 @@ def delete_lead_addresses(company_name):
frappe.db.sql("""update tabCustomer set lead_name=NULL where lead_name in ({leads})""".format(leads=",".join(leads)))
def delete_communications(doctype, company_name, company_fieldname):
-
+
refrence_docs = frappe.get_all(doctype, filters={company_fieldname:company_name})
reference_doctype_names = [r.name for r in refrence_docs]
communications = frappe.get_all("Communication", filters={"reference_doctype":doctype,"reference_name":["in",reference_doctype_names]})
communication_names = [c.name for c in communications]
- frappe.delete_doc("Communication",communication_names)
+ frappe.delete_doc("Communication", communication_names, ignore_permissions=True)
diff --git a/erpnext/setup/doctype/company/test_company.py b/erpnext/setup/doctype/company/test_company.py
index 524f3eb5b17..fe13d63955f 100644
--- a/erpnext/setup/doctype/company/test_company.py
+++ b/erpnext/setup/doctype/company/test_company.py
@@ -9,7 +9,7 @@ from frappe import _
from frappe.utils import random_string
from erpnext.accounts.doctype.account.chart_of_accounts.chart_of_accounts import get_charts_for_country
-test_ignore = ["Account", "Cost Center", "Payment Terms Template", "Salary Component"]
+test_ignore = ["Account", "Cost Center", "Payment Terms Template", "Salary Component", "Warehouse"]
test_dependencies = ["Fiscal Year"]
test_records = frappe.get_test_records('Company')
diff --git a/erpnext/setup/doctype/customer_group/customer_group.js b/erpnext/setup/doctype/customer_group/customer_group.js
index c199a8e57f1..44a50191209 100644
--- a/erpnext/setup/doctype/customer_group/customer_group.js
+++ b/erpnext/setup/doctype/customer_group/customer_group.js
@@ -8,7 +8,7 @@ cur_frm.cscript.refresh = function(doc, cdt, cdn) {
cur_frm.cscript.set_root_readonly = function(doc) {
// read-only for root customer group
- if(!doc.parent_customer_group) {
+ if(!doc.parent_customer_group && !doc.__islocal) {
cur_frm.set_read_only();
cur_frm.set_intro(__("This is a root customer group and cannot be edited."));
} else {
@@ -20,7 +20,8 @@ cur_frm.cscript.set_root_readonly = function(doc) {
cur_frm.fields_dict['parent_customer_group'].get_query = function(doc,cdt,cdn) {
return {
filters: {
- 'is_group': 1
+ 'is_group': 1,
+ 'name': ['!=', cur_frm.doc.customer_group_name]
}
}
}
diff --git a/erpnext/setup/doctype/customer_group/customer_group.json b/erpnext/setup/doctype/customer_group/customer_group.json
index 3565b4b38a9..82d5f7a3f73 100644
--- a/erpnext/setup/doctype/customer_group/customer_group.json
+++ b/erpnext/setup/doctype/customer_group/customer_group.json
@@ -1,11 +1,12 @@
{
- "_comments": "[]",
+ "actions": [],
"allow_import": 1,
"allow_rename": 1,
"autoname": "field:customer_group_name",
"creation": "2013-01-10 16:34:23",
"doctype": "DocType",
"document_type": "Setup",
+ "engine": "InnoDB",
"field_order": [
"customer_group_name",
"parent_customer_group",
@@ -136,10 +137,13 @@
],
"icon": "fa fa-sitemap",
"idx": 1,
- "modified": "2019-09-06 12:40:14.954697",
+ "is_tree": 1,
+ "links": [],
+ "modified": "2020-03-18 18:26:02.536305",
"modified_by": "Administrator",
"module": "Setup",
"name": "Customer Group",
+ "nsm_parent_field": "parent_customer_group",
"owner": "Administrator",
"permissions": [
{
@@ -187,8 +191,8 @@
"role": "Sales Manager"
}
],
- "quick_entry": 1,
"search_fields": "parent_customer_group",
"show_name_in_global_search": 1,
+ "sort_field": "modified",
"sort_order": "DESC"
}
\ No newline at end of file
diff --git a/erpnext/setup/doctype/customer_group/customer_group.py b/erpnext/setup/doctype/customer_group/customer_group.py
index f62613ea1bb..68e1ccb6356 100644
--- a/erpnext/setup/doctype/customer_group/customer_group.py
+++ b/erpnext/setup/doctype/customer_group/customer_group.py
@@ -6,9 +6,12 @@ import frappe
from frappe import _
-from frappe.utils.nestedset import NestedSet
+from frappe.utils.nestedset import NestedSet, get_root_of
class CustomerGroup(NestedSet):
nsm_parent_field = 'parent_customer_group'
+ def validate(self):
+ if not self.parent_customer_group:
+ self.parent_customer_group = get_root_of("Customer Group")
def on_update(self):
self.validate_name_with_customer()
diff --git a/erpnext/setup/doctype/email_digest/email_digest.py b/erpnext/setup/doctype/email_digest/email_digest.py
index 0bcddc21517..662cda26bfb 100644
--- a/erpnext/setup/doctype/email_digest/email_digest.py
+++ b/erpnext/setup/doctype/email_digest/email_digest.py
@@ -101,8 +101,7 @@ class EmailDigest(Document):
if not context.purchase_order_list:
frappe.throw(_("No items to be received are overdue"))
- if not (context.events or context.todo_list or context.notifications or context.cards
- or context.purchase_orders_items_overdue_list):
+ if not context:
return None
frappe.flags.ignore_account_permission = False
diff --git a/erpnext/setup/doctype/item_group/item_group.js b/erpnext/setup/doctype/item_group/item_group.js
index df2223192bd..9892dc3dcc0 100644
--- a/erpnext/setup/doctype/item_group/item_group.js
+++ b/erpnext/setup/doctype/item_group/item_group.js
@@ -66,7 +66,7 @@ frappe.ui.form.on("Item Group", {
set_root_readonly: function(frm) {
// read-only for root item group
frm.set_intro("");
- if(!frm.doc.parent_item_group) {
+ if(!frm.doc.parent_item_group && !frm.doc.__islocal) {
frm.set_read_only();
frm.set_intro(__("This is a root item group and cannot be edited."), true);
}
diff --git a/erpnext/setup/doctype/item_group/item_group.json b/erpnext/setup/doctype/item_group/item_group.json
index 656d460e0f5..849fb6fd911 100644
--- a/erpnext/setup/doctype/item_group/item_group.json
+++ b/erpnext/setup/doctype/item_group/item_group.json
@@ -1,817 +1,254 @@
{
- "allow_copy": 0,
- "allow_events_in_timeline": 0,
- "allow_guest_to_view": 0,
- "allow_import": 1,
- "allow_rename": 1,
- "autoname": "field:item_group_name",
- "beta": 0,
- "creation": "2013-03-28 10:35:29",
- "custom": 0,
- "description": "Item Classification",
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "Setup",
- "editable_grid": 0,
+ "actions": [],
+ "allow_import": 1,
+ "allow_rename": 1,
+ "autoname": "field:item_group_name",
+ "creation": "2013-03-28 10:35:29",
+ "description": "Item Classification",
+ "doctype": "DocType",
+ "document_type": "Setup",
+ "engine": "InnoDB",
+ "field_order": [
+ "gs",
+ "item_group_name",
+ "parent_item_group",
+ "is_group",
+ "image",
+ "column_break_5",
+ "defaults",
+ "item_group_defaults",
+ "sec_break_taxes",
+ "taxes",
+ "sb9",
+ "show_in_website",
+ "route",
+ "weightage",
+ "slideshow",
+ "description",
+ "website_specifications",
+ "lft",
+ "rgt",
+ "old_parent"
+ ],
"fields": [
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "gs",
- "fieldtype": "Section Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "General Settings",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "gs",
+ "fieldtype": "Section Break",
+ "label": "General Settings"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "item_group_name",
- "fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Item Group Name",
- "length": 0,
- "no_copy": 0,
- "oldfieldname": "item_group_name",
- "oldfieldtype": "Data",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
+ "fieldname": "item_group_name",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "label": "Item Group Name",
+ "oldfieldname": "item_group_name",
+ "oldfieldtype": "Data",
+ "reqd": 1,
"unique": 1
- },
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 1,
- "collapsible": 0,
- "columns": 0,
- "description": "",
- "fieldname": "parent_item_group",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 1,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Parent Item Group",
- "length": 0,
- "no_copy": 0,
- "oldfieldname": "parent_item_group",
- "oldfieldtype": "Link",
- "options": "Item Group",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "bold": 1,
+ "fieldname": "parent_item_group",
+ "fieldtype": "Link",
+ "ignore_user_permissions": 1,
+ "in_list_view": 1,
+ "label": "Parent Item Group",
+ "oldfieldname": "parent_item_group",
+ "oldfieldtype": "Link",
+ "options": "Item Group"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 1,
- "collapsible": 0,
- "columns": 0,
- "description": "Only leaf nodes are allowed in transaction",
- "fieldname": "is_group",
- "fieldtype": "Check",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Is Group",
- "length": 0,
- "no_copy": 0,
- "oldfieldname": "is_group",
- "oldfieldtype": "Select",
- "options": "",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "bold": 1,
+ "default": "0",
+ "description": "Only leaf nodes are allowed in transaction",
+ "fieldname": "is_group",
+ "fieldtype": "Check",
+ "in_list_view": 1,
+ "label": "Is Group",
+ "oldfieldname": "is_group",
+ "oldfieldtype": "Select"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "image",
- "fieldtype": "Attach Image",
- "hidden": 1,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Image",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "image",
+ "fieldtype": "Attach Image",
+ "hidden": 1,
+ "label": "Image"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "column_break_5",
- "fieldtype": "Column Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "column_break_5",
+ "fieldtype": "Column Break"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "defaults",
- "fieldtype": "Section Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Defaults",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "defaults",
+ "fieldtype": "Section Break",
+ "label": "Defaults"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "item_group_defaults",
- "fieldtype": "Table",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Item Group Defaults",
- "length": 0,
- "no_copy": 0,
- "options": "Item Default",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "item_group_defaults",
+ "fieldtype": "Table",
+ "label": "Item Group Defaults",
+ "options": "Item Default"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "collapsible_depends_on": "",
- "columns": 0,
- "fieldname": "sec_break_taxes",
- "fieldtype": "Section Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Item Tax",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "sec_break_taxes",
+ "fieldtype": "Section Break",
+ "label": "Item Tax"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "taxes",
- "fieldtype": "Table",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Taxes",
- "length": 0,
- "no_copy": 0,
- "options": "Item Tax",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "taxes",
+ "fieldtype": "Table",
+ "label": "Taxes",
+ "options": "Item Tax"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "sb9",
- "fieldtype": "Section Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Website Settings",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "sb9",
+ "fieldtype": "Section Break",
+ "label": "Website Settings"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "description": "Check this if you want to show in website",
- "fieldname": "show_in_website",
- "fieldtype": "Check",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Show in Website",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "default": "0",
+ "description": "Check this if you want to show in website",
+ "fieldname": "show_in_website",
+ "fieldtype": "Check",
+ "label": "Show in Website"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "depends_on": "show_in_website",
- "fieldname": "route",
- "fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Route",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
+ "depends_on": "show_in_website",
+ "fieldname": "route",
+ "fieldtype": "Data",
+ "label": "Route",
"unique": 1
- },
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "weightage",
- "fieldtype": "Int",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Weightage",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "weightage",
+ "fieldtype": "Int",
+ "label": "Weightage"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "depends_on": "show_in_website",
- "description": "Show this slideshow at the top of the page",
- "fieldname": "slideshow",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Slideshow",
- "length": 0,
- "no_copy": 0,
- "options": "Website Slideshow",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "depends_on": "show_in_website",
+ "description": "Show this slideshow at the top of the page",
+ "fieldname": "slideshow",
+ "fieldtype": "Link",
+ "label": "Slideshow",
+ "options": "Website Slideshow"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "depends_on": "show_in_website",
- "description": "HTML / Banner that will show on the top of product list.",
- "fieldname": "description",
- "fieldtype": "Text Editor",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Description",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "depends_on": "show_in_website",
+ "description": "HTML / Banner that will show on the top of product list.",
+ "fieldname": "description",
+ "fieldtype": "Text Editor",
+ "label": "Description"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "depends_on": "show_in_website",
- "fieldname": "website_specifications",
- "fieldtype": "Table",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Website Specifications",
- "length": 0,
- "no_copy": 0,
- "options": "Item Website Specification",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "depends_on": "show_in_website",
+ "fieldname": "website_specifications",
+ "fieldtype": "Table",
+ "label": "Website Specifications",
+ "options": "Item Website Specification"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "lft",
- "fieldtype": "Int",
- "hidden": 1,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "lft",
- "length": 0,
- "no_copy": 1,
- "oldfieldname": "lft",
- "oldfieldtype": "Int",
- "permlevel": 0,
- "print_hide": 1,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 1,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "lft",
+ "fieldtype": "Int",
+ "hidden": 1,
+ "label": "lft",
+ "no_copy": 1,
+ "oldfieldname": "lft",
+ "oldfieldtype": "Int",
+ "print_hide": 1,
+ "search_index": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "rgt",
- "fieldtype": "Int",
- "hidden": 1,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "rgt",
- "length": 0,
- "no_copy": 1,
- "oldfieldname": "rgt",
- "oldfieldtype": "Int",
- "permlevel": 0,
- "print_hide": 1,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 1,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "rgt",
+ "fieldtype": "Int",
+ "hidden": 1,
+ "label": "rgt",
+ "no_copy": 1,
+ "oldfieldname": "rgt",
+ "oldfieldtype": "Int",
+ "print_hide": 1,
+ "search_index": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "description": "",
- "fieldname": "old_parent",
- "fieldtype": "Link",
- "hidden": 1,
- "ignore_user_permissions": 1,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "old_parent",
- "length": 0,
- "no_copy": 1,
- "oldfieldname": "old_parent",
- "oldfieldtype": "Data",
- "options": "Item Group",
- "permlevel": 0,
- "print_hide": 1,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 1,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
+ "fieldname": "old_parent",
+ "fieldtype": "Link",
+ "hidden": 1,
+ "ignore_user_permissions": 1,
+ "label": "old_parent",
+ "no_copy": 1,
+ "oldfieldname": "old_parent",
+ "oldfieldtype": "Data",
+ "options": "Item Group",
+ "print_hide": 1,
+ "report_hide": 1
}
- ],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
- "icon": "fa fa-sitemap",
- "idx": 1,
- "image_field": "image",
- "image_view": 0,
- "in_create": 0,
- "is_submittable": 0,
- "issingle": 0,
- "istable": 0,
- "max_attachments": 3,
- "modified": "2018-11-23 15:17:28.003933",
- "modified_by": "Administrator",
- "module": "Setup",
- "name": "Item Group",
- "name_case": "Title Case",
- "owner": "Administrator",
+ ],
+ "icon": "fa fa-sitemap",
+ "idx": 1,
+ "image_field": "image",
+ "is_tree": 1,
+ "links": [],
+ "max_attachments": 3,
+ "modified": "2020-03-18 18:25:59.979083",
+ "modified_by": "Administrator",
+ "module": "Setup",
+ "name": "Item Group",
+ "name_case": "Title Case",
+ "nsm_parent_field": "parent_item_group",
+ "owner": "Administrator",
"permissions": [
{
- "amend": 0,
- "cancel": 0,
- "create": 0,
- "delete": 0,
- "email": 1,
- "export": 1,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "Stock Manager",
- "set_user_permissions": 0,
- "share": 0,
- "submit": 0,
- "write": 0
- },
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Stock Manager"
+ },
{
- "amend": 0,
- "cancel": 0,
- "create": 0,
- "delete": 0,
- "email": 1,
- "export": 0,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "Stock User",
- "set_user_permissions": 0,
- "share": 0,
- "submit": 0,
- "write": 0
- },
+ "email": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Stock User"
+ },
{
- "amend": 0,
- "cancel": 0,
- "create": 1,
- "delete": 1,
- "email": 1,
- "export": 1,
- "if_owner": 0,
- "import": 1,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "Item Manager",
- "set_user_permissions": 1,
- "share": 1,
- "submit": 0,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "import": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Item Manager",
+ "set_user_permissions": 1,
+ "share": 1,
"write": 1
- },
+ },
{
- "amend": 0,
- "cancel": 0,
- "create": 0,
- "delete": 0,
- "email": 1,
- "export": 0,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "Sales User",
- "set_user_permissions": 0,
- "share": 0,
- "submit": 0,
- "write": 0
- },
+ "email": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Sales User"
+ },
{
- "amend": 0,
- "cancel": 0,
- "create": 0,
- "delete": 0,
- "email": 1,
- "export": 0,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "Purchase User",
- "set_user_permissions": 0,
- "share": 0,
- "submit": 0,
- "write": 0
- },
+ "email": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Purchase User"
+ },
{
- "amend": 0,
- "cancel": 0,
- "create": 0,
- "delete": 0,
- "email": 1,
- "export": 0,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "Accounts User",
- "set_user_permissions": 0,
- "share": 0,
- "submit": 0,
- "write": 0
+ "email": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Accounts User"
}
- ],
- "quick_entry": 1,
- "read_only": 0,
- "read_only_onload": 0,
- "search_fields": "parent_item_group",
- "show_name_in_global_search": 1,
- "sort_order": "DESC",
- "track_changes": 0,
- "track_seen": 0,
- "track_views": 0
-}
+ ],
+ "search_fields": "parent_item_group",
+ "show_name_in_global_search": 1,
+ "sort_field": "modified",
+ "sort_order": "DESC"
+}
\ No newline at end of file
diff --git a/erpnext/setup/doctype/item_group/item_group.py b/erpnext/setup/doctype/item_group/item_group.py
index 22375ae22c3..43778404b60 100644
--- a/erpnext/setup/doctype/item_group/item_group.py
+++ b/erpnext/setup/doctype/item_group/item_group.py
@@ -119,7 +119,7 @@ def get_product_list_for_group(product_group=None, start=0, limit=10, search=Non
or I.name like %(search)s)"""
search = "%" + cstr(search) + "%"
- query += """order by I.weightage desc, in_stock desc, I.modified desc limit %s, %s""" % (start, limit)
+ query += """order by I.weightage desc, in_stock desc, I.modified desc limit %s, %s""" % (cint(start), cint(limit))
data = frappe.db.sql(query, {"product_group": product_group,"search": search, "today": nowdate()}, as_dict=1)
data = adjust_qty_for_expired_items(data)
@@ -174,15 +174,11 @@ def get_child_groups(item_group_name):
and show_in_website = 1""", {"lft": item_group.lft, "rgt": item_group.rgt})
def get_child_item_groups(item_group_name):
- child_item_groups = frappe.cache().hget("child_item_groups", item_group_name)
+ item_group = frappe.get_cached_value("Item Group",
+ item_group_name, ["lft", "rgt"], as_dict=1)
- if not child_item_groups:
- item_group = frappe.get_cached_doc("Item Group", item_group_name)
-
- child_item_groups = [d.name for d in frappe.get_all('Item Group',
- filters= {'lft': ('>=', item_group.lft),'rgt': ('>=', item_group.rgt)})]
-
- frappe.cache().hset("child_item_groups", item_group_name, child_item_groups)
+ child_item_groups = [d.name for d in frappe.get_all('Item Group',
+ filters= {'lft': ('>=', item_group.lft),'rgt': ('<=', item_group.rgt)})]
return child_item_groups or {}
diff --git a/erpnext/setup/doctype/party_type/party_type.py b/erpnext/setup/doctype/party_type/party_type.py
index b29c305ee7f..96e60936a4b 100644
--- a/erpnext/setup/doctype/party_type/party_type.py
+++ b/erpnext/setup/doctype/party_type/party_type.py
@@ -10,6 +10,7 @@ class PartyType(Document):
pass
@frappe.whitelist()
+@frappe.validate_and_sanitize_search_inputs
def get_party_type(doctype, txt, searchfield, start, page_len, filters):
cond = ''
if filters and filters.get('account'):
diff --git a/erpnext/setup/doctype/quotation_lost_reason_detail/__init__.py b/erpnext/setup/doctype/quotation_lost_reason_detail/__init__.py
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/erpnext/setup/doctype/quotation_lost_reason_detail/quotation_lost_reason_detail.json b/erpnext/setup/doctype/quotation_lost_reason_detail/quotation_lost_reason_detail.json
new file mode 100644
index 00000000000..898878f6bdb
--- /dev/null
+++ b/erpnext/setup/doctype/quotation_lost_reason_detail/quotation_lost_reason_detail.json
@@ -0,0 +1,29 @@
+{
+ "creation": "2020-07-14 09:21:44.057724",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "lost_reason"
+ ],
+ "fields": [
+ {
+ "fieldname": "lost_reason",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Quotation Lost Reason",
+ "options": "Quotation Lost Reason"
+ }
+ ],
+ "istable": 1,
+ "modified": "2020-08-12 23:33:14.490491",
+ "modified_by": "Administrator",
+ "module": "Setup",
+ "name": "Quotation Lost Reason Detail",
+ "owner": "Administrator",
+ "permissions": [],
+ "quick_entry": 1,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/erpnext/setup/doctype/quotation_lost_reason_detail/quotation_lost_reason_detail.py b/erpnext/setup/doctype/quotation_lost_reason_detail/quotation_lost_reason_detail.py
new file mode 100644
index 00000000000..7bb8d02670e
--- /dev/null
+++ b/erpnext/setup/doctype/quotation_lost_reason_detail/quotation_lost_reason_detail.py
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+# import frappe
+from frappe.model.document import Document
+
+class QuotationLostReasonDetail(Document):
+ pass
diff --git a/erpnext/setup/doctype/sales_person/sales_person.js b/erpnext/setup/doctype/sales_person/sales_person.js
index 9ff37fa4e83..8f7593d6eef 100644
--- a/erpnext/setup/doctype/sales_person/sales_person.js
+++ b/erpnext/setup/doctype/sales_person/sales_person.js
@@ -19,6 +19,11 @@ frappe.ui.form.on('Sales Person', {
}
}
};
+
+ frm.make_methods = {
+ 'Sales Order': () => frappe.new_doc("Sales Order")
+ .then(() => frm.add_child("sales_team", {"sales_person": frm.doc.name}))
+ }
}
});
@@ -28,7 +33,7 @@ cur_frm.cscript.refresh = function(doc, cdt, cdn) {
cur_frm.cscript.set_root_readonly = function(doc) {
// read-only for root
- if(!doc.parent_sales_person) {
+ if(!doc.parent_sales_person && !doc.__islocal) {
cur_frm.set_read_only();
cur_frm.set_intro(__("This is a root sales person and cannot be edited."));
} else {
diff --git a/erpnext/setup/doctype/sales_person/sales_person.json b/erpnext/setup/doctype/sales_person/sales_person.json
index 27f79eb41f2..d7a9d5dfba1 100644
--- a/erpnext/setup/doctype/sales_person/sales_person.json
+++ b/erpnext/setup/doctype/sales_person/sales_person.json
@@ -1,579 +1,185 @@
{
- "allow_copy": 0,
- "allow_events_in_timeline": 0,
- "allow_guest_to_view": 0,
+ "actions": [],
"allow_import": 1,
"allow_rename": 1,
"autoname": "field:sales_person_name",
- "beta": 0,
"creation": "2013-01-10 16:34:24",
- "custom": 0,
"description": "All Sales Transactions can be tagged against multiple **Sales Persons** so that you can set and monitor targets.",
- "docstatus": 0,
"doctype": "DocType",
"document_type": "Setup",
- "editable_grid": 0,
"engine": "InnoDB",
+ "field_order": [
+ "name_and_employee_id",
+ "sales_person_name",
+ "parent_sales_person",
+ "commission_rate",
+ "is_group",
+ "enabled",
+ "cb0",
+ "employee",
+ "department",
+ "lft",
+ "rgt",
+ "old_parent",
+ "target_details_section_break",
+ "targets"
+ ],
"fields": [
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_if_empty": 0,
"fieldname": "name_and_employee_id",
"fieldtype": "Section Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
"label": "Name and Employee ID",
- "length": 0,
- "no_copy": 0,
- "options": "icon-user",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
+ "options": "icon-user"
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_if_empty": 0,
"fieldname": "sales_person_name",
"fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
"in_list_view": 1,
- "in_standard_filter": 0,
"label": "Sales Person Name",
- "length": 0,
- "no_copy": 0,
"oldfieldname": "sales_person_name",
"oldfieldtype": "Data",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
"reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
"unique": 1
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"description": "Select company name first.",
- "fetch_if_empty": 0,
"fieldname": "parent_sales_person",
"fieldtype": "Link",
- "hidden": 0,
"ignore_user_permissions": 1,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
"in_list_view": 1,
- "in_standard_filter": 0,
"label": "Parent Sales Person",
- "length": 0,
- "no_copy": 0,
"oldfieldname": "parent_sales_person",
"oldfieldtype": "Link",
- "options": "Sales Person",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
+ "options": "Sales Person"
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_if_empty": 0,
"fieldname": "commission_rate",
"fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
"label": "Commission Rate",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 1,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
+ "print_hide": 1
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_if_empty": 0,
+ "default": "0",
"fieldname": "is_group",
"fieldtype": "Check",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
"in_list_view": 1,
- "in_standard_filter": 0,
"label": "Is Group",
- "length": 0,
- "no_copy": 0,
"oldfieldname": "is_group",
"oldfieldtype": "Select",
- "options": "",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
+ "reqd": 1
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"default": "1",
- "fetch_if_empty": 0,
"fieldname": "enabled",
"fieldtype": "Check",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Enabled",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
+ "label": "Enabled"
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_if_empty": 0,
"fieldname": "cb0",
- "fieldtype": "Column Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
+ "fieldtype": "Column Break"
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_if_empty": 0,
"fieldname": "employee",
"fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
"label": "Employee",
- "length": 0,
- "no_copy": 0,
- "options": "Employee",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
+ "options": "Employee"
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fetch_from": "employee.department",
- "fetch_if_empty": 0,
"fieldname": "department",
"fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
"label": "Department",
- "length": 0,
- "no_copy": 0,
"options": "Department",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
+ "read_only": 1
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_if_empty": 0,
"fieldname": "lft",
"fieldtype": "Int",
"hidden": 1,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
"label": "lft",
- "length": 0,
"no_copy": 1,
"oldfieldname": "lft",
"oldfieldtype": "Int",
- "permlevel": 0,
"print_hide": 1,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 1,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
+ "search_index": 1
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_if_empty": 0,
"fieldname": "rgt",
"fieldtype": "Int",
"hidden": 1,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
"label": "rgt",
- "length": 0,
"no_copy": 1,
"oldfieldname": "rgt",
"oldfieldtype": "Int",
- "permlevel": 0,
"print_hide": 1,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 1,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
+ "search_index": 1
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_if_empty": 0,
"fieldname": "old_parent",
"fieldtype": "Data",
"hidden": 1,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
"label": "old_parent",
- "length": 0,
"no_copy": 1,
"oldfieldname": "old_parent",
"oldfieldtype": "Data",
- "permlevel": 0,
- "print_hide": 1,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
+ "print_hide": 1
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"description": "Set targets Item Group-wise for this Sales Person.",
- "fetch_if_empty": 0,
"fieldname": "target_details_section_break",
"fieldtype": "Section Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
"label": "Sales Person Targets",
- "length": 0,
- "no_copy": 0,
"oldfieldtype": "Section Break",
- "options": "icon-bullseye",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
+ "options": "icon-bullseye"
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_if_empty": 0,
"fieldname": "targets",
"fieldtype": "Table",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
"label": "Targets",
- "length": 0,
- "no_copy": 0,
"oldfieldname": "target_details",
"oldfieldtype": "Table",
- "options": "Target Detail",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
+ "options": "Target Detail"
}
],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
"icon": "icon-user",
"idx": 1,
- "image_view": 0,
- "in_create": 0,
- "is_submittable": 0,
- "issingle": 0,
- "istable": 0,
- "max_attachments": 0,
- "modified": "2019-03-22 16:26:01.706129",
+ "is_tree": 1,
+ "links": [],
+ "modified": "2020-03-18 18:26:01.076203",
"modified_by": "Administrator",
"module": "Setup",
"name": "Sales Person",
+ "nsm_parent_field": "parent_sales_person",
"owner": "Administrator",
"permissions": [
{
- "amend": 0,
- "cancel": 0,
- "create": 0,
- "delete": 0,
"email": 1,
- "export": 0,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
- "role": "Sales Manager",
- "set_user_permissions": 0,
- "share": 0,
- "submit": 0,
- "write": 0
+ "role": "Sales Manager"
},
{
- "amend": 0,
- "cancel": 0,
- "create": 0,
- "delete": 0,
"email": 1,
- "export": 0,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
- "role": "Sales User",
- "set_user_permissions": 0,
- "share": 0,
- "submit": 0,
- "write": 0
+ "role": "Sales User"
},
{
- "amend": 0,
- "cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
- "export": 0,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "Sales Master Manager",
- "set_user_permissions": 0,
"share": 1,
- "submit": 0,
"write": 1
}
],
- "quick_entry": 1,
- "read_only": 0,
- "read_only_onload": 0,
"search_fields": "parent_sales_person",
"show_name_in_global_search": 1,
- "sort_order": "ASC",
- "track_changes": 0,
- "track_seen": 0,
- "track_views": 0
+ "sort_field": "modified",
+ "sort_order": "ASC"
}
\ No newline at end of file
diff --git a/erpnext/setup/doctype/sales_person/sales_person.py b/erpnext/setup/doctype/sales_person/sales_person.py
index 3379534cf82..19c2e5b9543 100644
--- a/erpnext/setup/doctype/sales_person/sales_person.py
+++ b/erpnext/setup/doctype/sales_person/sales_person.py
@@ -5,13 +5,16 @@ from __future__ import unicode_literals
import frappe
from frappe import _
from frappe.utils import flt
-from frappe.utils.nestedset import NestedSet
+from frappe.utils.nestedset import NestedSet, get_root_of
from erpnext import get_default_currency
class SalesPerson(NestedSet):
nsm_parent_field = 'parent_sales_person'
def validate(self):
+ if not self.parent_sales_person:
+ self.parent_sales_person = get_root_of("Sales Person")
+
for d in self.get('targets') or []:
if not flt(d.target_qty) and not flt(d.target_amount):
frappe.throw(_("Either target qty or target amount is mandatory."))
diff --git a/erpnext/setup/doctype/supplier_group/supplier_group.js b/erpnext/setup/doctype/supplier_group/supplier_group.js
index ac5bda6e2c6..e75030d4414 100644
--- a/erpnext/setup/doctype/supplier_group/supplier_group.js
+++ b/erpnext/setup/doctype/supplier_group/supplier_group.js
@@ -8,7 +8,7 @@ cur_frm.cscript.refresh = function(doc) {
cur_frm.cscript.set_root_readonly = function(doc) {
// read-only for root customer group
- if(!doc.parent_supplier_group) {
+ if(!doc.parent_supplier_group && !doc.__islocal) {
cur_frm.set_read_only();
cur_frm.set_intro(__("This is a root supplier group and cannot be edited."));
} else {
@@ -20,7 +20,8 @@ cur_frm.cscript.set_root_readonly = function(doc) {
cur_frm.fields_dict['parent_supplier_group'].get_query = function() {
return {
filters: {
- 'is_group': 1
+ 'is_group': 1,
+ 'name': ['!=', cur_frm.doc.supplier_group_name]
}
};
};
diff --git a/erpnext/setup/doctype/supplier_group/supplier_group.json b/erpnext/setup/doctype/supplier_group/supplier_group.json
index 5c413341935..b7989d4ffad 100644
--- a/erpnext/setup/doctype/supplier_group/supplier_group.json
+++ b/erpnext/setup/doctype/supplier_group/supplier_group.json
@@ -1,481 +1,165 @@
{
- "allow_copy": 0,
- "allow_guest_to_view": 0,
+ "actions": [],
"allow_import": 1,
"allow_rename": 1,
"autoname": "field:supplier_group_name",
- "beta": 0,
"creation": "2013-01-10 16:34:24",
- "custom": 0,
- "docstatus": 0,
"doctype": "DocType",
"document_type": "Setup",
- "editable_grid": 0,
+ "engine": "InnoDB",
+ "field_order": [
+ "supplier_group_name",
+ "parent_supplier_group",
+ "is_group",
+ "section_credit_limit",
+ "payment_terms",
+ "default_payable_account",
+ "accounts",
+ "lft",
+ "rgt",
+ "old_parent"
+ ],
"fields": [
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fieldname": "supplier_group_name",
"fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
"label": "Supplier Group Name",
- "length": 0,
- "no_copy": 0,
"oldfieldname": "supplier_type",
"oldfieldtype": "Data",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
"reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
"unique": 1
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
"bold": 1,
- "collapsible": 0,
- "columns": 0,
"fieldname": "parent_supplier_group",
"fieldtype": "Link",
- "hidden": 0,
"ignore_user_permissions": 1,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
"in_list_view": 1,
- "in_standard_filter": 0,
"label": "Parent Supplier Group",
- "length": 0,
- "no_copy": 0,
- "options": "Supplier Group",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
+ "options": "Supplier Group"
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
"bold": 1,
- "collapsible": 0,
- "columns": 0,
+ "default": "0",
"fieldname": "is_group",
"fieldtype": "Check",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
"in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Is Group",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
+ "label": "Is Group"
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
"collapsible": 1,
- "columns": 0,
"fieldname": "section_credit_limit",
"fieldtype": "Section Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Credit Limit",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
+ "label": "Credit Limit"
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fieldname": "payment_terms",
"fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
"label": "Default Payment Terms Template",
- "length": 0,
- "no_copy": 0,
- "options": "Payment Terms Template",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
+ "options": "Payment Terms Template"
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fieldname": "default_payable_account",
"fieldtype": "Section Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Default Payable Account",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
+ "label": "Default Payable Account"
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"depends_on": "eval:!doc.__islocal",
"description": "Mention if non-standard receivable account applicable",
"fieldname": "accounts",
"fieldtype": "Table",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
"label": "Accounts",
- "length": 0,
- "no_copy": 0,
- "options": "Party Account",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
+ "options": "Party Account"
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fieldname": "lft",
"fieldtype": "Int",
"hidden": 1,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
"label": "lft",
- "length": 0,
"no_copy": 1,
- "permlevel": 0,
- "precision": "",
"print_hide": 1,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
"report_hide": 1,
- "reqd": 0,
- "search_index": 1,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
+ "search_index": 1
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fieldname": "rgt",
"fieldtype": "Int",
"hidden": 1,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
"label": "rgt",
- "length": 0,
"no_copy": 1,
- "permlevel": 0,
- "precision": "",
"print_hide": 1,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
"report_hide": 1,
- "reqd": 0,
- "search_index": 1,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
+ "search_index": 1
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fieldname": "old_parent",
"fieldtype": "Link",
"hidden": 1,
"ignore_user_permissions": 1,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
"label": "Old Parent",
- "length": 0,
"no_copy": 1,
"options": "Supplier Group",
- "permlevel": 0,
- "precision": "",
"print_hide": 1,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 1,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
+ "report_hide": 1
}
],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
"icon": "fa fa-flag",
"idx": 1,
- "image_view": 0,
- "in_create": 0,
- "is_submittable": 0,
- "issingle": 0,
- "istable": 0,
- "max_attachments": 0,
- "modified": "2018-08-29 06:25:57.589824",
+ "is_tree": 1,
+ "links": [],
+ "modified": "2020-03-18 18:26:07.179330",
"modified_by": "Administrator",
"module": "Setup",
"name": "Supplier Group",
+ "nsm_parent_field": "parent_supplier_group",
"owner": "Administrator",
"permissions": [
{
- "amend": 0,
- "cancel": 0,
- "create": 0,
- "delete": 0,
"email": 1,
- "export": 0,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
- "role": "Purchase Manager",
- "set_user_permissions": 0,
- "share": 0,
- "submit": 0,
- "write": 0
+ "role": "Purchase Manager"
},
{
- "amend": 0,
- "cancel": 0,
- "create": 0,
- "delete": 0,
"email": 1,
- "export": 0,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
- "role": "Purchase User",
- "set_user_permissions": 0,
- "share": 0,
- "submit": 0,
- "write": 0
+ "role": "Purchase User"
},
{
- "amend": 0,
- "cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
- "if_owner": 0,
"import": 1,
- "permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "Purchase Master Manager",
"set_user_permissions": 1,
"share": 1,
- "submit": 0,
"write": 1
},
{
- "amend": 0,
- "cancel": 0,
- "create": 0,
- "delete": 0,
- "email": 0,
- "export": 0,
- "if_owner": 0,
- "import": 0,
"permlevel": 1,
- "print": 0,
"read": 1,
- "report": 0,
"role": "Purchase Master Manager",
- "set_user_permissions": 0,
- "share": 0,
- "submit": 0,
"write": 1
},
{
- "amend": 0,
- "cancel": 0,
- "create": 0,
- "delete": 0,
- "email": 0,
- "export": 0,
- "if_owner": 0,
- "import": 0,
"permlevel": 1,
- "print": 0,
"read": 1,
- "report": 0,
- "role": "Purchase Manager",
- "set_user_permissions": 0,
- "share": 0,
- "submit": 0,
- "write": 0
+ "role": "Purchase Manager"
},
{
- "amend": 0,
- "cancel": 0,
- "create": 0,
- "delete": 0,
- "email": 0,
- "export": 0,
- "if_owner": 0,
- "import": 0,
"permlevel": 1,
- "print": 0,
"read": 1,
- "report": 0,
- "role": "Purchase User",
- "set_user_permissions": 0,
- "share": 0,
- "submit": 0,
- "write": 0
+ "role": "Purchase User"
}
],
- "quick_entry": 0,
- "read_only": 0,
- "read_only_onload": 0,
"show_name_in_global_search": 1,
- "sort_order": "ASC",
- "track_changes": 0,
- "track_seen": 0,
- "track_views": 0
+ "sort_field": "modified",
+ "sort_order": "ASC"
}
\ No newline at end of file
diff --git a/erpnext/setup/doctype/terms_and_conditions/terms_and_conditions.json b/erpnext/setup/doctype/terms_and_conditions/terms_and_conditions.json
index aba6a791a4e..28d1d16a051 100644
--- a/erpnext/setup/doctype/terms_and_conditions/terms_and_conditions.json
+++ b/erpnext/setup/doctype/terms_and_conditions/terms_and_conditions.json
@@ -1,4 +1,5 @@
{
+ "actions": [],
"allow_import": 1,
"allow_rename": 1,
"autoname": "field:title",
@@ -49,7 +50,7 @@
"fieldname": "terms_and_conditions_help",
"fieldtype": "HTML",
"label": "Terms and Conditions Help",
- "options": "
Standard Terms and Conditions Example \n\n
Delivery Terms for Order number {{ name }}\n\n-Order Date : {{ transaction_date }} \n-Expected Delivery Date : {{ delivery_date }}\n \n\n
How to get fieldnames \n\n
The fieldnames you can use in your email template are the fields in the document from which you are sending the email. You can find out the fields of any documents via Setup > Customize Form View and selecting the document type (e.g. Sales Invoice)
\n\n
Templating \n\n
Templates are compiled using the Jinja Templating Langauge. To learn more about Jinja, read this documentation.
"
+ "options": "
Standard Terms and Conditions Example \n\n
Delivery Terms for Order number {{ name }}\n\n-Order Date : {{ transaction_date }} \n-Expected Delivery Date : {{ delivery_date }}\n \n\n
How to get fieldnames \n\n
The fieldnames you can use in your email template are the fields in the document from which you are sending the email. You can find out the fields of any documents via Setup > Customize Form View and selecting the document type (e.g. Sales Invoice)
\n\n
Templating \n\n
Templates are compiled using the Jinja Templating Language. To learn more about Jinja, read this documentation.
"
},
{
"fieldname": "applicable_modules_section",
@@ -81,7 +82,8 @@
],
"icon": "icon-legal",
"idx": 1,
- "modified": "2019-07-04 13:31:30.393425",
+ "links": [],
+ "modified": "2020-06-16 22:54:38.094844",
"modified_by": "Administrator",
"module": "Setup",
"name": "Terms and Conditions",
diff --git a/erpnext/setup/doctype/territory/territory.js b/erpnext/setup/doctype/territory/territory.js
index 1eb9958ce70..ceec47ae8c6 100644
--- a/erpnext/setup/doctype/territory/territory.js
+++ b/erpnext/setup/doctype/territory/territory.js
@@ -20,7 +20,7 @@ cur_frm.cscript.refresh = function(doc, cdt, cdn) {
cur_frm.cscript.set_root_readonly = function(doc) {
// read-only for root territory
- if(!doc.parent_territory) {
+ if(!doc.parent_territory && !doc.__islocal) {
cur_frm.set_read_only();
cur_frm.set_intro(__("This is a root territory and cannot be edited."));
} else {
diff --git a/erpnext/setup/doctype/territory/territory.json b/erpnext/setup/doctype/territory/territory.json
index beadb483779..4277fe75ec4 100644
--- a/erpnext/setup/doctype/territory/territory.json
+++ b/erpnext/setup/doctype/territory/territory.json
@@ -1,487 +1,175 @@
{
- "allow_copy": 0,
- "allow_events_in_timeline": 0,
- "allow_guest_to_view": 0,
- "allow_import": 1,
- "allow_rename": 1,
- "autoname": "field:territory_name",
- "beta": 0,
- "creation": "2013-01-10 16:34:24",
- "custom": 0,
- "description": "Classification of Customers by region",
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "Setup",
- "editable_grid": 0,
+ "actions": [],
+ "allow_import": 1,
+ "allow_rename": 1,
+ "autoname": "field:territory_name",
+ "creation": "2013-01-10 16:34:24",
+ "description": "Classification of Customers by region",
+ "doctype": "DocType",
+ "document_type": "Setup",
+ "engine": "InnoDB",
+ "field_order": [
+ "territory_name",
+ "parent_territory",
+ "is_group",
+ "cb0",
+ "territory_manager",
+ "lft",
+ "rgt",
+ "old_parent",
+ "target_details_section_break",
+ "targets"
+ ],
"fields": [
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_if_empty": 0,
- "fieldname": "territory_name",
- "fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Territory Name",
- "length": 0,
- "no_copy": 1,
- "oldfieldname": "territory_name",
- "oldfieldtype": "Data",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
+ "fieldname": "territory_name",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "label": "Territory Name",
+ "no_copy": 1,
+ "oldfieldname": "territory_name",
+ "oldfieldtype": "Data",
+ "reqd": 1,
"unique": 1
- },
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 1,
- "collapsible": 0,
- "columns": 0,
- "description": "",
- "fetch_if_empty": 0,
- "fieldname": "parent_territory",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 1,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Parent Territory",
- "length": 0,
- "no_copy": 0,
- "oldfieldname": "parent_territory",
- "oldfieldtype": "Link",
- "options": "Territory",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "bold": 1,
+ "fieldname": "parent_territory",
+ "fieldtype": "Link",
+ "ignore_user_permissions": 1,
+ "in_list_view": 1,
+ "label": "Parent Territory",
+ "oldfieldname": "parent_territory",
+ "oldfieldtype": "Link",
+ "options": "Territory"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 1,
- "collapsible": 0,
- "columns": 0,
- "description": "",
- "fetch_if_empty": 0,
- "fieldname": "is_group",
- "fieldtype": "Check",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Is Group",
- "length": 0,
- "no_copy": 0,
- "oldfieldname": "is_group",
- "oldfieldtype": "Select",
- "options": "",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "bold": 1,
+ "default": "0",
+ "fieldname": "is_group",
+ "fieldtype": "Check",
+ "in_list_view": 1,
+ "label": "Is Group",
+ "oldfieldname": "is_group",
+ "oldfieldtype": "Select"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_if_empty": 0,
- "fieldname": "cb0",
- "fieldtype": "Column Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "cb0",
+ "fieldtype": "Column Break"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "description": "For reference",
- "fetch_if_empty": 0,
- "fieldname": "territory_manager",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Territory Manager",
- "length": 0,
- "no_copy": 0,
- "oldfieldname": "territory_manager",
- "oldfieldtype": "Link",
- "options": "Sales Person",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 1,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "description": "For reference",
+ "fieldname": "territory_manager",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Territory Manager",
+ "oldfieldname": "territory_manager",
+ "oldfieldtype": "Link",
+ "options": "Sales Person",
+ "search_index": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_if_empty": 0,
- "fieldname": "lft",
- "fieldtype": "Int",
- "hidden": 1,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "lft",
- "length": 0,
- "no_copy": 1,
- "oldfieldname": "lft",
- "oldfieldtype": "Int",
- "permlevel": 0,
- "print_hide": 1,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 1,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "lft",
+ "fieldtype": "Int",
+ "hidden": 1,
+ "label": "lft",
+ "no_copy": 1,
+ "oldfieldname": "lft",
+ "oldfieldtype": "Int",
+ "print_hide": 1,
+ "search_index": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_if_empty": 0,
- "fieldname": "rgt",
- "fieldtype": "Int",
- "hidden": 1,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "rgt",
- "length": 0,
- "no_copy": 1,
- "oldfieldname": "rgt",
- "oldfieldtype": "Int",
- "permlevel": 0,
- "print_hide": 1,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 1,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "rgt",
+ "fieldtype": "Int",
+ "hidden": 1,
+ "label": "rgt",
+ "no_copy": 1,
+ "oldfieldname": "rgt",
+ "oldfieldtype": "Int",
+ "print_hide": 1,
+ "search_index": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "description": "",
- "fetch_if_empty": 0,
- "fieldname": "old_parent",
- "fieldtype": "Link",
- "hidden": 1,
- "ignore_user_permissions": 1,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "old_parent",
- "length": 0,
- "no_copy": 1,
- "oldfieldname": "old_parent",
- "oldfieldtype": "Data",
- "options": "Territory",
- "permlevel": 0,
- "print_hide": 1,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 1,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "old_parent",
+ "fieldtype": "Link",
+ "hidden": 1,
+ "ignore_user_permissions": 1,
+ "label": "old_parent",
+ "no_copy": 1,
+ "oldfieldname": "old_parent",
+ "oldfieldtype": "Data",
+ "options": "Territory",
+ "print_hide": 1,
+ "report_hide": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "description": "Set Item Group-wise budgets on this Territory. You can also include seasonality by setting the Distribution.",
- "fetch_if_empty": 0,
- "fieldname": "target_details_section_break",
- "fieldtype": "Section Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Territory Targets",
- "length": 0,
- "no_copy": 0,
- "oldfieldtype": "Section Break",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "description": "Set Item Group-wise budgets on this Territory. You can also include seasonality by setting the Distribution.",
+ "fieldname": "target_details_section_break",
+ "fieldtype": "Section Break",
+ "label": "Territory Targets",
+ "oldfieldtype": "Section Break"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_if_empty": 0,
- "fieldname": "targets",
- "fieldtype": "Table",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Targets",
- "length": 0,
- "no_copy": 0,
- "oldfieldname": "target_details",
- "oldfieldtype": "Table",
- "options": "Target Detail",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
+ "fieldname": "targets",
+ "fieldtype": "Table",
+ "label": "Targets",
+ "oldfieldname": "target_details",
+ "oldfieldtype": "Table",
+ "options": "Target Detail"
}
- ],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
- "icon": "fa fa-map-marker",
- "idx": 1,
- "image_view": 0,
- "in_create": 0,
- "is_submittable": 0,
- "issingle": 0,
- "istable": 0,
- "max_attachments": 0,
- "modified": "2019-03-21 16:26:58.581431",
- "modified_by": "Administrator",
- "module": "Setup",
- "name": "Territory",
- "name_case": "Title Case",
- "owner": "Administrator",
+ ],
+ "icon": "fa fa-map-marker",
+ "idx": 1,
+ "is_tree": 1,
+ "links": [],
+ "modified": "2020-03-18 18:26:02.055750",
+ "modified_by": "Administrator",
+ "module": "Setup",
+ "name": "Territory",
+ "name_case": "Title Case",
+ "nsm_parent_field": "parent_territory",
+ "owner": "Administrator",
"permissions": [
{
- "amend": 0,
- "cancel": 0,
- "create": 1,
- "delete": 1,
- "email": 1,
- "export": 1,
- "if_owner": 0,
- "import": 1,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "Sales Master Manager",
- "set_user_permissions": 1,
- "share": 1,
- "submit": 0,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "import": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Sales Master Manager",
+ "set_user_permissions": 1,
+ "share": 1,
"write": 1
- },
+ },
{
- "amend": 0,
- "cancel": 0,
- "create": 0,
- "delete": 0,
- "email": 1,
- "export": 0,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "Sales Manager",
- "set_user_permissions": 0,
- "share": 0,
- "submit": 0,
- "write": 0
- },
+ "email": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Sales Manager"
+ },
{
- "amend": 0,
- "cancel": 0,
- "create": 0,
- "delete": 0,
- "email": 1,
- "export": 0,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "Sales User",
- "set_user_permissions": 0,
- "share": 0,
- "submit": 0,
- "write": 0
- },
+ "email": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Sales User"
+ },
{
- "amend": 0,
- "cancel": 0,
- "create": 0,
- "delete": 0,
- "email": 0,
- "export": 0,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
- "print": 0,
- "read": 1,
- "report": 0,
- "role": "Stock User",
- "set_user_permissions": 0,
- "share": 0,
- "submit": 0,
- "write": 0
- },
+ "read": 1,
+ "role": "Stock User"
+ },
{
- "amend": 0,
- "cancel": 0,
- "create": 0,
- "delete": 0,
- "email": 0,
- "export": 0,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
- "print": 0,
- "read": 1,
- "report": 0,
- "role": "Maintenance User",
- "set_user_permissions": 0,
- "share": 0,
- "submit": 0,
- "write": 0
+ "read": 1,
+ "role": "Maintenance User"
}
- ],
- "quick_entry": 1,
- "read_only": 0,
- "read_only_onload": 0,
- "search_fields": "parent_territory,territory_manager",
- "show_name_in_global_search": 1,
- "sort_order": "DESC",
- "track_changes": 0,
- "track_seen": 0,
- "track_views": 0
+ ],
+ "search_fields": "parent_territory,territory_manager",
+ "show_name_in_global_search": 1,
+ "sort_field": "modified",
+ "sort_order": "DESC"
}
\ No newline at end of file
diff --git a/erpnext/setup/doctype/territory/territory.py b/erpnext/setup/doctype/territory/territory.py
index 095bd1c179c..89423b5a693 100644
--- a/erpnext/setup/doctype/territory/territory.py
+++ b/erpnext/setup/doctype/territory/territory.py
@@ -8,12 +8,15 @@ import frappe
from frappe.utils import flt
from frappe import _
-from frappe.utils.nestedset import NestedSet
+from frappe.utils.nestedset import NestedSet, get_root_of
class Territory(NestedSet):
nsm_parent_field = 'parent_territory'
def validate(self):
+ if not self.parent_territory:
+ self.parent_territory = get_root_of("Territory")
+
for d in self.get('targets') or []:
if not flt(d.target_qty) and not flt(d.target_amount):
frappe.throw(_("Either target qty or target amount is mandatory"))
diff --git a/erpnext/setup/setup_wizard/data/uom_conversion_data.json b/erpnext/setup/setup_wizard/data/uom_conversion_data.json
index 174ecd5903e..27a917d9db8 100644
--- a/erpnext/setup/setup_wizard/data/uom_conversion_data.json
+++ b/erpnext/setup/setup_wizard/data/uom_conversion_data.json
@@ -1571,5 +1571,19 @@
"to_uom": "Parts Per Million",
"abbr": "ppm",
"value": "10000"
+ },
+ {
+ "category": "Mass",
+ "from_uom": "Pound",
+ "to_uom": "Ounce",
+ "abbr": "oz",
+ "value": "16"
+ },
+ {
+ "category": "Mass",
+ "from_uom": "Gram",
+ "to_uom": "Ounce",
+ "abbr": "oz",
+ "value": "0.035274"
}
]
\ No newline at end of file
diff --git a/erpnext/setup/setup_wizard/operations/install_fixtures.py b/erpnext/setup/setup_wizard/operations/install_fixtures.py
index ebd7b509396..85e070a97f7 100644
--- a/erpnext/setup/setup_wizard/operations/install_fixtures.py
+++ b/erpnext/setup/setup_wizard/operations/install_fixtures.py
@@ -335,13 +335,14 @@ def add_uom_data():
"category_name": _(d.get("category"))
}).insert(ignore_permissions=True)
- uom_conversion = frappe.get_doc({
- "doctype": "UOM Conversion Factor",
- "category": _(d.get("category")),
- "from_uom": _(d.get("from_uom")),
- "to_uom": _(d.get("to_uom")),
- "value": d.get("value")
- }).insert(ignore_permissions=True)
+ if not frappe.db.exists("UOM Conversion Factor", {"from_uom": _(d.get("from_uom")), "to_uom": _(d.get("to_uom"))}):
+ uom_conversion = frappe.get_doc({
+ "doctype": "UOM Conversion Factor",
+ "category": _(d.get("category")),
+ "from_uom": _(d.get("from_uom")),
+ "to_uom": _(d.get("to_uom")),
+ "value": d.get("value")
+ }).insert(ignore_permissions=True)
def add_market_segments():
records = [
diff --git a/erpnext/setup/utils.py b/erpnext/setup/utils.py
index d1c206d8b1d..ffd5ab1e840 100644
--- a/erpnext/setup/utils.py
+++ b/erpnext/setup/utils.py
@@ -141,6 +141,6 @@ def insert_record(records):
raise
def welcome_email():
- site_name = get_default_company()
- title = _("Welcome to {0}".format(site_name))
+ site_name = get_default_company() or "ERPNext"
+ title = _("Welcome to {0}").format(site_name)
return title
diff --git a/erpnext/shopping_cart/cart.py b/erpnext/shopping_cart/cart.py
index 813d0dd196f..0fded2ec6f7 100644
--- a/erpnext/shopping_cart/cart.py
+++ b/erpnext/shopping_cart/cart.py
@@ -38,14 +38,14 @@ def get_cart_quotation(doc=None):
addresses = get_address_docs(party=party)
if not doc.customer_address and addresses:
- update_cart_address("customer_address", addresses[0].name)
+ update_cart_address("billing", addresses[0].name)
return {
"doc": decorate_quotation_doc(doc),
"shipping_addresses": [{"name": address.name, "display": address.display}
- for address in addresses],
+ for address in addresses if address.address_type == "Shipping"],
"billing_addresses": [{"name": address.name, "display": address.display}
- for address in addresses],
+ for address in addresses if address.address_type == "Billing"],
"shipping_rules": get_applicable_shipping_rules(party),
"cart_settings": frappe.get_cached_doc("Shopping Cart Settings")
}
@@ -64,6 +64,9 @@ def place_order():
# company used to create customer accounts
frappe.defaults.set_user_default("company", quotation.company)
+ if not (quotation.shipping_address_name or quotation.customer_address):
+ frappe.throw(_("Set Shipping Address or Billing Address"))
+
from erpnext.selling.doctype.quotation.quotation import _make_sales_order
sales_order = frappe.get_doc(_make_sales_order(quotation.name, ignore_permissions=True))
sales_order.payment_schedule = []
@@ -75,8 +78,10 @@ def place_order():
if is_stock_item:
item_stock = get_qty_in_stock(item.item_code, "website_warehouse")
+ if not cint(item_stock.in_stock):
+ throw(_("{1} Not in Stock").format(item.item_code))
if item.qty > item_stock.stock_qty[0][0]:
- throw(_("Only {0} in stock for item {1}").format(item_stock.stock_qty[0][0], item.item_code))
+ throw(_("Only {0} in Stock for item {1}").format(item_stock.stock_qty[0][0], item.item_code))
sales_order.flags.ignore_permissions = True
sales_order.insert()
@@ -194,21 +199,18 @@ def get_terms_and_conditions(terms_name):
return frappe.db.get_value('Terms and Conditions', terms_name, 'terms')
@frappe.whitelist()
-def update_cart_address(address_fieldname, address_name):
+def update_cart_address(address_type, address_name):
quotation = _get_cart_quotation()
address_display = get_address_display(frappe.get_doc("Address", address_name).as_dict())
- if address_fieldname == "shipping_address_name":
- quotation.shipping_address_name = address_name
- quotation.shipping_address = address_display
-
- if not quotation.customer_address:
- address_fieldname == "customer_address"
-
- if address_fieldname == "customer_address":
+ if address_type.lower() == "billing":
quotation.customer_address = address_name
quotation.address_display = address_display
-
+ quotation.shipping_address_name == quotation.shipping_address_name or address_name
+ elif address_type.lower() == "shipping":
+ quotation.shipping_address_name = address_name
+ quotation.shipping_address = address_display
+ quotation.customer_address == quotation.customer_address or address_name
apply_cart_settings(quotation=quotation)
@@ -319,7 +321,7 @@ def apply_cart_settings(party=None, quotation=None):
def set_price_list_and_rate(quotation, cart_settings):
"""set price list based on billing territory"""
- _set_price_list(quotation, cart_settings)
+ _set_price_list(cart_settings, quotation)
# reset values
quotation.price_list_currency = quotation.currency = \
@@ -334,23 +336,24 @@ def set_price_list_and_rate(quotation, cart_settings):
# set it in cookies for using in product page
frappe.local.cookie_manager.set_cookie("selling_price_list", quotation.selling_price_list)
-def _set_price_list(quotation, cart_settings):
+def _set_price_list(cart_settings, quotation=None):
"""Set price list based on customer or shopping cart default"""
from erpnext.accounts.party import get_default_price_list
-
- # check if customer price list exists
+ party_name = quotation.get("party_name") if quotation else get_party().get("name")
selling_price_list = None
- if quotation.party_name:
- selling_price_list = frappe.db.get_value('Customer', quotation.party_name, 'default_price_list')
- # else check for territory based price list
+ # check if default customer price list exists
+ if party_name:
+ selling_price_list = get_default_price_list(frappe.get_doc("Customer", party_name))
+
+ # check default price list in shopping cart
if not selling_price_list:
selling_price_list = cart_settings.price_list
- if not selling_price_list and quotation.party_name:
- selling_price_list = get_default_price_list(frappe.get_doc("Customer", quotation.party_name))
+ if quotation:
+ quotation.selling_price_list = selling_price_list
- quotation.selling_price_list = selling_price_list
+ return selling_price_list
def set_taxes(quotation, cart_settings):
"""set taxes based on billing territory"""
@@ -541,27 +544,31 @@ def show_terms(doc):
return doc.tc_name
@frappe.whitelist(allow_guest=True)
-def apply_coupon_code(applied_code,applied_referral_sales_partner):
+def apply_coupon_code(applied_code, applied_referral_sales_partner):
quotation = True
- if applied_code:
- coupon_list=frappe.get_all('Coupon Code', filters={"docstatus": ("<", "2"), 'coupon_code':applied_code }, fields=['name'])
- if coupon_list:
- coupon_name=coupon_list[0].name
- from erpnext.accounts.doctype.pricing_rule.utils import validate_coupon_code
- validate_coupon_code(coupon_name)
- quotation = _get_cart_quotation()
- quotation.coupon_code=coupon_name
+
+ if not applied_code:
+ frappe.throw(_("Please enter a coupon code"))
+
+ coupon_list = frappe.get_all('Coupon Code', filters={'coupon_code': applied_code})
+ if not coupon_list:
+ frappe.throw(_("Please enter a valid coupon code"))
+
+ coupon_name = coupon_list[0].name
+
+ from erpnext.accounts.doctype.pricing_rule.utils import validate_coupon_code
+ validate_coupon_code(coupon_name)
+ quotation = _get_cart_quotation()
+ quotation.coupon_code = coupon_name
+ quotation.flags.ignore_permissions = True
+ quotation.save()
+
+ if applied_referral_sales_partner:
+ sales_partner_list = frappe.get_all('Sales Partner', filters={'referral_code': applied_referral_sales_partner})
+ if sales_partner_list:
+ sales_partner_name = sales_partner_list[0].name
+ quotation.referral_sales_partner = sales_partner_name
quotation.flags.ignore_permissions = True
quotation.save()
- if applied_referral_sales_partner:
- sales_partner_list=frappe.get_all('Sales Partner', filters={'docstatus': 0, 'referral_code':applied_referral_sales_partner }, fields=['name'])
- if sales_partner_list:
- sales_partner_name=sales_partner_list[0].name
- quotation.referral_sales_partner=sales_partner_name
- quotation.flags.ignore_permissions = True
- quotation.save()
- else:
- frappe.throw(_("Please enter valid coupon code !!"))
- else:
- frappe.throw(_("Please enter coupon code !!"))
+
return quotation
diff --git a/erpnext/shopping_cart/doctype/shopping_cart_settings/shopping_cart_settings.js b/erpnext/shopping_cart/doctype/shopping_cart_settings/shopping_cart_settings.js
index e1510f53354..23814f2aabf 100644
--- a/erpnext/shopping_cart/doctype/shopping_cart_settings/shopping_cart_settings.js
+++ b/erpnext/shopping_cart/doctype/shopping_cart_settings/shopping_cart_settings.js
@@ -1,25 +1,32 @@
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
// License: GNU General Public License v3. See license.txt
-$.extend(cur_frm.cscript, {
- onload: function() {
- if(cur_frm.doc.__onload && cur_frm.doc.__onload.quotation_series) {
- cur_frm.fields_dict.quotation_series.df.options = cur_frm.doc.__onload.quotation_series;
- cur_frm.refresh_field("quotation_series");
+frappe.ui.form.on("Shopping Cart Settings", {
+ onload: function(frm) {
+ if(frm.doc.__onload && frm.doc.__onload.quotation_series) {
+ frm.fields_dict.quotation_series.df.options = frm.doc.__onload.quotation_series;
+ frm.refresh_field("quotation_series");
}
},
- refresh: function(){
- toggle_mandatory(cur_frm)
+ refresh: function(frm){
+ toggle_mandatory(frm)
},
- enable_checkout: function(){
- toggle_mandatory(cur_frm)
+ enable_checkout: function(frm){
+ toggle_mandatory(frm)
+ },
+ enabled: function(frm) {
+ if (frm.doc.enabled === 1) {
+ frm.set_value('enable_variants', 1);
+ }
+ let is_required = frm.doc.enabled ? 1 : 0;
+ frm.toggle_reqd(["company", "default_customer_group", "quotation_series"], is_required);
}
});
-function toggle_mandatory (cur_frm){
- cur_frm.toggle_reqd("payment_gateway_account", false);
- if(cur_frm.doc.enabled && cur_frm.doc.enable_checkout) {
- cur_frm.toggle_reqd("payment_gateway_account", true);
+function toggle_mandatory (frm){
+ frm.toggle_reqd("payment_gateway_account", false);
+ if(frm.doc.enabled && frm.doc.enable_checkout) {
+ frm.toggle_reqd("payment_gateway_account", true);
}
}
diff --git a/erpnext/shopping_cart/doctype/shopping_cart_settings/shopping_cart_settings.json b/erpnext/shopping_cart/doctype/shopping_cart_settings/shopping_cart_settings.json
index e828f54878b..e22af9a601d 100644
--- a/erpnext/shopping_cart/doctype/shopping_cart_settings/shopping_cart_settings.json
+++ b/erpnext/shopping_cart/doctype/shopping_cart_settings/shopping_cart_settings.json
@@ -1,750 +1,182 @@
{
- "allow_copy": 0,
- "allow_events_in_timeline": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "beta": 0,
- "creation": "2013-06-19 15:57:32",
- "custom": 0,
- "description": "Default settings for Shopping Cart",
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "System",
- "editable_grid": 0,
- "fields": [
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "enabled",
- "fieldtype": "Check",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Enable Shopping Cart",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "depends_on": "",
- "description": "",
- "fieldname": "display_settings",
- "fieldtype": "Section Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Display Settings",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "depends_on": "",
- "description": "",
- "fieldname": "show_attachments",
- "fieldtype": "Check",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Show Public Attachments",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "depends_on": "",
- "description": "",
- "fieldname": "show_price",
- "fieldtype": "Check",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Show Price",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "show_stock_availability",
- "fieldtype": "Check",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Show Stock Availability",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "show_configure_button",
- "fieldtype": "Check",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Show Configure Button",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "show_contact_us_button",
- "fieldtype": "Check",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Show Contact Us Button",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "depends_on": "show_stock_availability",
- "fieldname": "show_quantity_in_website",
- "fieldtype": "Check",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Show Stock Quantity",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "depends_on": "",
- "fetch_if_empty": 0,
- "fieldname": "show_apply_coupon_code_in_website",
- "fieldtype": "Check",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Show Apply Coupon Code",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_if_empty": 0,
- "fieldname": "allow_items_not_in_stock",
- "fieldtype": "Check",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Allow items not in stock to be added to cart",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "depends_on": "enabled",
- "fieldname": "section_break_2",
- "fieldtype": "Section Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "depends_on": "",
- "fieldname": "company",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Company",
- "length": 0,
- "no_copy": 0,
- "options": "Company",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 1,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "description": "Prices will not be shown if Price List is not set",
- "fieldname": "price_list",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Price List",
- "length": 0,
- "no_copy": 0,
- "options": "Price List",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "column_break_4",
- "fieldtype": "Column Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "description": "",
- "fieldname": "default_customer_group",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 1,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Default Customer Group",
- "length": 0,
- "no_copy": 0,
- "options": "Customer Group",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "quotation_series",
- "fieldtype": "Select",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Quotation Series",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 1,
- "collapsible_depends_on": "eval:doc.enable_checkout",
- "columns": 0,
- "depends_on": "enabled",
- "fieldname": "section_break_8",
- "fieldtype": "Section Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Checkout Settings",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "collapsible_depends_on": "",
- "columns": 0,
- "depends_on": "",
- "fieldname": "enable_checkout",
- "fieldtype": "Check",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Enable Checkout",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "default": "Orders",
- "description": "After payment completion redirect user to selected page.",
- "fieldname": "payment_success_url",
- "fieldtype": "Select",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Payment Success Url",
- "length": 0,
- "no_copy": 0,
- "options": "\nOrders\nInvoices\nMy Account",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "column_break_11",
- "fieldtype": "Column Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "payment_gateway_account",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Payment Gateway Account",
- "length": 0,
- "no_copy": 0,
- "options": "Payment Gateway Account",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- }
- ],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
- "icon": "fa fa-shopping-cart",
- "idx": 1,
- "image_view": 0,
- "in_create": 0,
- "is_submittable": 0,
- "issingle": 1,
- "istable": 0,
- "max_attachments": 0,
- "modified": "2019-10-14 13:54:24.575322",
- "modified_by": "Administrator",
- "module": "Shopping Cart",
- "name": "Shopping Cart Settings",
- "owner": "Administrator",
- "permissions": [
- {
- "amend": 0,
- "cancel": 0,
- "create": 1,
- "delete": 0,
- "email": 1,
- "export": 0,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "report": 0,
- "role": "Website Manager",
- "set_user_permissions": 0,
- "share": 1,
- "submit": 0,
- "write": 1
- }
- ],
- "quick_entry": 0,
- "read_only": 0,
- "read_only_onload": 0,
- "show_name_in_global_search": 0,
- "sort_order": "ASC",
- "track_changes": 0,
- "track_seen": 0,
- "track_views": 0
- }
\ No newline at end of file
+ "creation": "2013-06-19 15:57:32",
+ "description": "Default settings for Shopping Cart",
+ "doctype": "DocType",
+ "document_type": "System",
+ "engine": "InnoDB",
+ "field_order": [
+ "enabled",
+ "display_settings",
+ "show_attachments",
+ "show_price",
+ "show_stock_availability",
+ "enable_variants",
+ "show_contact_us_button",
+ "show_quantity_in_website",
+ "show_apply_coupon_code_in_website",
+ "allow_items_not_in_stock",
+ "section_break_2",
+ "company",
+ "price_list",
+ "column_break_4",
+ "default_customer_group",
+ "quotation_series",
+ "section_break_8",
+ "enable_checkout",
+ "payment_success_url",
+ "column_break_11",
+ "payment_gateway_account"
+ ],
+ "fields": [
+ {
+ "default": "0",
+ "fieldname": "enabled",
+ "fieldtype": "Check",
+ "in_list_view": 1,
+ "label": "Enable Shopping Cart"
+ },
+ {
+ "fieldname": "display_settings",
+ "fieldtype": "Section Break",
+ "label": "Display Settings"
+ },
+ {
+ "default": "0",
+ "fieldname": "show_attachments",
+ "fieldtype": "Check",
+ "label": "Show Public Attachments"
+ },
+ {
+ "default": "0",
+ "fieldname": "show_price",
+ "fieldtype": "Check",
+ "label": "Show Price"
+ },
+ {
+ "default": "0",
+ "fieldname": "show_stock_availability",
+ "fieldtype": "Check",
+ "label": "Show Stock Availability"
+ },
+ {
+ "default": "0",
+ "fieldname": "show_contact_us_button",
+ "fieldtype": "Check",
+ "label": "Show Contact Us Button"
+ },
+ {
+ "default": "0",
+ "depends_on": "show_stock_availability",
+ "fieldname": "show_quantity_in_website",
+ "fieldtype": "Check",
+ "label": "Show Stock Quantity"
+ },
+ {
+ "default": "0",
+ "fieldname": "show_apply_coupon_code_in_website",
+ "fieldtype": "Check",
+ "label": "Show Apply Coupon Code"
+ },
+ {
+ "default": "0",
+ "fieldname": "allow_items_not_in_stock",
+ "fieldtype": "Check",
+ "label": "Allow items not in stock to be added to cart"
+ },
+ {
+ "depends_on": "enabled",
+ "fieldname": "section_break_2",
+ "fieldtype": "Section Break"
+ },
+ {
+ "fieldname": "company",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Company",
+ "options": "Company",
+ "remember_last_selected_value": 1
+ },
+ {
+ "description": "Prices will not be shown if Price List is not set",
+ "fieldname": "price_list",
+ "fieldtype": "Link",
+ "label": "Price List",
+ "options": "Price List"
+ },
+ {
+ "fieldname": "column_break_4",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "default_customer_group",
+ "fieldtype": "Link",
+ "ignore_user_permissions": 1,
+ "label": "Default Customer Group",
+ "options": "Customer Group"
+ },
+ {
+ "fieldname": "quotation_series",
+ "fieldtype": "Select",
+ "label": "Quotation Series"
+ },
+ {
+ "collapsible": 1,
+ "collapsible_depends_on": "eval:doc.enable_checkout",
+ "depends_on": "enabled",
+ "fieldname": "section_break_8",
+ "fieldtype": "Section Break",
+ "label": "Checkout Settings"
+ },
+ {
+ "default": "0",
+ "fieldname": "enable_checkout",
+ "fieldtype": "Check",
+ "label": "Enable Checkout"
+ },
+ {
+ "default": "Orders",
+ "description": "After payment completion redirect user to selected page.",
+ "fieldname": "payment_success_url",
+ "fieldtype": "Select",
+ "label": "Payment Success Url",
+ "options": "\nOrders\nInvoices\nMy Account"
+ },
+ {
+ "fieldname": "column_break_11",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "payment_gateway_account",
+ "fieldtype": "Link",
+ "label": "Payment Gateway Account",
+ "options": "Payment Gateway Account"
+ },
+ {
+ "default": "0",
+ "fieldname": "enable_variants",
+ "fieldtype": "Check",
+ "label": "Enable Variants"
+ }
+ ],
+ "icon": "fa fa-shopping-cart",
+ "idx": 1,
+ "issingle": 1,
+ "modified": "2020-07-07 02:13:23.175604",
+ "modified_by": "Administrator",
+ "module": "Shopping Cart",
+ "name": "Shopping Cart Settings",
+ "owner": "Administrator",
+ "permissions": [
+ {
+ "create": 1,
+ "email": 1,
+ "print": 1,
+ "read": 1,
+ "role": "Website Manager",
+ "share": 1,
+ "write": 1
+ }
+ ],
+ "sort_field": "modified",
+ "sort_order": "ASC"
+}
\ No newline at end of file
diff --git a/erpnext/shopping_cart/doctype/shopping_cart_settings/shopping_cart_settings.py b/erpnext/shopping_cart/doctype/shopping_cart_settings/shopping_cart_settings.py
index 3098190383b..c069b90e986 100644
--- a/erpnext/shopping_cart/doctype/shopping_cart_settings/shopping_cart_settings.py
+++ b/erpnext/shopping_cart/doctype/shopping_cart_settings/shopping_cart_settings.py
@@ -80,6 +80,7 @@ def get_shopping_cart_settings():
return frappe.local.shopping_cart_settings
+@frappe.whitelist(allow_guest=True)
def is_cart_enabled():
return get_shopping_cart_settings().enabled
diff --git a/erpnext/shopping_cart/product_info.py b/erpnext/shopping_cart/product_info.py
index d69b5e3a215..29617a87485 100644
--- a/erpnext/shopping_cart/product_info.py
+++ b/erpnext/shopping_cart/product_info.py
@@ -4,24 +4,28 @@
from __future__ import unicode_literals
import frappe
-from erpnext.shopping_cart.cart import _get_cart_quotation
+from erpnext.shopping_cart.cart import _get_cart_quotation, _set_price_list
from erpnext.shopping_cart.doctype.shopping_cart_settings.shopping_cart_settings \
import get_shopping_cart_settings, show_quantity_in_website
-from erpnext.utilities.product import get_price, get_qty_in_stock
+from erpnext.utilities.product import get_price, get_qty_in_stock, get_non_stock_item_status
@frappe.whitelist(allow_guest=True)
-def get_product_info_for_website(item_code):
+def get_product_info_for_website(item_code, skip_quotation_creation=False):
"""get product price / stock info for website"""
cart_settings = get_shopping_cart_settings()
if not cart_settings.enabled:
return frappe._dict()
- cart_quotation = _get_cart_quotation()
+ cart_quotation = frappe._dict()
+ if not skip_quotation_creation:
+ cart_quotation = _get_cart_quotation()
+
+ selling_price_list = cart_quotation.get("selling_price_list") if cart_quotation else _set_price_list(cart_settings, None)
price = get_price(
item_code,
- cart_quotation.selling_price_list,
+ selling_price_list,
cart_settings.default_customer_group,
cart_settings.company
)
@@ -31,7 +35,7 @@ def get_product_info_for_website(item_code):
product_info = {
"price": price,
"stock_qty": stock_status.stock_qty,
- "in_stock": stock_status.in_stock if stock_status.is_stock_item else 1,
+ "in_stock": stock_status.in_stock if stock_status.is_stock_item else get_non_stock_item_status(item_code, "website_warehouse"),
"qty": 0,
"uom": frappe.db.get_value("Item", item_code, "stock_uom"),
"show_stock_qty": show_quantity_in_website(),
@@ -40,7 +44,7 @@ def get_product_info_for_website(item_code):
if product_info["price"]:
if frappe.session.user != "Guest":
- item = cart_quotation.get({"item_code": item_code})
+ item = cart_quotation.get({"item_code": item_code}) if cart_quotation else None
if item:
product_info["qty"] = item[0].qty
@@ -51,7 +55,7 @@ def get_product_info_for_website(item_code):
def set_product_info_for_website(item):
"""set product price uom for website"""
- product_info = get_product_info_for_website(item.item_code)
+ product_info = get_product_info_for_website(item.item_code, skip_quotation_creation=True).get("product_info")
if product_info:
item.update(product_info)
diff --git a/erpnext/startup/boot.py b/erpnext/startup/boot.py
index 4ca43a89b8f..2b80fb8dfa1 100644
--- a/erpnext/startup/boot.py
+++ b/erpnext/startup/boot.py
@@ -10,7 +10,6 @@ def boot_session(bootinfo):
"""boot session - send website info if guest"""
bootinfo.custom_css = frappe.db.get_value('Style Settings', None, 'custom_css') or ''
- bootinfo.website_settings = frappe.get_doc('Website Settings')
if frappe.session['user']!='Guest':
update_page_info(bootinfo)
diff --git a/erpnext/stock/__init__.py b/erpnext/stock/__init__.py
index a4d4cbd8ef8..8d64efe41dd 100644
--- a/erpnext/stock/__init__.py
+++ b/erpnext/stock/__init__.py
@@ -13,12 +13,16 @@ install_docs = [
]
def get_warehouse_account_map(company=None):
- if not frappe.flags.warehouse_account_map or frappe.flags.in_test:
+ company_warehouse_account_map = company and frappe.flags.setdefault('warehouse_account_map', {}).get(company)
+ warehouse_account_map = frappe.flags.warehouse_account_map
+
+ if not warehouse_account_map or not company_warehouse_account_map or frappe.flags.in_test:
warehouse_account = frappe._dict()
filters = {}
if company:
filters['company'] = company
+ frappe.flags.setdefault('warehouse_account_map', {}).setdefault(company, {})
for d in frappe.get_all('Warehouse',
fields = ["name", "account", "parent_warehouse", "company", "is_group"],
@@ -30,10 +34,12 @@ def get_warehouse_account_map(company=None):
if d.account:
d.account_currency = frappe.db.get_value('Account', d.account, 'account_currency', cache=True)
warehouse_account.setdefault(d.name, d)
-
- frappe.flags.warehouse_account_map = warehouse_account
-
- return frappe.flags.warehouse_account_map
+ if company:
+ frappe.flags.warehouse_account_map[company] = warehouse_account
+ else:
+ frappe.flags.warehouse_account_map = warehouse_account
+
+ return frappe.flags.warehouse_account_map.get(company) or frappe.flags.warehouse_account_map
def get_warehouse_account(warehouse, warehouse_account=None):
account = warehouse.account
diff --git a/erpnext/stock/doctype/batch/batch.py b/erpnext/stock/doctype/batch/batch.py
index 8ae978eaf05..c8424f13e12 100644
--- a/erpnext/stock/doctype/batch/batch.py
+++ b/erpnext/stock/doctype/batch/batch.py
@@ -7,7 +7,7 @@ import frappe
from frappe import _
from frappe.model.document import Document
from frappe.model.naming import make_autoname, revert_series_if_last
-from frappe.utils import flt, cint
+from frappe.utils import flt, cint, get_link_to_form
from frappe.utils.jinja import render_template
from frappe.utils.data import add_days
from six import string_types
@@ -122,8 +122,11 @@ class Batch(Document):
self.expiry_date = add_days(self.manufacturing_date, shelf_life_in_days)
if has_expiry_date and not self.expiry_date:
- frappe.msgprint(_('Expiry date is mandatory for selected item.'))
- frappe.throw(_("Set item's shelf life in days, to set expiry based on manufacturing date plus shelf-life."))
+ frappe.throw(msg=_("Please set {0} for Batched Item {1}, which is used to set {2} on Submit.") \
+ .format(frappe.bold("Shelf Life in Days"),
+ get_link_to_form("Item", self.item),
+ frappe.bold("Batch Expiry Date")),
+ title=_("Expiry Date Mandatory"))
def get_name_from_naming_series(self):
"""
@@ -140,7 +143,7 @@ class Batch(Document):
@frappe.whitelist()
-def get_batch_qty(batch_no=None, warehouse=None, item_code=None):
+def get_batch_qty(batch_no=None, warehouse=None, item_code=None, posting_date=None, posting_time=None):
"""Returns batch actual qty if warehouse is passed,
or returns dict of qty by warehouse if warehouse is None
@@ -152,9 +155,14 @@ def get_batch_qty(batch_no=None, warehouse=None, item_code=None):
out = 0
if batch_no and warehouse:
+ cond = ""
+ if posting_date and posting_time:
+ cond = " and timestamp(posting_date, posting_time) <= timestamp('{0}', '{1}')".format(posting_date,
+ posting_time)
+
out = float(frappe.db.sql("""select sum(actual_qty)
from `tabStock Ledger Entry`
- where warehouse=%s and batch_no=%s""",
+ where warehouse=%s and batch_no=%s {0}""".format(cond),
(warehouse, batch_no))[0][0] or 0)
if batch_no and not warehouse:
@@ -261,16 +269,20 @@ def get_batch_no(item_code, warehouse, qty=1, throw=False, serial_no=None):
def get_batches(item_code, warehouse, qty=1, throw=False, serial_no=None):
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
cond = ''
- if serial_no:
+ if serial_no and frappe.get_cached_value('Item', item_code, 'has_batch_no'):
+ serial_nos = get_serial_nos(serial_no)
batch = frappe.get_all("Serial No",
fields = ["distinct batch_no"],
filters= {
"item_code": item_code,
"warehouse": warehouse,
- "name": ("in", get_serial_nos(serial_no))
+ "name": ("in", serial_nos)
}
)
+ if not batch:
+ validate_serial_no_with_batch(serial_nos, item_code)
+
if batch and len(batch) > 1:
return []
@@ -285,4 +297,15 @@ def get_batches(item_code, warehouse, qty=1, throw=False, serial_no=None):
and (`tabBatch`.expiry_date >= CURDATE() or `tabBatch`.expiry_date IS NULL) {0}
group by batch_id
order by `tabBatch`.expiry_date ASC, `tabBatch`.creation ASC
- """.format(cond), (item_code, warehouse), as_dict=True)
\ No newline at end of file
+ """.format(cond), (item_code, warehouse), as_dict=True)
+
+def validate_serial_no_with_batch(serial_nos, item_code):
+ if frappe.get_cached_value("Serial No", serial_nos[0], "item_code") != item_code:
+ frappe.throw(_("The serial no {0} does not belong to item {1}")
+ .format(get_link_to_form("Serial No", serial_nos[0]), get_link_to_form("Item", item_code)))
+
+ serial_no_link = ','.join([get_link_to_form("Serial No", sn) for sn in serial_nos])
+
+ message = "Serial Nos" if len(serial_nos) > 1 else "Serial No"
+ frappe.throw(_("There is no batch found against the {0}: {1}")
+ .format(message, serial_no_link))
\ No newline at end of file
diff --git a/erpnext/stock/doctype/batch/test_batch.py b/erpnext/stock/doctype/batch/test_batch.py
index 32445a618d1..0cc0fd487d3 100644
--- a/erpnext/stock/doctype/batch/test_batch.py
+++ b/erpnext/stock/doctype/batch/test_batch.py
@@ -241,3 +241,18 @@ class TestBatch(unittest.TestCase):
batch.insert()
return batch
+
+def make_new_batch(**args):
+ args = frappe._dict(args)
+
+ try:
+ batch = frappe.get_doc({
+ "doctype": "Batch",
+ "batch_id": args.batch_id,
+ "item": args.item_code,
+ }).insert()
+
+ except frappe.DuplicateEntryError:
+ batch = frappe.get_doc("Batch", args.batch_id)
+
+ return batch
\ No newline at end of file
diff --git a/erpnext/stock/doctype/bin/bin.py b/erpnext/stock/doctype/bin/bin.py
index 97a84726da8..73b36e3d852 100644
--- a/erpnext/stock/doctype/bin/bin.py
+++ b/erpnext/stock/doctype/bin/bin.py
@@ -69,15 +69,21 @@ class Bin(Document):
'''Update qty reserved for production from Production Item tables
in open work orders'''
self.reserved_qty_for_production = frappe.db.sql('''
- select sum(item.required_qty - item.transferred_qty)
- from `tabWork Order` pro, `tabWork Order Item` item
- where
+ SELECT
+ CASE WHEN ifnull(skip_transfer, 0) = 0 THEN
+ SUM(item.required_qty - item.transferred_qty)
+ ELSE
+ SUM(item.required_qty - item.consumed_qty)
+ END
+ FROM `tabWork Order` pro, `tabWork Order Item` item
+ WHERE
item.item_code = %s
and item.parent = pro.name
and pro.docstatus = 1
and item.source_warehouse = %s
and pro.status not in ("Stopped", "Completed")
- and item.required_qty > item.transferred_qty''', (self.item_code, self.warehouse))[0][0]
+ and (item.required_qty > item.transferred_qty or item.required_qty > item.consumed_qty)
+ ''', (self.item_code, self.warehouse))[0][0]
self.set_projected_qty()
diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.js b/erpnext/stock/doctype/delivery_note/delivery_note.js
index 2ee68723789..6be20a2e71d 100644
--- a/erpnext/stock/doctype/delivery_note/delivery_note.js
+++ b/erpnext/stock/doctype/delivery_note/delivery_note.js
@@ -121,12 +121,18 @@ erpnext.stock.DeliveryNoteController = erpnext.selling.SellingController.extend(
if (this.frm.doc.docstatus===0) {
this.frm.add_custom_button(__('Sales Order'),
function() {
+ if (!me.frm.doc.customer) {
+ frappe.throw({
+ title: __("Mandatory"),
+ message: __("Please Select a Customer")
+ });
+ }
erpnext.utils.map_current_doc({
method: "erpnext.selling.doctype.sales_order.sales_order.make_delivery_note",
source_doctype: "Sales Order",
target: me.frm,
setters: {
- customer: me.frm.doc.customer || undefined,
+ customer: me.frm.doc.customer,
},
get_query_filters: {
docstatus: 1,
@@ -267,6 +273,14 @@ erpnext.stock.DeliveryNoteController = erpnext.selling.SellingController.extend(
frappe.ui.form.is_saving = false;
}
})
+ },
+
+ to_warehouse: function() {
+ let packed_items_table = this.frm.doc["packed_items"];
+ this.autofill_warehouse(this.frm.doc["items"], "target_warehouse", this.frm.doc.to_warehouse);
+ if (packed_items_table && packed_items_table.length) {
+ this.autofill_warehouse(packed_items_table, "target_warehouse", this.frm.doc.to_warehouse);
+ }
}
});
diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.json b/erpnext/stock/doctype/delivery_note/delivery_note.json
index 86200ba26b6..8a2fa397731 100644
--- a/erpnext/stock/doctype/delivery_note/delivery_note.json
+++ b/erpnext/stock/doctype/delivery_note/delivery_note.json
@@ -24,10 +24,10 @@
"return_against",
"customer_po_details",
"po_no",
- "section_break_18",
- "pick_list",
"column_break_17",
"po_date",
+ "section_break_18",
+ "pick_list",
"contact_info",
"shipping_address_name",
"shipping_address",
@@ -66,9 +66,9 @@
"base_total",
"base_net_total",
"column_break_33",
+ "total_net_weight",
"total",
"net_total",
- "total_net_weight",
"taxes_section",
"tax_category",
"column_break_39",
@@ -294,7 +294,6 @@
},
{
"collapsible": 1,
- "collapsible_depends_on": "po_no",
"fieldname": "customer_po_details",
"fieldtype": "Section Break",
"label": "Customer PO Details"
@@ -302,7 +301,7 @@
{
"allow_on_submit": 1,
"fieldname": "po_no",
- "fieldtype": "Data",
+ "fieldtype": "Small Text",
"label": "Customer's Purchase Order No",
"no_copy": 1,
"oldfieldname": "po_no",
@@ -316,7 +315,6 @@
"fieldtype": "Column Break"
},
{
- "depends_on": "eval:doc.po_no",
"fieldname": "po_date",
"fieldtype": "Date",
"label": "Customer's Purchase Order Date",
@@ -324,7 +322,6 @@
"oldfieldtype": "Data",
"print_hide": 1,
"print_width": "100px",
- "read_only": 1,
"width": "100px"
},
{
@@ -1240,7 +1237,7 @@
"idx": 146,
"is_submittable": 1,
"links": [],
- "modified": "2019-12-30 19:17:13.122644",
+ "modified": "2020-05-19 17:03:45.880106",
"modified_by": "Administrator",
"module": "Stock",
"name": "Delivery Note",
diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.py b/erpnext/stock/doctype/delivery_note/delivery_note.py
index 39aad2e0071..f4334c4980a 100644
--- a/erpnext/stock/doctype/delivery_note/delivery_note.py
+++ b/erpnext/stock/doctype/delivery_note/delivery_note.py
@@ -111,7 +111,6 @@ class DeliveryNote(SellingController):
self.so_required()
self.validate_proj_cust()
self.check_sales_order_on_hold_or_close("against_sales_order")
- self.validate_for_items()
self.validate_warehouse()
self.validate_uom_is_integer("stock_uom", "stock_qty")
self.validate_uom_is_integer("uom", "qty")
@@ -165,12 +164,6 @@ class DeliveryNote(SellingController):
if not res:
frappe.throw(_("Customer {0} does not belong to project {1}").format(self.customer, self.project))
- def validate_for_items(self):
- for d in self.get('items'):
- #Customer Provided parts will have zero valuation rate
- if frappe.db.get_value('Item', d.item_code, 'is_customer_provided_item'):
- d.allow_zero_valuation_rate = 1
-
def validate_warehouse(self):
super(DeliveryNote, self).validate_warehouse()
@@ -393,13 +386,12 @@ def get_invoiced_qty_map(delivery_note):
def get_returned_qty_map(delivery_note):
"""returns a map: {so_detail: returned_qty}"""
- returned_qty_map = frappe._dict(frappe.db.sql("""select dn_item.item_code, sum(abs(dn_item.qty)) as qty
+ returned_qty_map = frappe._dict(frappe.db.sql("""select dn_item.dn_detail, abs(dn_item.qty) as qty
from `tabDelivery Note Item` dn_item, `tabDelivery Note` dn
where dn.name = dn_item.parent
and dn.docstatus = 1
and dn.is_return = 1
and dn.return_against = %s
- group by dn_item.item_code
""", delivery_note))
return returned_qty_map
@@ -413,13 +405,12 @@ def make_sales_invoice(source_name, target_doc=None):
invoiced_qty_map = get_invoiced_qty_map(source_name)
def set_missing_values(source, target):
- target.is_pos = 0
target.ignore_pricing_rule = 1
target.run_method("set_missing_values")
target.run_method("set_po_nos")
if len(target.get("items")) == 0:
- frappe.throw(_("All these items have already been invoiced"))
+ frappe.throw(_("All these items have already been Invoiced/Returned"))
target.run_method("calculate_taxes_and_totals")
@@ -444,9 +435,9 @@ def make_sales_invoice(source_name, target_doc=None):
pending_qty = item_row.qty - invoiced_qty_map.get(item_row.name, 0)
returned_qty = 0
- if returned_qty_map.get(item_row.item_code, 0) > 0:
- returned_qty = flt(returned_qty_map.get(item_row.item_code, 0))
- returned_qty_map[item_row.item_code] -= pending_qty
+ if returned_qty_map.get(item_row.name, 0) > 0:
+ returned_qty = flt(returned_qty_map.get(item_row.name, 0))
+ returned_qty_map[item_row.name] -= pending_qty
if returned_qty:
if returned_qty >= pending_qty:
diff --git a/erpnext/stock/doctype/delivery_note/test_delivery_note.py b/erpnext/stock/doctype/delivery_note/test_delivery_note.py
index dc92c5c9ff2..9d92d43ec2f 100644
--- a/erpnext/stock/doctype/delivery_note/test_delivery_note.py
+++ b/erpnext/stock/doctype/delivery_note/test_delivery_note.py
@@ -21,6 +21,7 @@ from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation \
from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order, create_dn_against_so
from erpnext.accounts.doctype.account.test_account import get_inventory_account, create_account
from erpnext.stock.doctype.warehouse.test_warehouse import get_warehouse
+from erpnext.stock.doctype.item.test_item import create_item
class TestDeliveryNote(unittest.TestCase):
def setUp(self):
@@ -87,7 +88,7 @@ class TestDeliveryNote(unittest.TestCase):
# check stock in hand balance
bal = get_balance_on(stock_in_hand_account)
- self.assertEqual(bal, prev_bal - stock_value_difference)
+ self.assertEqual(flt(bal, 2), flt(prev_bal - stock_value_difference, 2))
# back dated incoming entry
make_stock_entry(posting_date=add_days(nowdate(), -2), target="Stores - TCP1",
@@ -547,11 +548,8 @@ class TestDeliveryNote(unittest.TestCase):
dt = make_delivery_trip(dn.name)
self.assertEqual(dn.name, dt.delivery_stops[0].delivery_note)
- def test_delivery_note_for_enable_allow_cost_center_in_entry_of_bs_account(self):
+ def test_delivery_note_with_cost_center(self):
from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center
- accounts_settings = frappe.get_doc('Accounts Settings', 'Accounts Settings')
- accounts_settings.allow_cost_center_in_entry_of_bs_account = 1
- accounts_settings.save()
cost_center = "_Test Cost Center for BS Account - TCP1"
create_cost_center(cost_center_name="_Test Cost Center for BS Account", company="_Test Company with perpetual inventory")
@@ -577,13 +575,8 @@ class TestDeliveryNote(unittest.TestCase):
}
for i, gle in enumerate(gl_entries):
self.assertEqual(expected_values[gle.account]["cost_center"], gle.cost_center)
- accounts_settings.allow_cost_center_in_entry_of_bs_account = 0
- accounts_settings.save()
- def test_delivery_note_for_disable_allow_cost_center_in_entry_of_bs_account(self):
- accounts_settings = frappe.get_doc('Accounts Settings', 'Accounts Settings')
- accounts_settings.allow_cost_center_in_entry_of_bs_account = 0
- accounts_settings.save()
+ def test_delivery_note_cost_center_with_balance_sheet_account(self):
cost_center = "Main - TCP1"
company = frappe.db.get_value('Warehouse', 'Stores - TCP1', 'company')
@@ -593,7 +586,11 @@ class TestDeliveryNote(unittest.TestCase):
make_stock_entry(target="Stores - TCP1", qty=5, basic_rate=100)
stock_in_hand_account = get_inventory_account('_Test Company with perpetual inventory')
- dn = create_delivery_note(company='_Test Company with perpetual inventory', warehouse='Stores - TCP1', cost_center = 'Main - TCP1', expense_account = "Cost of Goods Sold - TCP1")
+ dn = create_delivery_note(company='_Test Company with perpetual inventory', warehouse='Stores - TCP1', cost_center = 'Main - TCP1', expense_account = "Cost of Goods Sold - TCP1",
+ do_not_submit=1)
+
+ dn.get('items')[0].cost_center = None
+ dn.submit()
gl_entries = get_gl_entries("Delivery Note", dn.name)
@@ -603,7 +600,7 @@ class TestDeliveryNote(unittest.TestCase):
"cost_center": cost_center
},
stock_in_hand_account: {
- "cost_center": None
+ "cost_center": cost_center
}
}
for i, gle in enumerate(gl_entries):
@@ -622,6 +619,7 @@ class TestDeliveryNote(unittest.TestCase):
dn1 = create_delivery_note(is_return=1, return_against=dn.name, qty=-1, do_not_submit=True)
dn1.items[0].against_sales_order = so.name
dn1.items[0].so_detail = so.items[0].name
+ dn1.items[0].dn_detail = dn.items[0].name
dn1.submit()
si = make_sales_invoice(dn.name)
@@ -648,7 +646,9 @@ class TestDeliveryNote(unittest.TestCase):
si1.save()
si1.submit()
- create_delivery_note(is_return=1, return_against=dn.name, qty=-2)
+ dn1 = create_delivery_note(is_return=1, return_against=dn.name, qty=-2, do_not_submit=True)
+ dn1.items[0].dn_detail = dn.items[0].name
+ dn1.submit()
si2 = make_sales_invoice(dn.name)
self.assertEquals(si2.items[0].qty, 2)
@@ -671,7 +671,7 @@ def create_delivery_note(**args):
"item_code": args.item or args.item_code or "_Test Item",
"warehouse": args.warehouse or "_Test Warehouse - _TC",
"qty": args.qty or 1,
- "rate": args.rate or 100,
+ "rate": args.rate if args.get("rate") is not None else 100,
"conversion_factor": 1.0,
"allow_zero_valuation_rate": args.allow_zero_valuation_rate or 1,
"expense_account": args.expense_account or "Cost of Goods Sold - _TC",
diff --git a/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json b/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json
index af06daeabd3..542d198c946 100644
--- a/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json
+++ b/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json
@@ -66,6 +66,7 @@
"so_detail",
"against_sales_invoice",
"si_detail",
+ "dn_detail",
"section_break_40",
"batch_no",
"serial_no",
@@ -80,6 +81,9 @@
"accounting_dimensions_section",
"cost_center",
"dimension_col_break",
+ "project",
+ "reason_for_return_section_break",
+ "reason_for_return",
"section_break_72",
"page_break"
],
@@ -180,6 +184,7 @@
"oldfieldname": "stock_uom",
"oldfieldtype": "Data",
"options": "UOM",
+ "print_hide": 1,
"print_width": "50px",
"read_only": 1,
"reqd": 1,
@@ -195,7 +200,6 @@
"in_list_view": 1,
"label": "UOM",
"options": "UOM",
- "print_hide": 1,
"reqd": 1
},
{
@@ -698,11 +702,41 @@
{
"fieldname": "dimension_col_break",
"fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "project",
+ "fieldtype": "Link",
+ "label": "Project",
+ "options": "Project"
+ },
+ {
+ "depends_on": "eval:parent.is_return==1",
+ "fieldname": "reason_for_return",
+ "fieldtype": "Small Text",
+ "label": "Reason for Return",
+ "no_copy": 1
+ },
+ {
+ "default": "0",
+ "depends_on": "eval:parent.is_return==1",
+ "fieldname": "reason_for_return_section_break",
+ "fieldtype": "Section Break",
+ "label": "Reason For Return"
+ },
+ {
+ "fieldname": "dn_detail",
+ "fieldtype": "Data",
+ "hidden": 1,
+ "label": "Against Delivery Note Item",
+ "no_copy": 1,
+ "print_hide": 1,
+ "read_only": 1
}
],
"idx": 1,
"istable": 1,
- "modified": "2019-05-25 22:08:27.452734",
+ "links": [],
+ "modified": "2020-08-20 11:18:33.131672",
"modified_by": "Administrator",
"module": "Stock",
"name": "Delivery Note Item",
@@ -710,4 +744,4 @@
"permissions": [],
"sort_field": "modified",
"sort_order": "DESC"
-}
\ No newline at end of file
+}
diff --git a/erpnext/stock/doctype/delivery_trip/delivery_trip.json b/erpnext/stock/doctype/delivery_trip/delivery_trip.json
index 0a526243bc4..e78bf6b5049 100644
--- a/erpnext/stock/doctype/delivery_trip/delivery_trip.json
+++ b/erpnext/stock/doctype/delivery_trip/delivery_trip.json
@@ -163,6 +163,7 @@
},
{
"fetch_from": "driver.address",
+ "fetch_if_empty": 1,
"fieldname": "driver_address",
"fieldtype": "Link",
"label": "Driver Address",
@@ -170,7 +171,8 @@
}
],
"is_submittable": 1,
- "modified": "2019-09-27 15:43:01.975139",
+ "links": [],
+ "modified": "2020-01-26 22:37:14.824021",
"modified_by": "Administrator",
"module": "Stock",
"name": "Delivery Trip",
diff --git a/erpnext/stock/doctype/delivery_trip/delivery_trip.py b/erpnext/stock/doctype/delivery_trip/delivery_trip.py
index 77d322ed28d..209b1ef1468 100644
--- a/erpnext/stock/doctype/delivery_trip/delivery_trip.py
+++ b/erpnext/stock/doctype/delivery_trip/delivery_trip.py
@@ -238,7 +238,7 @@ class DeliveryTrip(Document):
try:
directions = maps_client.directions(**directions_data)
except Exception as e:
- frappe.throw(_(e))
+ frappe.throw(_(str(e)))
return directions[0] if directions else False
diff --git a/erpnext/stock/doctype/item/item.js b/erpnext/stock/doctype/item/item.js
index c10e62973bb..34476273c28 100644
--- a/erpnext/stock/doctype/item/item.js
+++ b/erpnext/stock/doctype/item/item.js
@@ -117,7 +117,7 @@ frappe.ui.form.on("Item", {
const stock_exists = (frm.doc.__onload
&& frm.doc.__onload.stock_exists) ? 1 : 0;
- ['is_stock_item', 'has_serial_no', 'has_batch_no'].forEach((fieldname) => {
+ ['is_stock_item', 'has_serial_no', 'has_batch_no', 'has_variants'].forEach((fieldname) => {
frm.set_df_property(fieldname, 'read_only', stock_exists);
});
diff --git a/erpnext/stock/doctype/item/item.json b/erpnext/stock/doctype/item/item.json
index 3503e7cc1c5..11be6588957 100644
--- a/erpnext/stock/doctype/item/item.json
+++ b/erpnext/stock/doctype/item/item.json
@@ -1,5 +1,4 @@
{
- "actions": [],
"allow_guest_to_view": 1,
"allow_import": 1,
"allow_rename": 1,
@@ -114,6 +113,8 @@
"is_sub_contracted_item",
"column_break_74",
"customer_code",
+ "default_item_manufacturer",
+ "default_manufacturer_part_no",
"website_section",
"show_in_website",
"show_variant_in_website",
@@ -343,7 +344,8 @@
{
"fieldname": "shelf_life_in_days",
"fieldtype": "Int",
- "label": "Shelf Life In Days"
+ "label": "Shelf Life In Days",
+ "mandatory_depends_on": "eval:doc.has_batch_no && doc.has_expiry_date"
},
{
"default": "2099-12-31",
@@ -470,6 +472,7 @@
},
{
"default": "0",
+ "depends_on": "has_batch_no",
"fieldname": "retain_sample",
"fieldtype": "Check",
"label": "Retain Sample"
@@ -496,7 +499,7 @@
"oldfieldtype": "Select"
},
{
- "depends_on": "eval:doc.is_stock_item || doc.is_fixed_asset",
+ "depends_on": "has_serial_no",
"description": "Example: ABCD.#####\nIf series is set and Serial No is not mentioned in transactions, then automatic serial number will be created based on this series. If you always want to explicitly mention Serial Nos for this item. leave this blank.",
"fieldname": "serial_no_series",
"fieldtype": "Data",
@@ -984,6 +987,7 @@
"read_only": 1
},
{
+ "collapsible": 1,
"depends_on": "eval:(!doc.is_item_from_hub)",
"fieldname": "hub_publishing_sb",
"fieldtype": "Section Break",
@@ -1037,15 +1041,26 @@
"fieldname": "auto_create_assets",
"fieldtype": "Check",
"label": "Auto Create Assets on Purchase"
+ },
+ {
+ "fieldname": "default_item_manufacturer",
+ "fieldtype": "Data",
+ "label": "Default Item Manufacturer",
+ "read_only": 1
+ },
+ {
+ "fieldname": "default_manufacturer_part_no",
+ "fieldtype": "Data",
+ "label": "Default Manufacturer Part No",
+ "read_only": 1
}
],
"has_web_view": 1,
"icon": "fa fa-tag",
"idx": 2,
"image_field": "image",
- "links": [],
"max_attachments": 1,
- "modified": "2020-01-02 19:13:59.295963",
+ "modified": "2020-08-06 17:03:26.594319",
"modified_by": "Administrator",
"module": "Stock",
"name": "Item",
diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py
index d2e04914017..deace33f343 100644
--- a/erpnext/stock/doctype/item/item.py
+++ b/erpnext/stock/doctype/item/item.py
@@ -13,7 +13,7 @@ from erpnext.controllers.item_variant import (ItemVariantExistsError,
from erpnext.setup.doctype.item_group.item_group import (get_parent_item_groups, invalidate_cache_for)
from frappe import _, msgprint
from frappe.utils import (cint, cstr, flt, formatdate, get_timestamp, getdate,
- now_datetime, random_string, strip)
+ now_datetime, random_string, strip, get_link_to_form)
from frappe.utils.html_utils import clean_html
from frappe.website.doctype.website_slideshow.website_slideshow import \
get_slideshow
@@ -101,6 +101,7 @@ class Item(WebsiteGenerator):
self.add_default_uom_in_conversion_factor_table()
self.validate_conversion_factor()
self.validate_item_type()
+ self.validate_naming_series()
self.check_for_active_boms()
self.fill_customer_code()
self.check_item_tax()
@@ -183,12 +184,17 @@ class Item(WebsiteGenerator):
# default warehouse, or Stores
for default in self.item_defaults or [frappe._dict({'company': frappe.defaults.get_defaults().company})]:
default_warehouse = (default.default_warehouse
- or frappe.db.get_single_value('Stock Settings', 'default_warehouse')
- or frappe.db.get_value('Warehouse', {'warehouse_name': _('Stores')}))
+ or frappe.db.get_single_value('Stock Settings', 'default_warehouse'))
+ if default_warehouse:
+ warehouse_company = frappe.db.get_value("Warehouse", default_warehouse, "company")
+
+ if not default_warehouse or warehouse_company != default.company:
+ default_warehouse = frappe.db.get_value('Warehouse',
+ {'warehouse_name': _('Stores'), 'company': default.company})
if default_warehouse:
stock_entry = make_stock_entry(item_code=self.name, target=default_warehouse, qty=self.opening_stock,
- rate=self.valuation_rate, company=default.company)
+ rate=self.valuation_rate, company=default.company)
stock_entry.add_comment("Comment", _("Opening Stock"))
@@ -461,7 +467,7 @@ class Item(WebsiteGenerator):
def set_shopping_cart_data(self, context):
from erpnext.shopping_cart.product_info import get_product_info_for_website
- context.shopping_cart = get_product_info_for_website(self.name)
+ context.shopping_cart = get_product_info_for_website(self.name, skip_quotation_creation=True)
def add_default_uom_in_conversion_factor_table(self):
uom_conv_list = [d.uom for d in self.get("uoms")]
@@ -517,6 +523,13 @@ class Item(WebsiteGenerator):
if self.has_serial_no == 0 and self.serial_no_series:
self.serial_no_series = None
+ def validate_naming_series(self):
+ for field in ["serial_no_series", "batch_number_series"]:
+ series = self.get(field)
+ if series and "#" in series and "." not in series:
+ frappe.throw(_("Invalid naming series (. missing) for {0}")
+ .format(frappe.bold(self.meta.get_field(field).label)))
+
def check_for_active_boms(self):
if self.default_bom:
bom_item = frappe.db.get_value("BOM", self.default_bom, "item")
@@ -559,6 +572,13 @@ class Item(WebsiteGenerator):
frappe.throw(_("Barcode {0} is not a valid {1} code").format(
item_barcode.barcode, item_barcode.barcode_type), InvalidBarcode)
+ if item_barcode.barcode != item_barcode.name:
+ # if barcode is getting updated , the row name has to reset.
+ # Delete previous old row doc and re-enter row as if new to reset name in db.
+ item_barcode.set("__islocal", True)
+ item_barcode.name = None
+ frappe.delete_doc("Item Barcode", item_barcode.name)
+
def validate_warehouse_for_reorder(self):
'''Validate Reorder level table for duplicate and conditional mandatory'''
warehouse = []
@@ -614,6 +634,9 @@ class Item(WebsiteGenerator):
+ ": \n" + ", ".join([self.meta.get_label(fld) for fld in field_list]))
def after_rename(self, old_name, new_name, merge):
+ if merge:
+ self.validate_duplicate_item_in_stock_reconciliation(old_name, new_name)
+
if self.route:
invalidate_cache_for_item(self)
clear_cache(self.route)
@@ -636,6 +659,27 @@ class Item(WebsiteGenerator):
frappe.db.set_value(dt, d.name, "item_wise_tax_detail",
json.dumps(item_wise_tax_detail), update_modified=False)
+ def validate_duplicate_item_in_stock_reconciliation(self, old_name, new_name):
+ records = frappe.db.sql(""" SELECT parent, COUNT(*) as records
+ FROM `tabStock Reconciliation Item`
+ WHERE item_code = %s and docstatus = 1
+ GROUP By item_code, warehouse, parent
+ HAVING records > 1
+ """, new_name, as_dict=1)
+
+ if not records: return
+ document = _("Stock Reconciliation") if len(records) == 1 else _("Stock Reconciliations")
+
+ msg = _("The items {0} and {1} are present in the following {2} :
"
+ .format(frappe.bold(old_name), frappe.bold(new_name), document))
+
+ msg += ', '.join([get_link_to_form("Stock Reconciliation", d.parent) for d in records]) + "
"
+
+ msg += _("Note: To merge the items, create a separate Stock Reconciliation for the old item {0}"
+ .format(frappe.bold(old_name)))
+
+ frappe.throw(_(msg), title=_("Merge not allowed"))
+
def set_last_purchase_rate(self, new_name):
last_purchase_rate = get_last_purchase_details(new_name).get("base_net_rate", 0)
frappe.db.set_value("Item", new_name, "last_purchase_rate", last_purchase_rate)
@@ -736,14 +780,12 @@ class Item(WebsiteGenerator):
defaults = frappe.defaults.get_defaults() or {}
# To check default warehouse is belong to the default company
- if defaults.get("default_warehouse") and frappe.db.exists("Warehouse",
+ if defaults.get("default_warehouse") and defaults.company and frappe.db.exists("Warehouse",
{'name': defaults.default_warehouse, 'company': defaults.company}):
- warehouse = defaults.default_warehouse
-
- self.append("item_defaults", {
- "company": defaults.get("company"),
- "default_warehouse": warehouse
- })
+ self.append("item_defaults", {
+ "company": defaults.get("company"),
+ "default_warehouse": defaults.default_warehouse
+ })
def update_variants(self):
if self.flags.dont_update_variants or \
@@ -865,7 +907,12 @@ class Item(WebsiteGenerator):
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 \
+ if doctype in ("Purchase Invoice Item", "Sales Invoice Item",):
+ # If Invoice has Stock impact, only then consider it.
+ if self.stock_ledger_created():
+ return True
+
+ elif 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
@@ -931,6 +978,7 @@ def _msgprint(msg, verbose):
def get_last_purchase_details(item_code, doc_name=None, conversion_rate=1.0):
"""returns last purchase details in stock uom"""
# get last purchase order item details
+
last_purchase_order = frappe.db.sql("""\
select po.name, po.transaction_date, po.conversion_rate,
po_item.conversion_factor, po_item.base_price_list_rate,
@@ -941,6 +989,7 @@ def get_last_purchase_details(item_code, doc_name=None, conversion_rate=1.0):
order by po.transaction_date desc, po.name desc
limit 1""", (item_code, cstr(doc_name)), as_dict=1)
+
# get last purchase receipt item details
last_purchase_receipt = frappe.db.sql("""\
select pr.name, pr.posting_date, pr.posting_time, pr.conversion_rate,
@@ -952,19 +1001,20 @@ def get_last_purchase_details(item_code, doc_name=None, conversion_rate=1.0):
order by pr.posting_date desc, pr.posting_time desc, pr.name desc
limit 1""", (item_code, cstr(doc_name)), as_dict=1)
+
+
purchase_order_date = getdate(last_purchase_order and last_purchase_order[0].transaction_date
or "1900-01-01")
purchase_receipt_date = getdate(last_purchase_receipt and
last_purchase_receipt[0].posting_date or "1900-01-01")
- if (purchase_order_date > purchase_receipt_date) or \
- (last_purchase_order and not last_purchase_receipt):
+ if last_purchase_order and (purchase_order_date >= purchase_receipt_date or not last_purchase_receipt):
# use purchase order
+
last_purchase = last_purchase_order[0]
purchase_date = purchase_order_date
- elif (purchase_receipt_date > purchase_order_date) or \
- (last_purchase_receipt and not last_purchase_order):
+ elif last_purchase_receipt and (purchase_receipt_date > purchase_order_date or not last_purchase_order):
# use purchase receipt
last_purchase = last_purchase_receipt[0]
purchase_date = purchase_receipt_date
@@ -976,10 +1026,11 @@ def get_last_purchase_details(item_code, doc_name=None, conversion_rate=1.0):
out = frappe._dict({
"base_price_list_rate": flt(last_purchase.base_price_list_rate) / conversion_factor,
"base_rate": flt(last_purchase.base_rate) / conversion_factor,
- "base_net_rate": flt(last_purchase.net_rate) / conversion_factor,
+ "base_net_rate": flt(last_purchase.base_net_rate) / conversion_factor,
"discount_percentage": flt(last_purchase.discount_percentage),
"purchase_date": purchase_date
})
+
conversion_rate = flt(conversion_rate) or 1.0
out.update({
diff --git a/erpnext/stock/doctype/item/test_records.json b/erpnext/stock/doctype/item/test_records.json
index 6c1a55945c8..9ca887c77e3 100644
--- a/erpnext/stock/doctype/item/test_records.json
+++ b/erpnext/stock/doctype/item/test_records.json
@@ -92,8 +92,7 @@
{
"doctype": "Item Tax",
"parentfield": "taxes",
- "item_tax_template": "_Test Account Excise Duty @ 10",
- "tax_category": ""
+ "item_tax_template": "_Test Account Excise Duty @ 10"
}
],
"stock_uom": "_Test UOM 1"
@@ -371,8 +370,7 @@
{
"doctype": "Item Tax",
"parentfield": "taxes",
- "item_tax_template": "_Test Account Excise Duty @ 10",
- "tax_category": ""
+ "item_tax_template": "_Test Account Excise Duty @ 10"
},
{
"doctype": "Item Tax",
@@ -451,14 +449,13 @@
{
"doctype": "Item Tax",
"parentfield": "taxes",
- "item_tax_template": "_Test Account Excise Duty @ 20",
- "tax_category": ""
+ "item_tax_template": "_Test Account Excise Duty @ 20"
},
{
"doctype": "Item Tax",
"parentfield": "taxes",
- "item_tax_template": "_Test Item Tax Template 1",
- "tax_category": "_Test Tax Category 1"
+ "tax_category": "_Test Tax Category 1",
+ "item_tax_template": "_Test Item Tax Template 1"
}
]
}
diff --git a/erpnext/stock/doctype/item_alternative/item_alternative.py b/erpnext/stock/doctype/item_alternative/item_alternative.py
index 8e54539b18b..204f71a2bb4 100644
--- a/erpnext/stock/doctype/item_alternative/item_alternative.py
+++ b/erpnext/stock/doctype/item_alternative/item_alternative.py
@@ -22,11 +22,28 @@ class ItemAlternative(Document):
if self.item_code == self.alternative_item_code:
frappe.throw(_("Alternative item must not be same as item code"))
+ item_meta = frappe.get_meta("Item")
+ fields = ["is_stock_item", "include_item_in_manufacturing","has_serial_no","has_batch_no"]
+ item_data = frappe.db.get_values("Item", self.item_code, fields, as_dict=1)
+ alternative_item_data = frappe.db.get_values("Item", self.alternative_item_code, fields, as_dict=1)
+
+ for field in fields:
+ if item_data[0].get(field) != alternative_item_data[0].get(field):
+ raise_exception, alert = [1, False] if field == "is_stock_item" else [0, True]
+
+ frappe.msgprint(_("The value of {0} differs between Items {1} and {2}") \
+ .format(frappe.bold(item_meta.get_label(field)),
+ frappe.bold(self.alternative_item_code),
+ frappe.bold(self.item_code)),
+ alert=alert, raise_exception=raise_exception)
+
def validate_duplicate(self):
if frappe.db.get_value("Item Alternative", {'item_code': self.item_code,
'alternative_item_code': self.alternative_item_code, 'name': ('!=', self.name)}):
frappe.throw(_("Already record exists for the item {0}".format(self.item_code)))
+@frappe.whitelist()
+@frappe.validate_and_sanitize_search_inputs
def get_alternative_items(doctype, txt, searchfield, start, page_len, filters):
return frappe.db.sql(""" (select alternative_item_code from `tabItem Alternative`
where item_code = %(item_code)s and alternative_item_code like %(txt)s)
@@ -37,4 +54,4 @@ def get_alternative_items(doctype, txt, searchfield, start, page_len, filters):
""".format(start, page_len), {
"item_code": filters.get('item_code'),
"txt": '%' + txt + '%'
- })
\ No newline at end of file
+ })
diff --git a/erpnext/stock/doctype/item_alternative/test_item_alternative.py b/erpnext/stock/doctype/item_alternative/test_item_alternative.py
index f045e4f9114..61d90392364 100644
--- a/erpnext/stock/doctype/item_alternative/test_item_alternative.py
+++ b/erpnext/stock/doctype/item_alternative/test_item_alternative.py
@@ -13,6 +13,7 @@ from erpnext.manufacturing.doctype.work_order.test_work_order import make_wo_ord
from erpnext.buying.doctype.purchase_order.purchase_order import make_purchase_receipt, make_rm_stock_entry
import unittest
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import set_perpetual_inventory
+from erpnext.stock.doctype.stock_reconciliation.stock_reconciliation import EmptyStockReconciliationItemsError
class TestItemAlternative(unittest.TestCase):
def setUp(self):
@@ -110,8 +111,11 @@ def make_items():
if not frappe.db.exists('Item', item_code):
create_item(item_code)
- create_stock_reconciliation(item_code="Test FG A RW 1",
- warehouse='_Test Warehouse - _TC', qty=10, rate=2000)
+ try:
+ create_stock_reconciliation(item_code="Test FG A RW 1",
+ warehouse='_Test Warehouse - _TC', qty=10, rate=2000)
+ except EmptyStockReconciliationItemsError:
+ pass
if frappe.db.exists('Item', 'Test FG A RW 1'):
doc = frappe.get_doc('Item', 'Test FG A RW 1')
diff --git a/erpnext/stock/doctype/item_attribute/item_attribute.py b/erpnext/stock/doctype/item_attribute/item_attribute.py
index 71b998fb954..2f75bbd97c0 100644
--- a/erpnext/stock/doctype/item_attribute/item_attribute.py
+++ b/erpnext/stock/doctype/item_attribute/item_attribute.py
@@ -34,7 +34,7 @@ class ItemAttribute(Document):
if self.numeric_values:
validate_is_incremental(self, self.name, item.value, item.name)
else:
- validate_item_attribute_value(attributes_list, self.name, item.value, item.name)
+ validate_item_attribute_value(attributes_list, self.name, item.value, item.name, from_variant=False)
def validate_numeric(self):
if self.numeric_values:
diff --git a/erpnext/stock/doctype/item_manufacturer/item_manufacturer.json b/erpnext/stock/doctype/item_manufacturer/item_manufacturer.json
index 956c92e6738..0cef6eafaef 100644
--- a/erpnext/stock/doctype/item_manufacturer/item_manufacturer.json
+++ b/erpnext/stock/doctype/item_manufacturer/item_manufacturer.json
@@ -1,4 +1,5 @@
{
+ "actions": [],
"allow_import": 1,
"creation": "2019-06-02 04:41:37.332911",
"doctype": "DocType",
@@ -10,7 +11,8 @@
"manufacturer_part_no",
"column_break_3",
"item_name",
- "description"
+ "description",
+ "is_default"
],
"fields": [
{
@@ -52,9 +54,17 @@
"fieldtype": "Small Text",
"label": "Description",
"read_only": 1
+ },
+ {
+ "default": "0",
+ "fieldname": "is_default",
+ "fieldtype": "Check",
+ "in_list_view": 1,
+ "label": "Is Default"
}
],
- "modified": "2019-06-06 19:07:31.175919",
+ "links": [],
+ "modified": "2020-04-07 20:25:55.507905",
"modified_by": "Administrator",
"module": "Stock",
"name": "Item Manufacturer",
diff --git a/erpnext/stock/doctype/item_manufacturer/item_manufacturer.py b/erpnext/stock/doctype/item_manufacturer/item_manufacturer.py
index 67eab82d970..c27d1be7892 100644
--- a/erpnext/stock/doctype/item_manufacturer/item_manufacturer.py
+++ b/erpnext/stock/doctype/item_manufacturer/item_manufacturer.py
@@ -11,6 +11,10 @@ from frappe.model.document import Document
class ItemManufacturer(Document):
def validate(self):
self.validate_duplicate_entry()
+ self.manage_default_item_manufacturer()
+
+ def on_trash(self):
+ self.manage_default_item_manufacturer(delete=True)
def validate_duplicate_entry(self):
if self.is_new():
@@ -24,6 +28,40 @@ class ItemManufacturer(Document):
frappe.throw(_("Duplicate entry against the item code {0} and manufacturer {1}")
.format(self.item_code, self.manufacturer))
+ def manage_default_item_manufacturer(self, delete=False):
+ from frappe.model.utils import set_default
+
+ item = frappe.get_doc("Item", self.item_code)
+ default_manufacturer = item.default_item_manufacturer
+ default_part_no = item.default_manufacturer_part_no
+
+ if not self.is_default:
+ # if unchecked and default in Item master, clear it.
+ if default_manufacturer == self.manufacturer and default_part_no == self.manufacturer_part_no:
+ frappe.db.set_value("Item", item.name,
+ {
+ "default_item_manufacturer": None,
+ "default_manufacturer_part_no": None
+ })
+
+ elif self.is_default:
+ set_default(self, "item_code")
+ manufacturer, manufacturer_part_no = default_manufacturer, default_part_no
+
+ if delete:
+ manufacturer, manufacturer_part_no = None, None
+
+ elif (default_manufacturer != self.manufacturer) or \
+ (default_manufacturer == self.manufacturer and default_part_no != self.manufacturer_part_no):
+ manufacturer = self.manufacturer
+ manufacturer_part_no = self.manufacturer_part_no
+
+ frappe.db.set_value("Item", item.name,
+ {
+ "default_item_manufacturer": manufacturer,
+ "default_manufacturer_part_no": manufacturer_part_no
+ })
+
@frappe.whitelist()
def get_item_manufacturer_part_no(item_code, manufacturer):
return frappe.db.get_value("Item Manufacturer",
diff --git a/erpnext/stock/doctype/item_price/item_price.json b/erpnext/stock/doctype/item_price/item_price.json
index 8456b584ca0..2e0ddfdaefc 100644
--- a/erpnext/stock/doctype/item_price/item_price.json
+++ b/erpnext/stock/doctype/item_price/item_price.json
@@ -10,7 +10,6 @@
"item_code",
"uom",
"packing_unit",
- "min_qty",
"column_break_17",
"item_name",
"brand",
@@ -63,13 +62,6 @@
"fieldtype": "Int",
"label": "Packing Unit"
},
- {
- "default": "1",
- "fieldname": "min_qty",
- "fieldtype": "Int",
- "in_list_view": 1,
- "label": "Minimum Qty "
- },
{
"fieldname": "column_break_17",
"fieldtype": "Column Break"
@@ -216,7 +208,7 @@
"icon": "fa fa-flag",
"idx": 1,
"links": [],
- "modified": "2019-12-31 03:11:09.702250",
+ "modified": "2020-02-28 14:21:25.580331",
"modified_by": "Administrator",
"module": "Stock",
"name": "Item Price",
@@ -251,6 +243,7 @@
}
],
"quick_entry": 1,
+ "sort_field": "modified",
"sort_order": "ASC",
"title_field": "item_name",
"track_changes": 1
diff --git a/erpnext/stock/doctype/item_price/item_price.py b/erpnext/stock/doctype/item_price/item_price.py
index 957c41546b3..8e39eb5037d 100644
--- a/erpnext/stock/doctype/item_price/item_price.py
+++ b/erpnext/stock/doctype/item_price/item_price.py
@@ -69,3 +69,10 @@ class ItemPrice(Document):
self.reference = self.customer
if self.buying:
self.reference = self.supplier
+
+ if self.selling and not self.buying:
+ # if only selling then remove supplier
+ self.supplier = None
+ if self.buying and not self.selling:
+ # if only buying then remove customer
+ self.customer = None
diff --git a/erpnext/stock/doctype/item_price/test_records.json b/erpnext/stock/doctype/item_price/test_records.json
index 473bacb3c33..0a3d7e81985 100644
--- a/erpnext/stock/doctype/item_price/test_records.json
+++ b/erpnext/stock/doctype/item_price/test_records.json
@@ -4,7 +4,6 @@
"item_code": "_Test Item",
"price_list": "_Test Price List",
"price_list_rate": 100,
- "min_qty": 2,
"valid_from": "2017-04-18",
"valid_upto": "2017-04-26"
},
@@ -12,8 +11,7 @@
"doctype": "Item Price",
"item_code": "_Test Item",
"price_list": "_Test Price List Rest of the World",
- "price_list_rate": 10,
- "min_qty": 5
+ "price_list_rate": 10
},
{
"doctype": "Item Price",
@@ -22,7 +20,6 @@
"price_list_rate": 20,
"valid_from": "2017-04-18",
"valid_upto": "2017-04-26",
- "min_qty": 7,
"customer": "_Test Customer",
"uom": "_Test UOM"
},
@@ -31,19 +28,15 @@
"item_code": "_Test Item Home Desktop 100",
"price_list": "_Test Price List",
"price_list_rate": 1000,
- "min_qty" : 10,
"valid_from": "2017-04-10",
- "valid_upto": "2017-04-17",
- "min_qty": 2
+ "valid_upto": "2017-04-17"
},
{
"doctype": "Item Price",
"item_code": "_Test Item Home Desktop Manufactured",
"price_list": "_Test Price List",
"price_list_rate": 1000,
- "min_qty" : 10,
"valid_from": "2017-04-10",
- "valid_upto": "2017-04-17",
- "min_qty": 2
+ "valid_upto": "2017-04-17"
}
]
diff --git a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py
index 7df40fb02cd..5ad0e13db9a 100644
--- a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py
+++ b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py
@@ -8,6 +8,7 @@ from frappe.utils import flt
from frappe.model.meta import get_field_precision
from frappe.model.document import Document
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
+from erpnext.accounts.doctype.account.account import get_account_currency
class LandedCostVoucher(Document):
def get_items_from_purchase_receipts(self):
@@ -43,6 +44,7 @@ class LandedCostVoucher(Document):
else:
self.validate_applicable_charges_for_item()
self.validate_purchase_receipts()
+ self.validate_expense_accounts()
self.set_total_taxes_and_charges()
def check_mandatory(self):
@@ -71,6 +73,14 @@ class LandedCostVoucher(Document):
frappe.throw(_("Row {0}: Cost center is required for an item {1}")
.format(item.idx, item.item_code))
+ def validate_expense_accounts(self):
+ company_currency = erpnext.get_company_currency(self.company)
+ for account in self.taxes:
+ if get_account_currency(account.expense_account) != company_currency:
+ frappe.throw(msg=_(""" Row {0}: Expense account currency should be same as company's default currency.
+ Please select expense account with account currency as {1}""")
+ .format(account.idx, frappe.bold(company_currency)), title=_("Invalid Account Currency"))
+
def set_total_taxes_and_charges(self):
self.total_taxes_and_charges = sum([flt(d.amount) for d in self.get("taxes")])
@@ -104,7 +114,6 @@ class LandedCostVoucher(Document):
def update_landed_cost(self):
for d in self.get("purchase_receipts"):
doc = frappe.get_doc(d.receipt_document_type, d.receipt_document)
-
# check if there are {qty} assets created and linked to this receipt document
self.validate_asset_qty_and_status(d.receipt_document_type, doc)
@@ -123,6 +132,8 @@ class LandedCostVoucher(Document):
# update latest valuation rate in serial no
self.update_rate_in_serial_no_for_non_asset_items(doc)
+ for d in self.get("purchase_receipts"):
+ doc = frappe.get_doc(d.receipt_document_type, d.receipt_document)
# update stock & gl entries for cancelled state of PR
doc.docstatus = 2
doc.update_stock_ledger(allow_negative_stock=True, via_landed_cost_voucher=True)
diff --git a/erpnext/stock/doctype/material_request/material_request.js b/erpnext/stock/doctype/material_request/material_request.js
index 935d61310eb..1ccd8cf31a0 100644
--- a/erpnext/stock/doctype/material_request/material_request.js
+++ b/erpnext/stock/doctype/material_request/material_request.js
@@ -17,13 +17,14 @@ frappe.ui.form.on('Material Request', {
// formatter for material request item
frm.set_indicator_formatter('item_code',
- function(doc) { return (doc.qty<=doc.ordered_qty) ? "green" : "orange"; });
+ function(doc) { return (doc.stock_qty<=doc.ordered_qty) ? "green" : "orange"; });
- frm.set_query("item_code", "items", function() {
+ frm.set_query("from_warehouse", "items", function(doc) {
return {
- query: "erpnext.controllers.queries.item_query"
+ filters: {'company': doc.company}
};
});
+
},
onload: function(frm) {
@@ -32,11 +33,24 @@ frappe.ui.form.on('Material Request', {
// set schedule_date
set_schedule_date(frm);
- frm.fields_dict["items"].grid.get_field("warehouse").get_query = function(doc) {
+
+ frm.set_query("warehouse", "items", function(doc) {
return {
filters: {'company': doc.company}
};
- };
+ });
+
+ frm.set_query("set_warehouse", function(doc){
+ return {
+ filters: {'company': doc.company}
+ };
+ });
+
+ frm.set_query("set_from_warehouse", function(doc){
+ return {
+ filters: {'company': doc.company}
+ };
+ });
},
onload_post_render: function(frm) {
@@ -145,6 +159,8 @@ frappe.ui.form.on('Material Request', {
},
get_item_data: function(frm, item) {
+ if (item && !item.item_code) { return; }
+
frm.call({
method: "erpnext.stock.get_item_details.get_item_details",
child: item,
@@ -230,8 +246,19 @@ frappe.ui.form.on('Material Request', {
make_purchase_order: function(frm) {
frappe.prompt(
- {fieldname:'default_supplier', label: __('For Default Supplier (optional)'), description: __('Selected Supplier\
- must be the Default Supplier of one of the items below.'), fieldtype: 'Link', options: 'Supplier'},
+ {
+ label: __('For Default Supplier (Optional)'),
+ fieldname:'default_supplier',
+ fieldtype: 'Link',
+ options: 'Supplier',
+ description: __('Select a Supplier from the Default Supplier List of the items below.'),
+ get_query: () => {
+ return{
+ query: "erpnext.stock.doctype.material_request.material_request.get_default_supplier_query",
+ filters: {'doc': frm.doc.name}
+ }
+ }
+ },
(values) => {
frappe.model.open_mapped_doc({
method: "erpnext.stock.doctype.material_request.material_request.make_purchase_order",
@@ -348,6 +375,22 @@ erpnext.buying.MaterialRequestController = erpnext.buying.BuyingController.exten
set_schedule_date(this.frm);
},
+ onload: function(doc, cdt, cdn) {
+ this.frm.set_query("item_code", "items", function() {
+ if (doc.material_request_type == "Customer Provided") {
+ return{
+ query: "erpnext.controllers.queries.item_query",
+ filters:{ 'customer': me.frm.doc.customer }
+ }
+ } else if (doc.material_request_type != "Manufacture") {
+ return{
+ query: "erpnext.controllers.queries.item_query",
+ filters: {'is_purchase_item': 1}
+ }
+ }
+ });
+ },
+
items_add: function(doc, cdt, cdn) {
var row = frappe.get_doc(cdt, cdn);
if(doc.schedule_date) {
diff --git a/erpnext/stock/doctype/material_request/material_request.py b/erpnext/stock/doctype/material_request/material_request.py
index 44e890cc50e..1c7cdad48bc 100644
--- a/erpnext/stock/doctype/material_request/material_request.py
+++ b/erpnext/stock/doctype/material_request/material_request.py
@@ -174,12 +174,11 @@ class MaterialRequest(BuyingController):
frappe.db.set_value(d.doctype, d.name, "ordered_qty", d.ordered_qty)
- target_ref_field = 'qty' if self.material_request_type == "Manufacture" else 'stock_qty'
self._update_percent_field({
"target_dt": "Material Request Item",
"target_parent_dt": self.doctype,
"target_parent_field": "per_ordered",
- "target_ref_field": target_ref_field,
+ "target_ref_field": "stock_qty",
"target_field": "ordered_qty",
"name": self.name,
}, update_modified)
@@ -386,6 +385,20 @@ def get_material_requests_based_on_supplier(supplier):
return material_requests, supplier_items
+@frappe.whitelist()
+@frappe.validate_and_sanitize_search_inputs
+def get_default_supplier_query(doctype, txt, searchfield, start, page_len, filters):
+ doc = frappe.get_doc("Material Request", filters.get("doc"))
+ item_list = []
+ for d in doc.items:
+ item_list.append(d.item_code)
+
+ return frappe.db.sql("""select default_supplier
+ from `tabItem Default`
+ where parent in ({0}) and
+ default_supplier IS NOT NULL
+ """.format(', '.join(['%s']*len(item_list))),tuple(item_list))
+
@frappe.whitelist()
def make_supplier_quotation(source_name, target_doc=None):
def postprocess(source, target_doc):
@@ -425,6 +438,9 @@ def make_stock_entry(source_name, target_doc=None):
else:
target.s_warehouse = obj.warehouse
+ if source_parent.material_request_type == "Customer Provided":
+ target.allow_zero_valuation_rate = 1
+
def set_missing_values(source, target):
target.purpose = source.material_request_type
if source.job_card:
@@ -442,7 +458,7 @@ def make_stock_entry(source_name, target_doc=None):
"doctype": "Stock Entry",
"validation": {
"docstatus": ["=", 1],
- "material_request_type": ["in", ["Material Transfer", "Material Issue"]]
+ "material_request_type": ["in", ["Material Transfer", "Material Issue", "Customer Provided"]]
}
},
"Material Request Item": {
@@ -467,12 +483,12 @@ def raise_work_orders(material_request):
default_wip_warehouse = frappe.db.get_single_value("Manufacturing Settings", "default_wip_warehouse")
for d in mr.items:
- if (d.qty - d.ordered_qty) >0:
+ if (d.stock_qty - d.ordered_qty) > 0:
if frappe.db.exists("BOM", {"item": d.item_code, "is_default": 1}):
wo_order = frappe.new_doc("Work Order")
wo_order.update({
"production_item": d.item_code,
- "qty": d.qty - d.ordered_qty,
+ "qty": d.stock_qty - d.ordered_qty,
"fg_warehouse": d.warehouse,
"wip_warehouse": default_wip_warehouse,
"description": d.description,
@@ -499,7 +515,7 @@ def raise_work_orders(material_request):
msgprint(_("The following Work Orders were created:") + '\n' + new_line_sep(message))
if errors:
- frappe.throw(_("Productions Orders cannot be raised for:") + '\n' + new_line_sep(errors))
+ frappe.throw(_("Work Order cannot be created for following reason:") + '\n' + new_line_sep(errors))
return work_orders
@@ -526,4 +542,4 @@ def create_pick_list(source_name, target_doc=None):
doc.set_item_locations()
- return doc
\ No newline at end of file
+ return doc
diff --git a/erpnext/stock/doctype/material_request/test_material_request.py b/erpnext/stock/doctype/material_request/test_material_request.py
index 79cdc1ad18a..19924b16363 100644
--- a/erpnext/stock/doctype/material_request/test_material_request.py
+++ b/erpnext/stock/doctype/material_request/test_material_request.py
@@ -7,7 +7,8 @@
from __future__ import unicode_literals
import frappe, unittest, erpnext
from frappe.utils import flt, today
-from erpnext.stock.doctype.material_request.material_request import raise_work_orders
+from erpnext.stock.doctype.material_request.material_request \
+ import raise_work_orders, make_stock_entry, make_purchase_order, make_supplier_quotation
from erpnext.stock.doctype.item.test_item import create_item
class TestMaterialRequest(unittest.TestCase):
@@ -15,8 +16,6 @@ class TestMaterialRequest(unittest.TestCase):
erpnext.set_perpetual_inventory(0)
def test_make_purchase_order(self):
- from erpnext.stock.doctype.material_request.material_request import make_purchase_order
-
mr = frappe.copy_doc(test_records[0]).insert()
self.assertRaises(frappe.ValidationError, make_purchase_order,
@@ -30,8 +29,6 @@ class TestMaterialRequest(unittest.TestCase):
self.assertEqual(len(po.get("items")), len(mr.get("items")))
def test_make_supplier_quotation(self):
- from erpnext.stock.doctype.material_request.material_request import make_supplier_quotation
-
mr = frappe.copy_doc(test_records[0]).insert()
self.assertRaises(frappe.ValidationError, make_supplier_quotation, mr.name)
@@ -45,12 +42,9 @@ class TestMaterialRequest(unittest.TestCase):
def test_make_stock_entry(self):
- from erpnext.stock.doctype.material_request.material_request import make_stock_entry
-
mr = frappe.copy_doc(test_records[0]).insert()
- self.assertRaises(frappe.ValidationError, make_stock_entry,
- mr.name)
+ self.assertRaises(frappe.ValidationError, make_stock_entry, mr.name)
mr = frappe.get_doc("Material Request", mr.name)
mr.material_request_type = "Material Transfer"
@@ -62,40 +56,40 @@ class TestMaterialRequest(unittest.TestCase):
def _insert_stock_entry(self, qty1, qty2, warehouse = None ):
se = frappe.get_doc({
- "company": "_Test Company",
- "doctype": "Stock Entry",
- "posting_date": "2013-03-01",
- "posting_time": "00:00:00",
- "purpose": "Material Receipt",
- "items": [
- {
- "conversion_factor": 1.0,
- "doctype": "Stock Entry Detail",
- "item_code": "_Test Item Home Desktop 100",
- "parentfield": "items",
- "basic_rate": 100,
- "qty": qty1,
- "stock_uom": "_Test UOM 1",
- "transfer_qty": qty1,
- "uom": "_Test UOM 1",
- "t_warehouse": warehouse or "_Test Warehouse 1 - _TC",
- "cost_center": "_Test Cost Center - _TC"
- },
- {
- "conversion_factor": 1.0,
- "doctype": "Stock Entry Detail",
- "item_code": "_Test Item Home Desktop 200",
- "parentfield": "items",
- "basic_rate": 100,
- "qty": qty2,
- "stock_uom": "_Test UOM 1",
- "transfer_qty": qty2,
- "uom": "_Test UOM 1",
- "t_warehouse": warehouse or "_Test Warehouse 1 - _TC",
- "cost_center": "_Test Cost Center - _TC"
- }
- ]
- })
+ "company": "_Test Company",
+ "doctype": "Stock Entry",
+ "posting_date": "2013-03-01",
+ "posting_time": "00:00:00",
+ "purpose": "Material Receipt",
+ "items": [
+ {
+ "conversion_factor": 1.0,
+ "doctype": "Stock Entry Detail",
+ "item_code": "_Test Item Home Desktop 100",
+ "parentfield": "items",
+ "basic_rate": 100,
+ "qty": qty1,
+ "stock_uom": "_Test UOM 1",
+ "transfer_qty": qty1,
+ "uom": "_Test UOM 1",
+ "t_warehouse": warehouse or "_Test Warehouse 1 - _TC",
+ "cost_center": "_Test Cost Center - _TC"
+ },
+ {
+ "conversion_factor": 1.0,
+ "doctype": "Stock Entry Detail",
+ "item_code": "_Test Item Home Desktop 200",
+ "parentfield": "items",
+ "basic_rate": 100,
+ "qty": qty2,
+ "stock_uom": "_Test UOM 1",
+ "transfer_qty": qty2,
+ "uom": "_Test UOM 1",
+ "t_warehouse": warehouse or "_Test Warehouse 1 - _TC",
+ "cost_center": "_Test Cost Center - _TC"
+ }
+ ]
+ })
se.set_stock_entry_type()
se.insert()
@@ -198,14 +192,7 @@ class TestMaterialRequest(unittest.TestCase):
mr.insert()
mr.submit()
- # check if per complete is None
- mr.load_from_db()
- self.assertEqual(mr.per_ordered, 0)
- self.assertEqual(mr.get("items")[0].ordered_qty, 0)
- self.assertEqual(mr.get("items")[1].ordered_qty, 0)
-
# map a purchase order
- from erpnext.stock.doctype.material_request.material_request import make_purchase_order
po_doc = make_purchase_order(mr.name)
po_doc.supplier = "_Test Supplier"
po_doc.transaction_date = "2013-07-07"
@@ -279,8 +266,6 @@ class TestMaterialRequest(unittest.TestCase):
self.assertEqual(current_requested_qty_item1, existing_requested_qty_item1 + 54.0)
self.assertEqual(current_requested_qty_item2, existing_requested_qty_item2 + 3.0)
- from erpnext.stock.doctype.material_request.material_request import make_stock_entry
-
# map a stock entry
se_doc = make_stock_entry(mr.name)
se_doc.update({
@@ -357,14 +342,7 @@ class TestMaterialRequest(unittest.TestCase):
mr.insert()
mr.submit()
- # check if per complete is None
- mr.load_from_db()
- self.assertEqual(mr.per_ordered, 0)
- self.assertEqual(mr.get("items")[0].ordered_qty, 0)
- self.assertEqual(mr.get("items")[1].ordered_qty, 0)
-
# map a stock entry
- from erpnext.stock.doctype.material_request.material_request import make_stock_entry
se_doc = make_stock_entry(mr.name)
se_doc.update({
@@ -435,9 +413,6 @@ class TestMaterialRequest(unittest.TestCase):
mr.insert()
mr.submit()
- # map a stock entry
- from erpnext.stock.doctype.material_request.material_request import make_stock_entry
-
se_doc = make_stock_entry(mr.name)
se_doc.update({
"posting_date": "2013-03-01",
@@ -468,8 +443,6 @@ class TestMaterialRequest(unittest.TestCase):
mr.insert()
mr.submit()
- # map a stock entry
- from erpnext.stock.doctype.material_request.material_request import make_stock_entry
se_doc = make_stock_entry(mr.name)
self.assertEqual(se_doc.get("items")[0].s_warehouse, "_Test Warehouse - _TC")
@@ -483,8 +456,6 @@ class TestMaterialRequest(unittest.TestCase):
return flt(frappe.db.get_value("Bin", {"item_code": item_code, "warehouse": warehouse}, "indented_qty"))
def test_make_stock_entry_for_material_issue(self):
- from erpnext.stock.doctype.material_request.material_request import make_stock_entry
-
mr = frappe.copy_doc(test_records[0]).insert()
self.assertRaises(frappe.ValidationError, make_stock_entry,
@@ -503,8 +474,6 @@ class TestMaterialRequest(unittest.TestCase):
return flt(frappe.db.get_value("Bin", {"item_code": "_Test Item Home Desktop 100",
"warehouse": "_Test Warehouse - _TC"}, "indented_qty"))
- from erpnext.stock.doctype.material_request.material_request import make_stock_entry
-
existing_requested_qty = _get_requested_qty()
mr = frappe.copy_doc(test_records[0])
@@ -512,7 +481,7 @@ class TestMaterialRequest(unittest.TestCase):
mr.submit()
#testing bin value after material request is submitted
- self.assertEqual(_get_requested_qty(), existing_requested_qty + 54.0)
+ self.assertEqual(_get_requested_qty(), existing_requested_qty - 54.0)
# receive items to allow issue
self._insert_stock_entry(60, 6, "_Test Warehouse - _TC")
@@ -563,9 +532,37 @@ class TestMaterialRequest(unittest.TestCase):
item_code= %s and warehouse= %s """, (mr.items[0].item_code, mr.items[0].warehouse))[0][0]
self.assertEqual(requested_qty, new_requested_qty)
- def test_multi_uom_for_purchase(self):
- from erpnext.stock.doctype.material_request.material_request import make_purchase_order
+ def test_requested_qty_multi_uom(self):
+ existing_requested_qty = self._get_requested_qty('_Test FG Item', '_Test Warehouse - _TC')
+ mr = make_material_request(item_code='_Test FG Item', material_request_type='Manufacture',
+ uom="_Test UOM 1", conversion_factor=12)
+
+ requested_qty = self._get_requested_qty('_Test FG Item', '_Test Warehouse - _TC')
+
+ self.assertEqual(requested_qty, existing_requested_qty + 120)
+
+ work_order = raise_work_orders(mr.name)
+ wo = frappe.get_doc("Work Order", work_order[0])
+ wo.qty = 50
+ wo.wip_warehouse = "_Test Warehouse 1 - _TC"
+ wo.submit()
+
+ requested_qty = self._get_requested_qty('_Test FG Item', '_Test Warehouse - _TC')
+ self.assertEqual(requested_qty, existing_requested_qty + 70)
+
+ wo.cancel()
+
+ requested_qty = self._get_requested_qty('_Test FG Item', '_Test Warehouse - _TC')
+ self.assertEqual(requested_qty, existing_requested_qty + 120)
+
+ mr.reload()
+ mr.cancel()
+ requested_qty = self._get_requested_qty('_Test FG Item', '_Test Warehouse - _TC')
+ self.assertEqual(requested_qty, existing_requested_qty)
+
+
+ def test_multi_uom_for_purchase(self):
mr = frappe.copy_doc(test_records[0])
mr.material_request_type = 'Purchase'
item = mr.items[0]
@@ -607,8 +604,9 @@ class TestMaterialRequest(unittest.TestCase):
self.assertEqual(mr.per_ordered, 100)
def test_customer_provided_parts_mr(self):
- from erpnext.stock.doctype.material_request.material_request import make_stock_entry
create_item('CUST-0987', is_customer_provided_item = 1, customer = '_Test Customer', is_purchase_item = 0)
+ existing_requested_qty = self._get_requested_qty("_Test Customer", "_Test Warehouse - _TC")
+
mr = make_material_request(item_code='CUST-0987', material_request_type='Customer Provided')
se = make_stock_entry(mr.name)
se.insert()
@@ -617,7 +615,10 @@ class TestMaterialRequest(unittest.TestCase):
self.assertEqual(se.get("items")[0].material_request, mr.name)
mr = frappe.get_doc("Material Request", mr.name)
mr.submit()
+ current_requested_qty = self._get_requested_qty("_Test Customer", "_Test Warehouse - _TC")
+
self.assertEqual(mr.per_ordered, 100)
+ self.assertEqual(existing_requested_qty, current_requested_qty)
def make_material_request(**args):
args = frappe._dict(args)
@@ -628,6 +629,8 @@ def make_material_request(**args):
mr.append("items", {
"item_code": args.item_code or "_Test Item",
"qty": args.qty or 10,
+ "uom": args.uom or "_Test UOM",
+ "conversion_factor": args.conversion_factor or 1,
"schedule_date": args.schedule_date or today(),
"warehouse": args.warehouse or "_Test Warehouse - _TC",
"cost_center": args.cost_center or "_Test Cost Center - _TC"
diff --git a/erpnext/stock/doctype/material_request_item/material_request_item.json b/erpnext/stock/doctype/material_request_item/material_request_item.json
index 795971b5e3d..56049131bb4 100644
--- a/erpnext/stock/doctype/material_request_item/material_request_item.json
+++ b/erpnext/stock/doctype/material_request_item/material_request_item.json
@@ -185,12 +185,14 @@
{
"fieldname": "rate",
"fieldtype": "Currency",
- "label": "Rate"
+ "label": "Rate",
+ "no_copy": 1
},
{
"fieldname": "amount",
"fieldtype": "Currency",
"label": "Amount",
+ "no_copy": 1,
"read_only": 1
},
{
@@ -371,7 +373,10 @@
{
"fieldname": "received_qty",
"fieldtype": "Float",
- "label": "Received Quantity"
+ "label": "Received Quantity",
+ "no_copy": 1,
+ "print_hide": 1,
+ "read_only": 1
},
{
"collapsible": 1,
@@ -401,13 +406,13 @@
{
"fieldname": "manufacturer_part_no",
"fieldtype": "Data",
- "label": "Manufacturer Part Number",
- "read_only": 1
+ "label": "Manufacturer Part Number"
}
],
"idx": 1,
"istable": 1,
- "modified": "2019-06-02 06:49:36.493957",
+ "links": [],
+ "modified": "2020-04-16 09:00:00.992835",
"modified_by": "Administrator",
"module": "Stock",
"name": "Material Request Item",
diff --git a/erpnext/stock/doctype/packing_slip/packing_slip.py b/erpnext/stock/doctype/packing_slip/packing_slip.py
index 7a5ae317c2b..a7a29cca7f8 100644
--- a/erpnext/stock/doctype/packing_slip/packing_slip.py
+++ b/erpnext/stock/doctype/packing_slip/packing_slip.py
@@ -175,6 +175,8 @@ class PackingSlip(Document):
self.update_item_details()
+@frappe.whitelist()
+@frappe.validate_and_sanitize_search_inputs
def item_details(doctype, txt, searchfield, start, page_len, filters):
from erpnext.controllers.queries import get_match_cond
return frappe.db.sql("""select name, item_name, description from `tabItem`
diff --git a/erpnext/stock/doctype/pick_list/pick_list.js b/erpnext/stock/doctype/pick_list/pick_list.js
index 278971125f0..3a5ef769805 100644
--- a/erpnext/stock/doctype/pick_list/pick_list.js
+++ b/erpnext/stock/doctype/pick_list/pick_list.js
@@ -31,20 +31,30 @@ frappe.ui.form.on('Pick List', {
};
});
frm.set_query('item_code', 'locations', () => {
+ return erpnext.queries.item({ "is_stock_item": 1 });
+ });
+ frm.set_query('batch_no', 'locations', (frm, cdt, cdn) => {
+ const row = locals[cdt][cdn];
return {
+ query: 'erpnext.controllers.queries.get_batch_no',
filters: {
- is_stock_item: 1
- }
+ item_code: row.item_code,
+ warehouse: row.warehouse
+ },
};
});
},
- get_item_locations: (frm) => {
- if (!frm.doc.locations || !frm.doc.locations.length) {
- frappe.msgprint(__('First add items in the Item Locations table'));
+ set_item_locations:(frm, save) => {
+ if (!(frm.doc.locations && frm.doc.locations.length)) {
+ frappe.msgprint(__('Add items in the Item Locations table'));
} else {
- frm.call('set_item_locations');
+ frm.call('set_item_locations', {save: save});
}
},
+ get_item_locations: (frm) => {
+ // Button on the form
+ frm.events.set_item_locations(frm, false);
+ },
refresh: (frm) => {
frm.trigger('add_get_items_button');
if (frm.doc.docstatus === 1) {
@@ -52,8 +62,13 @@ frappe.ui.form.on('Pick List', {
'pick_list_name': frm.doc.name,
'purpose': frm.doc.purpose
}).then(target_document_exists => {
+ frm.set_df_property("locations", "allow_on_submit", target_document_exists ? 0 : 1);
+
if (target_document_exists) return;
- if (frm.doc.purpose === 'Delivery against Sales Order') {
+
+ frm.add_custom_button(__('Update Current Stock'), () => frm.trigger('update_pick_list_stock'));
+
+ if (frm.doc.purpose === 'Delivery') {
frm.add_custom_button(__('Delivery Note'), () => frm.trigger('create_delivery_note'), __('Create'));
} else {
frm.add_custom_button(__('Stock Entry'), () => frm.trigger('create_stock_entry'), __('Create'));
@@ -105,6 +120,7 @@ frappe.ui.form.on('Pick List', {
method: 'erpnext.stock.doctype.pick_list.pick_list.create_delivery_note',
frm: frm
});
+
},
create_stock_entry: (frm) => {
frappe.xcall('erpnext.stock.doctype.pick_list.pick_list.create_stock_entry', {
@@ -114,9 +130,12 @@ frappe.ui.form.on('Pick List', {
frappe.set_route("Form", 'Stock Entry', stock_entry.name);
});
},
+ update_pick_list_stock: (frm) => {
+ frm.events.set_item_locations(frm, true);
+ },
add_get_items_button: (frm) => {
let purpose = frm.doc.purpose;
- if (purpose != 'Delivery against Sales Order' || frm.doc.docstatus !== 0) return;
+ if (purpose != 'Delivery' || frm.doc.docstatus !== 0) return;
let get_query_filters = {
docstatus: 1,
per_delivered: ['<', 100],
diff --git a/erpnext/stock/doctype/pick_list/pick_list.json b/erpnext/stock/doctype/pick_list/pick_list.json
index 8d5ef3d12ab..c01388dcd21 100644
--- a/erpnext/stock/doctype/pick_list/pick_list.json
+++ b/erpnext/stock/doctype/pick_list/pick_list.json
@@ -1,4 +1,5 @@
{
+ "actions": [],
"autoname": "naming_series:",
"creation": "2019-07-11 16:03:13.681045",
"doctype": "DocType",
@@ -44,7 +45,7 @@
"options": "Warehouse"
},
{
- "depends_on": "eval:doc.purpose==='Delivery against Sales Order'",
+ "depends_on": "eval:doc.purpose==='Delivery'",
"fieldname": "customer",
"fieldtype": "Link",
"in_list_view": 1,
@@ -59,6 +60,7 @@
"options": "Work Order"
},
{
+ "allow_on_submit": 1,
"fieldname": "locations",
"fieldtype": "Table",
"label": "Item Locations",
@@ -86,7 +88,7 @@
"fieldname": "purpose",
"fieldtype": "Select",
"label": "Purpose",
- "options": "Material Transfer for Manufacture\nMaterial Transfer\nDelivery against Sales Order"
+ "options": "Material Transfer for Manufacture\nMaterial Transfer\nDelivery"
},
{
"depends_on": "eval:['Material Transfer', 'Material Issue'].includes(doc.purpose)",
@@ -111,7 +113,8 @@
}
],
"is_submittable": 1,
- "modified": "2019-08-29 21:10:11.572387",
+ "links": [],
+ "modified": "2020-03-17 11:38:41.932875",
"modified_by": "Administrator",
"module": "Stock",
"name": "Pick List",
diff --git a/erpnext/stock/doctype/pick_list/pick_list.py b/erpnext/stock/doctype/pick_list/pick_list.py
index c4d8c41ebb7..63a59b87104 100644
--- a/erpnext/stock/doctype/pick_list/pick_list.py
+++ b/erpnext/stock/doctype/pick_list/pick_list.py
@@ -24,12 +24,15 @@ class PickList(Document):
for item in self.locations:
if not frappe.get_cached_value('Item', item.item_code, 'has_serial_no'):
continue
+ if not item.serial_no:
+ frappe.throw(_("Row #{0}: {1} does not have any available serial numbers in {2}".format(
+ frappe.bold(item.idx), frappe.bold(item.item_code), frappe.bold(item.warehouse))))
if len(item.serial_no.split('\n')) == item.picked_qty:
continue
frappe.throw(_('For item {0} at row {1}, count of serial numbers does not match with the picked quantity')
.format(frappe.bold(item.item_code), frappe.bold(item.idx)))
- def set_item_locations(self):
+ def set_item_locations(self, save=False):
items = self.aggregate_item_qty()
self.item_location_map = frappe._dict()
@@ -43,7 +46,7 @@ class PickList(Document):
item_code = item_doc.item_code
self.item_location_map.setdefault(item_code,
- get_available_item_locations(item_code, from_warehouses, self.item_count_map.get(item_code)))
+ get_available_item_locations(item_code, from_warehouses, self.item_count_map.get(item_code), self.company))
locations = get_items_with_location_and_quantity(item_doc, self.item_location_map)
@@ -59,12 +62,17 @@ class PickList(Document):
location.update(row)
self.append('locations', location)
+ if save:
+ self.save()
+
def aggregate_item_qty(self):
locations = self.get('locations')
self.item_count_map = {}
# aggregate qty for same item
item_map = OrderedDict()
for item in locations:
+ if not item.item_code:
+ frappe.throw("Row #{0}: Item Code is Mandatory".format(item.idx))
item_code = item.item_code
reference = item.sales_order_item or item.material_request_item
key = (item_code, item.uom, reference)
@@ -74,17 +82,21 @@ class PickList(Document):
if item_map.get(key):
item_map[key].qty += item.qty
- item_map[key].stock_qty += item.stock_qty
+ item_map[key].stock_qty += flt(item.stock_qty)
else:
item_map[key] = item
# maintain count of each item (useful to limit get query)
self.item_count_map.setdefault(item_code, 0)
- self.item_count_map[item_code] += item.stock_qty
+ self.item_count_map[item_code] += flt(item.stock_qty)
return item_map.values()
+def validate_item_locations(pick_list):
+ if not pick_list.locations:
+ frappe.throw(_("Add items in the Item Locations table"))
+
def get_items_with_location_and_quantity(item_doc, item_location_map):
available_locations = item_location_map.get(item_doc.item_code)
locations = []
@@ -107,11 +119,13 @@ def get_items_with_location_and_quantity(item_doc, item_location_map):
if item_location.serial_no:
serial_nos = '\n'.join(item_location.serial_no[0: cint(stock_qty)])
+ auto_set_serial_no = frappe.db.get_single_value("Stock Settings", "automatically_set_serial_nos_based_on_fifo")
+
locations.append(frappe._dict({
'qty': qty,
'stock_qty': stock_qty,
'warehouse': item_location.warehouse,
- 'serial_no': serial_nos,
+ 'serial_no': serial_nos if auto_set_serial_no else item_doc.serial_no,
'batch_no': item_location.batch_no
}))
@@ -130,14 +144,14 @@ def get_items_with_location_and_quantity(item_doc, item_location_map):
item_location_map[item_doc.item_code] = available_locations
return locations
-def get_available_item_locations(item_code, from_warehouses, required_qty):
+def get_available_item_locations(item_code, from_warehouses, required_qty, company):
locations = []
if frappe.get_cached_value('Item', item_code, 'has_serial_no'):
- locations = get_available_item_locations_for_serialized_item(item_code, from_warehouses, required_qty)
+ locations = get_available_item_locations_for_serialized_item(item_code, from_warehouses, required_qty, company)
elif frappe.get_cached_value('Item', item_code, 'has_batch_no'):
- locations = get_available_item_locations_for_batched_item(item_code, from_warehouses, required_qty)
+ locations = get_available_item_locations_for_batched_item(item_code, from_warehouses, required_qty, company)
else:
- locations = get_available_item_locations_for_other_item(item_code, from_warehouses, required_qty)
+ locations = get_available_item_locations_for_other_item(item_code, from_warehouses, required_qty, company)
total_qty_available = sum(location.get('qty') for location in locations)
@@ -150,9 +164,10 @@ def get_available_item_locations(item_code, from_warehouses, required_qty):
return locations
-def get_available_item_locations_for_serialized_item(item_code, from_warehouses, required_qty):
+def get_available_item_locations_for_serialized_item(item_code, from_warehouses, required_qty, company):
filters = frappe._dict({
'item_code': item_code,
+ 'company': company,
'warehouse': ['!=', '']
})
@@ -180,7 +195,7 @@ def get_available_item_locations_for_serialized_item(item_code, from_warehouses,
return locations
-def get_available_item_locations_for_batched_item(item_code, from_warehouses, required_qty):
+def get_available_item_locations_for_batched_item(item_code, from_warehouses, required_qty, company):
warehouse_condition = 'and warehouse in %(warehouses)s' if from_warehouses else ''
batch_locations = frappe.db.sql("""
SELECT
@@ -192,6 +207,8 @@ def get_available_item_locations_for_batched_item(item_code, from_warehouses, re
WHERE
sle.batch_no = batch.name
and sle.`item_code`=%(item_code)s
+ and sle.`company` = %(company)s
+ and batch.disabled = 0
and IFNULL(batch.`expiry_date`, '2200-01-01') > %(today)s
{warehouse_condition}
GROUP BY
@@ -202,16 +219,20 @@ def get_available_item_locations_for_batched_item(item_code, from_warehouses, re
ORDER BY IFNULL(batch.`expiry_date`, '2200-01-01'), batch.`creation`
""".format(warehouse_condition=warehouse_condition), { #nosec
'item_code': item_code,
+ 'company': company,
'today': today(),
'warehouses': from_warehouses
}, as_dict=1)
return batch_locations
-def get_available_item_locations_for_other_item(item_code, from_warehouses, required_qty):
+def get_available_item_locations_for_other_item(item_code, from_warehouses, required_qty, company):
# gets all items available in different warehouses
+ warehouses = [x.get('name') for x in frappe.get_list("Warehouse", {'company': company}, "name")]
+
filters = frappe._dict({
'item_code': item_code,
+ 'warehouse': ['in', warehouses],
'actual_qty': ['>', 0]
})
@@ -230,7 +251,9 @@ def get_available_item_locations_for_other_item(item_code, from_warehouses, requ
@frappe.whitelist()
def create_delivery_note(source_name, target_doc=None):
pick_list = frappe.get_doc('Pick List', source_name)
- sales_orders = [d.sales_order for d in pick_list.locations]
+ validate_item_locations(pick_list)
+
+ sales_orders = [d.sales_order for d in pick_list.locations if d.sales_order]
sales_orders = set(sales_orders)
delivery_note = None
@@ -238,6 +261,10 @@ def create_delivery_note(source_name, target_doc=None):
delivery_note = create_delivery_note_from_sales_order(sales_order,
delivery_note, skip_item_mapping=True)
+ # map rows without sales orders as well
+ if not delivery_note:
+ delivery_note = frappe.new_doc("Delivery Note")
+
item_table_mapper = {
'doctype': 'Delivery Note Item',
'field_map': {
@@ -248,9 +275,25 @@ def create_delivery_note(source_name, target_doc=None):
'condition': lambda doc: abs(doc.delivered_qty) < abs(doc.qty) and doc.delivered_by_supplier!=1
}
+ item_table_mapper_without_so = {
+ 'doctype': 'Delivery Note Item',
+ 'field_map': {
+ 'rate': 'rate',
+ 'name': 'name',
+ 'parent': '',
+ }
+ }
+
for location in pick_list.locations:
- sales_order_item = frappe.get_cached_doc('Sales Order Item', location.sales_order_item)
- dn_item = map_child_doc(sales_order_item, delivery_note, item_table_mapper)
+ if location.sales_order_item:
+ sales_order_item = frappe.get_cached_doc('Sales Order Item', {'name':location.sales_order_item})
+ else:
+ sales_order_item = None
+
+ source_doc, table_mapper = [sales_order_item, item_table_mapper] if sales_order_item \
+ else [location, item_table_mapper_without_so]
+
+ dn_item = map_child_doc(source_doc, delivery_note, table_mapper)
if dn_item:
dn_item.warehouse = location.warehouse
@@ -258,17 +301,19 @@ def create_delivery_note(source_name, target_doc=None):
dn_item.batch_no = location.batch_no
dn_item.serial_no = location.serial_no
- update_delivery_note_item(sales_order_item, dn_item, delivery_note)
+ update_delivery_note_item(source_doc, dn_item, delivery_note)
set_delivery_note_missing_values(delivery_note)
delivery_note.pick_list = pick_list.name
+ delivery_note.customer = pick_list.customer if pick_list.customer else None
return delivery_note
@frappe.whitelist()
def create_stock_entry(pick_list):
pick_list = frappe.get_doc(json.loads(pick_list))
+ validate_item_locations(pick_list)
if stock_entry_exists(pick_list.get('name')):
return frappe.msgprint(_('Stock Entry has been already created against this Pick List'))
@@ -318,7 +363,7 @@ def get_pending_work_orders(doctype, txt, searchfield, start, page_length, filte
@frappe.whitelist()
def target_document_exists(pick_list_name, purpose):
- if purpose == 'Delivery against Sales Order':
+ if purpose == 'Delivery':
return frappe.db.exists('Delivery Note', {
'pick_list': pick_list_name
})
@@ -429,4 +474,4 @@ def update_common_item_properties(item, location):
item.material_request = location.material_request
item.serial_no = location.serial_no
item.batch_no = location.batch_no
- item.material_request_item = location.material_request_item
\ No newline at end of file
+ item.material_request_item = location.material_request_item
diff --git a/erpnext/stock/doctype/pick_list/test_pick_list.py b/erpnext/stock/doctype/pick_list/test_pick_list.py
index 6b4f73b140a..1b9ff41cc33 100644
--- a/erpnext/stock/doctype/pick_list/test_pick_list.py
+++ b/erpnext/stock/doctype/pick_list/test_pick_list.py
@@ -111,6 +111,7 @@ class TestPickList(unittest.TestCase):
stock_reconciliation = frappe.get_doc({
'doctype': 'Stock Reconciliation',
+ 'purpose': 'Stock Reconciliation',
'company': '_Test Company',
'items': [{
'item_code': '_Test Serialized Item',
diff --git a/erpnext/stock/doctype/pick_list_item/pick_list_item.json b/erpnext/stock/doctype/pick_list_item/pick_list_item.json
index c7a35df51f5..71fbf9a866a 100644
--- a/erpnext/stock/doctype/pick_list_item/pick_list_item.json
+++ b/erpnext/stock/doctype/pick_list_item/pick_list_item.json
@@ -1,4 +1,5 @@
{
+ "actions": [],
"creation": "2019-07-11 16:01:22.832885",
"doctype": "DocType",
"editable_grid": 1,
@@ -8,6 +9,7 @@
"item_name",
"column_break_2",
"description",
+ "item_group",
"section_break_5",
"warehouse",
"quantity_section",
@@ -120,7 +122,8 @@
"fieldtype": "Link",
"in_list_view": 1,
"label": "Item",
- "options": "Item"
+ "options": "Item",
+ "reqd": 1
},
{
"fieldname": "quantity_section",
@@ -166,10 +169,18 @@
"fieldtype": "Data",
"label": "Material Request Item",
"read_only": 1
+ },
+ {
+ "fetch_from": "item_code.item_group",
+ "fieldname": "item_group",
+ "fieldtype": "Data",
+ "label": "Item Group",
+ "read_only": 1
}
],
"istable": 1,
- "modified": "2019-08-29 21:28:39.539007",
+ "links": [],
+ "modified": "2020-03-13 19:08:21.995986",
"modified_by": "Administrator",
"module": "Stock",
"name": "Pick List Item",
diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js
index d5914f9b28d..27946586eaa 100644
--- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js
+++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js
@@ -25,7 +25,7 @@ frappe.ui.form.on("Purchase Receipt", {
frm.custom_make_buttons = {
'Stock Entry': 'Return',
- 'Purchase Invoice': 'Invoice'
+ 'Purchase Invoice': 'Purchase Invoice'
};
frm.set_query("expense_account", "items", function() {
@@ -101,12 +101,18 @@ erpnext.stock.PurchaseReceiptController = erpnext.buying.BuyingController.extend
if (this.frm.doc.docstatus == 0) {
this.frm.add_custom_button(__('Purchase Order'),
function () {
+ if (!me.frm.doc.supplier) {
+ frappe.throw({
+ title: __("Mandatory"),
+ message: __("Please Select a Supplier")
+ });
+ }
erpnext.utils.map_current_doc({
method: "erpnext.buying.doctype.purchase_order.purchase_order.make_purchase_receipt",
source_doctype: "Purchase Order",
target: me.frm,
setters: {
- supplier: me.frm.doc.supplier || undefined,
+ supplier: me.frm.doc.supplier,
},
get_query_filters: {
docstatus: 1,
diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json
index 63ef7ca8d82..6f657ab3d01 100755
--- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json
+++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json
@@ -1,5 +1,4 @@
{
- "actions": [],
"allow_import": 1,
"autoname": "naming_series:",
"creation": "2013-05-21 16:16:39",
@@ -47,6 +46,7 @@
"is_subcontracted",
"supplier_warehouse",
"items_section",
+ "scan_barcode",
"items",
"pricing_rule_details",
"pricing_rules",
@@ -58,9 +58,9 @@
"base_total",
"base_net_total",
"column_break_27",
+ "total_net_weight",
"total",
"net_total",
- "total_net_weight",
"taxes_charges_section",
"tax_category",
"shipping_col",
@@ -101,6 +101,7 @@
"bill_no",
"bill_date",
"more_info",
+ "project",
"status",
"amended_from",
"range",
@@ -1053,13 +1054,25 @@
"oldfieldtype": "Date",
"print_width": "100px",
"width": "100px"
+ },
+ {
+ "fieldname": "scan_barcode",
+ "fieldtype": "Data",
+ "label": "Scan Barcode"
+ },
+ {
+ "description": "Track this Purchase Receipt against any Project",
+ "fieldname": "project",
+ "fieldtype": "Link",
+ "label": "Project",
+ "options": "Project"
}
],
"icon": "fa fa-truck",
"idx": 261,
"is_submittable": 1,
"links": [],
- "modified": "2019-12-30 19:12:49.709711",
+ "modified": "2020-07-15 12:49:42.095297",
"modified_by": "Administrator",
"module": "Stock",
"name": "Purchase Receipt",
diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
index 3ec4b84ccee..be2453373e4 100644
--- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
+++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
@@ -49,8 +49,8 @@ class PurchaseReceipt(BuyingController):
'target_field': 'received_qty',
'target_parent_dt': 'Material Request',
'target_parent_field': 'per_received',
- 'target_ref_field': 'qty',
- 'source_field': 'qty',
+ 'target_ref_field': 'stock_qty',
+ 'source_field': 'stock_qty',
'percent_join_field': 'material_request'
}]
if cint(self.is_return):
@@ -195,6 +195,7 @@ class PurchaseReceipt(BuyingController):
# because updating ordered qty in bin depends upon updated ordered qty in PO
self.update_stock_ledger()
self.make_gl_entries_on_cancel()
+ self.delete_auto_created_batches()
def get_current_stock(self):
for d in self.get('supplied_items'):
@@ -222,6 +223,15 @@ class PurchaseReceipt(BuyingController):
if not stock_value_diff:
continue
+
+ # If PR is sub-contracted and fg item rate is zero
+ # in that case if account for shource and target warehouse are same,
+ # then GL entries should not be posted
+ if flt(stock_value_diff) == flt(d.rm_supp_cost) \
+ and warehouse_account.get(self.supplier_warehouse) \
+ and warehouse_account[d.warehouse]["account"] == warehouse_account[self.supplier_warehouse]["account"]:
+ continue
+
gl_entries.append(self.get_gl_dict({
"account": warehouse_account[d.warehouse]["account"],
"against": stock_rbnb,
@@ -231,16 +241,17 @@ class PurchaseReceipt(BuyingController):
}, warehouse_account[d.warehouse]["account_currency"], item=d))
# stock received but not billed
- stock_rbnb_currency = get_account_currency(stock_rbnb)
- gl_entries.append(self.get_gl_dict({
- "account": stock_rbnb,
- "against": warehouse_account[d.warehouse]["account"],
- "cost_center": d.cost_center,
- "remarks": self.get("remarks") or _("Accounting Entry for Stock"),
- "credit": flt(d.base_net_amount, d.precision("base_net_amount")),
- "credit_in_account_currency": flt(d.base_net_amount, d.precision("base_net_amount")) \
- if stock_rbnb_currency==self.company_currency else flt(d.net_amount, d.precision("net_amount"))
- }, stock_rbnb_currency, item=d))
+ if d.base_net_amount:
+ stock_rbnb_currency = get_account_currency(stock_rbnb)
+ gl_entries.append(self.get_gl_dict({
+ "account": stock_rbnb,
+ "against": warehouse_account[d.warehouse]["account"],
+ "cost_center": d.cost_center,
+ "remarks": self.get("remarks") or _("Accounting Entry for Stock"),
+ "credit": flt(d.base_net_amount, d.precision("base_net_amount")),
+ "credit_in_account_currency": flt(d.base_net_amount, d.precision("base_net_amount")) \
+ if stock_rbnb_currency==self.company_currency else flt(d.net_amount, d.precision("net_amount"))
+ }, stock_rbnb_currency, item=d))
negative_expense_to_be_booked += flt(d.item_tax_amount)
@@ -348,7 +359,7 @@ class PurchaseReceipt(BuyingController):
if warehouse_with_no_account:
frappe.msgprint(_("No accounting entries for the following warehouses") + ": \n" +
"\n".join(warehouse_with_no_account))
-
+
return process_gl_map(gl_entries)
def get_asset_gl_entry(self, gl_entries):
@@ -494,7 +505,7 @@ def make_purchase_invoice(source_name, target_doc=None):
def set_missing_values(source, target):
if len(target.get("items")) == 0:
- frappe.throw(_("All items have already been invoiced"))
+ frappe.throw(_("All items have already been Invoiced/Returned"))
doc = frappe.get_doc(target)
doc.ignore_pricing_rule = 1
@@ -504,11 +515,11 @@ def make_purchase_invoice(source_name, target_doc=None):
def update_item(source_doc, target_doc, source_parent):
target_doc.qty, returned_qty = get_pending_qty(source_doc)
- returned_qty_map[source_doc.item_code] = returned_qty
+ returned_qty_map[source_doc.name] = returned_qty
def get_pending_qty(item_row):
pending_qty = item_row.qty - invoiced_qty_map.get(item_row.name, 0)
- returned_qty = flt(returned_qty_map.get(item_row.item_code, 0))
+ returned_qty = flt(returned_qty_map.get(item_row.name, 0))
if returned_qty:
if returned_qty >= pending_qty:
pending_qty = 0
@@ -566,13 +577,12 @@ def get_invoiced_qty_map(purchase_receipt):
def get_returned_qty_map(purchase_receipt):
"""returns a map: {so_detail: returned_qty}"""
- returned_qty_map = frappe._dict(frappe.db.sql("""select pr_item.item_code, sum(abs(pr_item.qty)) as qty
+ returned_qty_map = frappe._dict(frappe.db.sql("""select pr_item.purchase_receipt_item, abs(pr_item.qty) as qty
from `tabPurchase Receipt Item` pr_item, `tabPurchase Receipt` pr
where pr.name = pr_item.parent
and pr.docstatus = 1
and pr.is_return = 1
and pr.return_against = %s
- group by pr_item.item_code
""", purchase_receipt))
return returned_qty_map
@@ -615,7 +625,7 @@ def get_item_account_wise_additional_cost(purchase_document):
if not landed_cost_vouchers:
return
-
+
item_account_wise_cost = {}
for lcv in landed_cost_vouchers:
diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
index c80b9bd04bb..c9cda37c40b 100644
--- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
+++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
@@ -3,10 +3,12 @@
from __future__ import unicode_literals
import unittest
+import json
import frappe, erpnext
import frappe.defaults
from frappe.utils import cint, flt, cstr, today, random_string
from erpnext.stock.doctype.purchase_receipt.purchase_receipt import make_purchase_invoice
+from erpnext.stock.doctype.item.test_item import create_item
from erpnext import set_perpetual_inventory
from erpnext.stock.doctype.serial_no.serial_no import SerialNoDuplicateError
from erpnext.accounts.doctype.account.test_account import get_inventory_account
@@ -51,6 +53,30 @@ class TestPurchaseReceipt(unittest.TestCase):
self.assertFalse(get_gl_entries("Purchase Receipt", pr.name))
+ def test_batched_serial_no_purchase(self):
+ item = frappe.db.exists("Item", {'item_name': 'Batched Serialized Item'})
+ if not item:
+ item = create_item("Batched Serialized Item")
+ item.has_batch_no = 1
+ item.create_new_batch = 1
+ item.has_serial_no = 1
+ item.batch_number_series = "BS-BATCH-.##"
+ item.serial_no_series = "BS-.####"
+ item.save()
+ else:
+ item = frappe.get_doc("Item", {'item_name': 'Batched Serialized Item'})
+
+ pr = make_purchase_receipt(item_code=item.name, qty=5, rate=500)
+
+ self.assertTrue(frappe.db.get_value('Batch', {'item': item.name, 'reference_name': pr.name}))
+
+ pr.load_from_db()
+ batch_no = pr.items[0].batch_no
+ pr.cancel()
+
+ self.assertFalse(frappe.db.get_value('Batch', {'item': item.name, 'reference_name': pr.name}))
+ self.assertFalse(frappe.db.get_all('Serial No', {'batch_no': batch_no}))
+
def test_purchase_receipt_gl_entry(self):
pr = make_purchase_receipt(company="_Test Company with perpetual inventory", warehouse = "Stores - TCP1", supplier_warehouse = "Work in Progress - TCP1", get_multiple_items = True, get_taxes_and_charges = True)
self.assertEqual(cint(erpnext.is_perpetual_inventory_enabled(pr.company)), 1)
@@ -96,6 +122,87 @@ class TestPurchaseReceipt(unittest.TestCase):
rm_supp_cost = sum([d.amount for d in pr.get("supplied_items")])
self.assertEqual(pr.get("items")[0].rm_supp_cost, flt(rm_supp_cost, 2))
+ def test_subcontracting_gle_fg_item_rate_zero(self):
+ from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
+ set_perpetual_inventory()
+ frappe.db.set_value("Buying Settings", None, "backflush_raw_materials_of_subcontract_based_on", "BOM")
+ make_stock_entry(item_code="_Test Item", target="Work In Progress - TCP1", qty=100, basic_rate=100, company="_Test Company with perpetual inventory")
+ make_stock_entry(item_code="_Test Item Home Desktop 100", target="Work In Progress - TCP1",
+ qty=100, basic_rate=100, company="_Test Company with perpetual inventory")
+ pr = make_purchase_receipt(item_code="_Test FG Item", qty=10, rate=0, is_subcontracted="Yes",
+ company="_Test Company with perpetual inventory", warehouse='Stores - TCP1', supplier_warehouse='Work In Progress - TCP1')
+
+ gl_entries = get_gl_entries("Purchase Receipt", pr.name)
+
+ self.assertFalse(gl_entries)
+
+ set_perpetual_inventory(0)
+
+ def test_subcontracting_over_receipt(self):
+ """
+ Behaviour: Raise multiple PRs against one PO that in total
+ receive more than the required qty in the PO.
+ Expected Result: Error Raised for Over Receipt against PO.
+ """
+ from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
+ from erpnext.buying.doctype.purchase_order.test_purchase_order import (update_backflush_based_on,
+ make_subcontracted_item, create_purchase_order)
+ from erpnext.buying.doctype.purchase_order.purchase_order import (make_purchase_receipt,
+ make_rm_stock_entry as make_subcontract_transfer_entry)
+
+ update_backflush_based_on("Material Transferred for Subcontract")
+ item_code = "_Test Subcontracted FG Item 1"
+ make_subcontracted_item(item_code=item_code)
+
+ po = create_purchase_order(item_code=item_code, qty=1,
+ is_subcontracted="Yes", supplier_warehouse="_Test Warehouse 1 - _TC")
+
+ #stock raw materials in a warehouse before transfer
+ make_stock_entry(target="_Test Warehouse - _TC",
+ item_code="_Test Item Home Desktop 100", qty=1, basic_rate=100)
+ make_stock_entry(target="_Test Warehouse - _TC",
+ item_code = "Test Extra Item 1", qty=1, basic_rate=100)
+ make_stock_entry(target="_Test Warehouse - _TC",
+ item_code = "_Test Item", qty=1, basic_rate=100)
+
+ rm_items = [
+ {
+ "item_code": item_code,
+ "rm_item_code": po.supplied_items[0].rm_item_code,
+ "item_name": "_Test Item",
+ "qty": po.supplied_items[0].required_qty,
+ "warehouse": "_Test Warehouse - _TC",
+ "stock_uom": "Nos"
+ },
+ {
+ "item_code": item_code,
+ "rm_item_code": po.supplied_items[1].rm_item_code,
+ "item_name": "Test Extra Item 1",
+ "qty": po.supplied_items[1].required_qty,
+ "warehouse": "_Test Warehouse - _TC",
+ "stock_uom": "Nos"
+ },
+ {
+ "item_code": item_code,
+ "rm_item_code": po.supplied_items[2].rm_item_code,
+ "item_name": "_Test Item Home Desktop 100",
+ "qty": po.supplied_items[2].required_qty,
+ "warehouse": "_Test Warehouse - _TC",
+ "stock_uom": "Nos"
+ }
+ ]
+ rm_item_string = json.dumps(rm_items)
+ se = frappe.get_doc(make_subcontract_transfer_entry(po.name, rm_item_string))
+ se.to_warehouse = "_Test Warehouse 1 - _TC"
+ se.save()
+ se.submit()
+
+ pr1 = make_purchase_receipt(po.name)
+ pr2 = make_purchase_receipt(po.name)
+
+ pr1.submit()
+ self.assertRaises(frappe.ValidationError, pr2.submit)
+
def test_serial_no_supplier(self):
pr = make_purchase_receipt(item_code="_Test Serialized Item With Series", qty=1)
self.assertEqual(frappe.db.get_value("Serial No", pr.get("items")[0].serial_no, "supplier"),
@@ -158,8 +265,11 @@ class TestPurchaseReceipt(unittest.TestCase):
def test_purchase_return_for_rejected_qty(self):
from erpnext.stock.doctype.warehouse.test_warehouse import get_warehouse
- rejected_warehouse=get_warehouse(company = "_Test Company with perpetual inventory", abbr = " - TCP1", warehouse_name = "_Test Rejected Warehouse").name
- print(rejected_warehouse)
+ rejected_warehouse="_Test Rejected Warehouse - TCP1"
+ if not frappe.db.exists("Warehouse", rejected_warehouse):
+ get_warehouse(company = "_Test Company with perpetual inventory",
+ abbr = " - TCP1", warehouse_name = "_Test Rejected Warehouse").name
+
pr = make_purchase_receipt(company="_Test Company with perpetual inventory", warehouse = "Stores - TCP1", supplier_warehouse = "Work in Progress - TCP1", received_qty=4, qty=2, rejected_warehouse=rejected_warehouse)
return_pr = make_purchase_receipt(company="_Test Company with perpetual inventory", warehouse = "Stores - TCP1", supplier_warehouse = "Work in Progress - TCP1", is_return=1, return_against=pr.name, received_qty = -4, qty=-2, rejected_warehouse=rejected_warehouse)
@@ -262,13 +372,39 @@ class TestPurchaseReceipt(unittest.TestCase):
self.assertEqual(pr2.per_billed, 80)
self.assertEqual(pr2.status, "To Bill")
+ def test_serial_no_against_purchase_receipt(self):
+ from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
+
+ item_code = "Test Manual Created Serial No"
+ if not frappe.db.exists("Item", item_code):
+ item = make_item(item_code, dict(has_serial_no=1))
+
+ serial_no = "12903812901"
+ pr_doc = make_purchase_receipt(item_code=item_code,
+ qty=1, serial_no = serial_no)
+
+ self.assertEqual(serial_no, frappe.db.get_value("Serial No",
+ {"purchase_document_type": "Purchase Receipt", "purchase_document_no": pr_doc.name}, "name"))
+
+ pr_doc.cancel()
+
+ item_code = "Test Auto Created Serial No"
+ if not frappe.db.exists("Item", item_code):
+ item = make_item(item_code, dict(has_serial_no=1, serial_no_series="KLJL.###"))
+
+ new_pr_doc = make_purchase_receipt(item_code=item_code, qty=1)
+
+ serial_no = get_serial_nos(new_pr_doc.items[0].serial_no)[0]
+ self.assertEqual(serial_no, frappe.db.get_value("Serial No",
+ {"purchase_document_type": "Purchase Receipt", "purchase_document_no": new_pr_doc.name}, "name"))
+
def test_not_accept_duplicate_serial_no(self):
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note
- item_code = frappe.db.get_value('Item', {'has_serial_no': 1, 'is_fixed_asset': 0})
+ item_code = frappe.db.get_value('Item', {'has_serial_no': 1, 'is_fixed_asset': 0, "has_batch_no": 0})
if not item_code:
- item = make_item("Test Serial Item 1", dict(has_serial_no=1))
+ item = make_item("Test Serial Item 1", dict(has_serial_no=1, has_batch_no=0))
item_code = item.name
serial_no = random_string(5)
@@ -301,8 +437,8 @@ class TestPurchaseReceipt(unittest.TestCase):
'accounts': [{
'company_name': '_Test Company',
'fixed_asset_account': '_Test Fixed Asset - _TC',
- 'accumulated_depreciation_account': 'Depreciation - _TC',
- 'depreciation_expense_account': 'Depreciation - _TC'
+ 'accumulated_depreciation_account': '_Test Accumulated Depreciations - _TC',
+ 'depreciation_expense_account': '_Test Depreciation - _TC'
}]
}).insert()
@@ -321,11 +457,35 @@ class TestPurchaseReceipt(unittest.TestCase):
location = frappe.db.get_value('Asset', assets[0].name, 'location')
self.assertEquals(location, "Test Location")
- def test_purchase_receipt_for_enable_allow_cost_center_in_entry_of_bs_account(self):
+ def test_purchase_return_with_submitted_asset(self):
+ from erpnext.stock.doctype.purchase_receipt.purchase_receipt import make_purchase_return
+
+ pr = make_purchase_receipt(item_code="Test Asset Item", qty=1)
+
+ asset = frappe.get_doc("Asset", {
+ 'purchase_receipt': pr.name
+ })
+ asset.available_for_use_date = frappe.utils.nowdate()
+ asset.gross_purchase_amount = 50.0
+ asset.append("finance_books", {
+ "expected_value_after_useful_life": 10,
+ "depreciation_method": "Straight Line",
+ "total_number_of_depreciations": 3,
+ "frequency_of_depreciation": 1,
+ "depreciation_start_date": frappe.utils.nowdate()
+ })
+ asset.submit()
+
+ pr_return = make_purchase_return(pr.name)
+ self.assertRaises(frappe.exceptions.ValidationError, pr_return.submit)
+
+ asset.load_from_db()
+ asset.cancel()
+
+ pr_return.submit()
+
+ def test_purchase_receipt_cost_center(self):
from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center
- accounts_settings = frappe.get_doc('Accounts Settings', 'Accounts Settings')
- accounts_settings.allow_cost_center_in_entry_of_bs_account = 1
- accounts_settings.save()
cost_center = "_Test Cost Center for BS Account - TCP1"
create_cost_center(cost_center_name="_Test Cost Center for BS Account", company="_Test Company with perpetual inventory")
@@ -353,14 +513,7 @@ class TestPurchaseReceipt(unittest.TestCase):
for i, gle in enumerate(gl_entries):
self.assertEqual(expected_values[gle.account]["cost_center"], gle.cost_center)
- accounts_settings.allow_cost_center_in_entry_of_bs_account = 0
- accounts_settings.save()
-
- def test_purchase_receipt_for_disable_allow_cost_center_in_entry_of_bs_account(self):
- accounts_settings = frappe.get_doc('Accounts Settings', 'Accounts Settings')
- accounts_settings.allow_cost_center_in_entry_of_bs_account = 0
- accounts_settings.save()
-
+ def test_purchase_receipt_cost_center_with_balance_sheet_account(self):
if not frappe.db.exists('Location', 'Test Location'):
frappe.get_doc({
'doctype': 'Location',
@@ -372,13 +525,14 @@ class TestPurchaseReceipt(unittest.TestCase):
gl_entries = get_gl_entries("Purchase Receipt", pr.name)
self.assertTrue(gl_entries)
+ cost_center = pr.get('items')[0].cost_center
expected_values = {
"Stock Received But Not Billed - TCP1": {
- "cost_center": None
+ "cost_center": cost_center
},
stock_in_hand_account: {
- "cost_center": None
+ "cost_center": cost_center
}
}
for i, gle in enumerate(gl_entries):
@@ -393,6 +547,7 @@ class TestPurchaseReceipt(unittest.TestCase):
pr1 = make_purchase_receipt(is_return=1, return_against=pr.name, qty=-1, do_not_submit=True)
pr1.items[0].purchase_order = po.name
pr1.items[0].purchase_order_item = po.items[0].name
+ pr1.items[0].purchase_receipt_item = pr.items[0].name
pr1.submit()
pi = make_purchase_invoice(pr.name)
@@ -416,12 +571,75 @@ class TestPurchaseReceipt(unittest.TestCase):
pi1.save()
pi1.submit()
- make_purchase_receipt(is_return=1, return_against=pr1.name, qty=-2)
+ pr2 = make_purchase_receipt(is_return=1, return_against=pr1.name, qty=-2, do_not_submit=True)
+ pr2.items[0].purchase_receipt_item = pr1.items[0].name
+ pr2.submit()
pi2 = make_purchase_invoice(pr1.name)
self.assertEquals(pi2.items[0].qty, 2)
self.assertEquals(pi2.items[1].qty, 1)
+ def test_subcontracted_pr_for_multi_transfer_batches(self):
+ from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
+ from erpnext.buying.doctype.purchase_order.purchase_order import make_rm_stock_entry, make_purchase_receipt
+ from erpnext.buying.doctype.purchase_order.test_purchase_order import (update_backflush_based_on,
+ create_purchase_order)
+
+ update_backflush_based_on("Material Transferred for Subcontract")
+ item_code = "_Test Subcontracted FG Item 3"
+
+ make_item('Sub Contracted Raw Material 3', {
+ 'is_stock_item': 1,
+ 'is_sub_contracted_item': 1,
+ 'has_batch_no': 1,
+ 'create_new_batch': 1
+ })
+
+ create_subcontracted_item(item_code=item_code, has_batch_no=1,
+ raw_materials=["Sub Contracted Raw Material 3"])
+
+ order_qty = 500
+ po = create_purchase_order(item_code=item_code, qty=order_qty,
+ is_subcontracted="Yes", supplier_warehouse="_Test Warehouse 1 - _TC")
+
+ ste1=make_stock_entry(target="_Test Warehouse - _TC",
+ item_code = "Sub Contracted Raw Material 3", qty=300, basic_rate=100)
+ ste2=make_stock_entry(target="_Test Warehouse - _TC",
+ item_code = "Sub Contracted Raw Material 3", qty=200, basic_rate=100)
+
+ transferred_batch = {
+ ste1.items[0].batch_no : 300,
+ ste2.items[0].batch_no : 200
+ }
+
+ rm_items = [
+ {"item_code":item_code,"rm_item_code":"Sub Contracted Raw Material 3","item_name":"_Test Item",
+ "qty":300,"warehouse":"_Test Warehouse - _TC", "stock_uom":"Nos"},
+ {"item_code":item_code,"rm_item_code":"Sub Contracted Raw Material 3","item_name":"_Test Item",
+ "qty":200,"warehouse":"_Test Warehouse - _TC", "stock_uom":"Nos"}
+ ]
+
+ rm_item_string = json.dumps(rm_items)
+ se = frappe.get_doc(make_rm_stock_entry(po.name, rm_item_string))
+ self.assertEqual(len(se.items), 2)
+ se.items[0].batch_no = ste1.items[0].batch_no
+ se.items[1].batch_no = ste2.items[0].batch_no
+ se.submit()
+
+ supplied_qty = frappe.db.get_value("Purchase Order Item Supplied",
+ {"parent": po.name, "rm_item_code": "Sub Contracted Raw Material 3"}, "supplied_qty")
+
+ self.assertEqual(supplied_qty, 500.00)
+
+ pr = make_purchase_receipt(po.name)
+ pr.save()
+ self.assertEqual(len(pr.supplied_items), 2)
+
+ for row in pr.supplied_items:
+ self.assertEqual(transferred_batch.get(row.batch_no), row.consumed_qty)
+
+ update_backflush_based_on("BOM")
+
def get_gl_entries(voucher_type, voucher_no):
return frappe.db.sql("""select account, debit, credit, cost_center
from `tabGL Entry` where voucher_type=%s and voucher_no=%s
@@ -532,7 +750,7 @@ def make_purchase_receipt(**args):
"received_qty": received_qty,
"rejected_qty": rejected_qty,
"rejected_warehouse": args.rejected_warehouse or "_Test Rejected Warehouse - _TC" if rejected_qty != 0 else "",
- "rate": args.rate or 50,
+ "rate": args.rate if args.rate != None else 50,
"conversion_factor": args.conversion_factor or 1.0,
"serial_no": args.serial_no,
"stock_uom": args.stock_uom or "_Test UOM",
@@ -557,6 +775,33 @@ def make_purchase_receipt(**args):
pr.submit()
return pr
+def create_subcontracted_item(**args):
+ from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom
+
+ args = frappe._dict(args)
+
+ if not frappe.db.exists('Item', args.item_code):
+ make_item(args.item_code, {
+ 'is_stock_item': 1,
+ 'is_sub_contracted_item': 1,
+ 'has_batch_no': args.get("has_batch_no") or 0
+ })
+
+ if not args.raw_materials:
+ if not frappe.db.exists('Item', "Test Extra Item 1"):
+ make_item("Test Extra Item 1", {
+ 'is_stock_item': 1,
+ })
+
+ if not frappe.db.exists('Item', "Test Extra Item 2"):
+ make_item("Test Extra Item 2", {
+ 'is_stock_item': 1,
+ })
+
+ args.raw_materials = ['_Test FG Item', 'Test Extra Item 1']
+
+ if not frappe.db.get_value('BOM', {'item': args.item_code}, 'name'):
+ make_bom(item = args.item_code, raw_materials = args.get("raw_materials"))
test_dependencies = ["BOM", "Item Price", "Location"]
test_records = frappe.get_test_records('Purchase Receipt')
diff --git a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json
index 16ec8db335c..3d46289d570 100644
--- a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json
+++ b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json
@@ -78,6 +78,7 @@
"stock_qty",
"purchase_order_item",
"material_request_item",
+ "purchase_receipt_item",
"section_break_45",
"allow_zero_valuation_rate",
"bom",
@@ -222,7 +223,6 @@
"oldfieldname": "uom",
"oldfieldtype": "Link",
"options": "UOM",
- "print_hide": 1,
"print_width": "100px",
"reqd": 1,
"width": "100px"
@@ -552,7 +552,7 @@
"fieldname": "batch_no",
"fieldtype": "Link",
"in_list_view": 1,
- "label": "Batch No!",
+ "label": "Batch No",
"no_copy": 1,
"oldfieldname": "batch_no",
"oldfieldtype": "Link",
@@ -800,8 +800,7 @@
{
"fieldname": "manufacturer_part_no",
"fieldtype": "Data",
- "label": "Manufacturer Part Number",
- "read_only": 1
+ "label": "Manufacturer Part Number"
},
{
"depends_on": "is_fixed_asset",
@@ -819,11 +818,21 @@
"label": "Asset Category",
"options": "Asset Category",
"read_only": 1
+ },
+ {
+ "fieldname": "purchase_receipt_item",
+ "fieldtype": "Data",
+ "hidden": 1,
+ "label": "Purchase Receipt Item",
+ "no_copy": 1,
+ "print_hide": 1,
+ "read_only": 1
}
],
"idx": 1,
"istable": 1,
- "modified": "2019-10-14 16:03:25.499557",
+ "links": [],
+ "modified": "2020-06-20 18:49:16.824489",
"modified_by": "Administrator",
"module": "Stock",
"name": "Purchase Receipt Item",
diff --git a/erpnext/stock/doctype/quality_inspection/quality_inspection.json b/erpnext/stock/doctype/quality_inspection/quality_inspection.json
index a9f3cd09ef5..b99e98b65c1 100644
--- a/erpnext/stock/doctype/quality_inspection/quality_inspection.json
+++ b/erpnext/stock/doctype/quality_inspection/quality_inspection.json
@@ -72,7 +72,8 @@
"fieldname": "reference_type",
"fieldtype": "Select",
"label": "Reference Type",
- "options": "\nPurchase Receipt\nPurchase Invoice\nDelivery Note\nSales Invoice\nStock Entry"
+ "options": "\nPurchase Receipt\nPurchase Invoice\nDelivery Note\nSales Invoice\nStock Entry",
+ "reqd": 1
},
{
"fieldname": "reference_name",
@@ -83,7 +84,8 @@
"label": "Reference Name",
"oldfieldname": "purchase_receipt_no",
"oldfieldtype": "Link",
- "options": "reference_type"
+ "options": "reference_type",
+ "reqd": 1
},
{
"fieldname": "section_break_7",
@@ -230,8 +232,10 @@
],
"icon": "fa fa-search",
"idx": 1,
+ "index_web_pages_for_search": 1,
"is_submittable": 1,
- "modified": "2019-07-12 12:07:23.153698",
+ "links": [],
+ "modified": "2020-09-12 16:11:31.910508",
"modified_by": "Administrator",
"module": "Stock",
"name": "Quality Inspection",
diff --git a/erpnext/stock/doctype/quality_inspection/quality_inspection.py b/erpnext/stock/doctype/quality_inspection/quality_inspection.py
index 37ab807cb7b..c3bb5141849 100644
--- a/erpnext/stock/doctype/quality_inspection/quality_inspection.py
+++ b/erpnext/stock/doctype/quality_inspection/quality_inspection.py
@@ -58,6 +58,8 @@ class QualityInspection(Document):
.format(parent_doc=self.reference_type, child_doc=doctype),
(quality_inspection, self.modified, self.reference_name, self.item_code))
+@frappe.whitelist()
+@frappe.validate_and_sanitize_search_inputs
def item_query(doctype, txt, searchfield, start, page_len, filters):
if filters.get("from"):
from frappe.desk.reportview import get_match_cond
@@ -86,6 +88,8 @@ def item_query(doctype, txt, searchfield, start, page_len, filters):
page_len = page_len, qi_condition = qi_condition),
{'parent': filters.get('parent'), 'txt': "%%%s%%" % txt})
+@frappe.whitelist()
+@frappe.validate_and_sanitize_search_inputs
def quality_inspection_query(doctype, txt, searchfield, start, page_len, filters):
return frappe.get_all('Quality Inspection',
limit_start=start,
@@ -118,4 +122,4 @@ def make_quality_inspection(source_name, target_doc=None):
}
}, target_doc, postprocess)
- return doc
\ No newline at end of file
+ return doc
diff --git a/erpnext/stock/doctype/quality_inspection_template/quality_inspection_template_dashboard.py b/erpnext/stock/doctype/quality_inspection_template/quality_inspection_template_dashboard.py
new file mode 100644
index 00000000000..db459575f3e
--- /dev/null
+++ b/erpnext/stock/doctype/quality_inspection_template/quality_inspection_template_dashboard.py
@@ -0,0 +1,12 @@
+from frappe import _
+
+def get_data():
+ return {
+ 'fieldname': 'quality_inspection_template',
+ 'transactions': [
+ {
+ 'label': _('Quality Inspection'),
+ 'items': ['Quality Inspection']
+ }
+ ]
+ }
\ No newline at end of file
diff --git a/erpnext/stock/doctype/serial_no/serial_no.json b/erpnext/stock/doctype/serial_no/serial_no.json
index 712aadc5254..b9427289449 100644
--- a/erpnext/stock/doctype/serial_no/serial_no.json
+++ b/erpnext/stock/doctype/serial_no/serial_no.json
@@ -1,4 +1,5 @@
{
+ "actions": [],
"allow_import": 1,
"allow_rename": 1,
"autoname": "field:serial_no",
@@ -6,6 +7,7 @@
"description": "Distinct unit of an Item",
"doctype": "DocType",
"document_type": "Setup",
+ "engine": "InnoDB",
"field_order": [
"details",
"column_break0",
@@ -40,7 +42,6 @@
"delivery_document_no",
"delivery_date",
"delivery_time",
- "is_cancelled",
"column_break5",
"customer",
"customer_name",
@@ -55,7 +56,8 @@
"warranty_period",
"more_info",
"serial_no_details",
- "company"
+ "company",
+ "status"
],
"fields": [
{
@@ -104,10 +106,11 @@
},
{
"fieldname": "batch_no",
- "fieldtype": "Data",
+ "fieldtype": "Link",
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Batch No",
+ "options": "Batch",
"read_only": 1
},
{
@@ -304,16 +307,6 @@
"no_copy": 1,
"read_only": 1
},
- {
- "fieldname": "is_cancelled",
- "fieldtype": "Select",
- "hidden": 1,
- "label": "Is Cancelled",
- "oldfieldname": "is_cancelled",
- "oldfieldtype": "Select",
- "options": "\nYes\nNo",
- "report_hide": 1
- },
{
"fieldname": "column_break5",
"fieldtype": "Column Break",
@@ -421,11 +414,20 @@
"remember_last_selected_value": 1,
"reqd": 1,
"search_index": 1
+ },
+ {
+ "fieldname": "status",
+ "fieldtype": "Select",
+ "in_standard_filter": 1,
+ "label": "Status",
+ "options": "\nActive\nInactive\nDelivered\nExpired",
+ "read_only": 1
}
],
"icon": "fa fa-barcode",
"idx": 1,
- "modified": "2019-08-07 17:28:32.243280",
+ "links": [],
+ "modified": "2020-07-22 15:53:50.900855",
"modified_by": "Administrator",
"module": "Stock",
"name": "Serial No",
diff --git a/erpnext/stock/doctype/serial_no/serial_no.py b/erpnext/stock/doctype/serial_no/serial_no.py
index 19eb398130c..f8885a91edc 100644
--- a/erpnext/stock/doctype/serial_no/serial_no.py
+++ b/erpnext/stock/doctype/serial_no/serial_no.py
@@ -29,13 +29,23 @@ class SerialNo(StockController):
self.via_stock_ledger = False
def validate(self):
- if self.get("__islocal") and self.warehouse:
+ if self.get("__islocal") and self.warehouse and not self.via_stock_ledger:
frappe.throw(_("New Serial No cannot have Warehouse. Warehouse must be set by Stock Entry or Purchase Receipt"), SerialNoCannotCreateDirectError)
self.set_maintenance_status()
self.validate_warehouse()
self.validate_item()
- self.on_stock_ledger_entry()
+ self.set_status()
+
+ def set_status(self):
+ if self.delivery_document_type:
+ self.status = "Delivered"
+ elif self.warranty_expiry_date and getdate(self.warranty_expiry_date) <= getdate(nowdate()):
+ self.status = "Expired"
+ elif not self.warehouse:
+ self.status = "Inactive"
+ else:
+ self.status = "Active"
def set_maintenance_status(self):
if not self.warranty_expiry_date and not self.amc_expiry_date:
@@ -68,7 +78,7 @@ class SerialNo(StockController):
"""
Validate whether serial no is required for this item
"""
- item = frappe.get_doc("Item", self.item_code)
+ item = frappe.get_cached_doc("Item", self.item_code)
if item.has_serial_no!=1:
frappe.throw(_("Item {0} is not setup for Serial Nos. Check Item master").format(self.item_code))
@@ -117,9 +127,9 @@ class SerialNo(StockController):
"warranty_expiry_date"):
self.set(fieldname, None)
- def get_last_sle(self):
+ def get_last_sle(self, serial_no=None):
entries = {}
- sle_dict = self.get_stock_ledger_entries()
+ sle_dict = self.get_stock_ledger_entries(serial_no)
if sle_dict:
if sle_dict.get("incoming", []):
entries["purchase_sle"] = sle_dict["incoming"][0]
@@ -132,13 +142,28 @@ class SerialNo(StockController):
return entries
- def get_stock_ledger_entries(self):
+ def get_stock_ledger_entries(self, serial_no=None):
sle_dict = {}
- for sle in frappe.db.sql("""select * from `tabStock Ledger Entry`
- where serial_no like %s and item_code=%s and ifnull(is_cancelled, 'No')='No'
- order by posting_date desc, posting_time desc, creation desc""",
- ("%%%s%%" % self.name, self.item_code), as_dict=1):
- if self.name.upper() in get_serial_nos(sle.serial_no):
+ if not serial_no:
+ serial_no = self.name
+
+ for sle in frappe.db.sql("""
+ SELECT voucher_type, voucher_no,
+ posting_date, posting_time, incoming_rate, actual_qty, serial_no
+ FROM
+ `tabStock Ledger Entry`
+ WHERE
+ item_code=%s AND company = %s AND ifnull(is_cancelled, 'No')='No'
+ AND (serial_no = %s
+ OR serial_no like %s
+ OR serial_no like %s
+ OR serial_no like %s
+ )
+ ORDER BY
+ posting_date desc, posting_time desc, creation desc""",
+ (self.item_code, self.company,
+ serial_no, serial_no+'\n%', '%\n'+serial_no, '%\n'+serial_no+'\n%'), as_dict=1):
+ if serial_no.upper() in get_serial_nos(sle.serial_no):
if cint(sle.actual_qty) > 0:
sle_dict.setdefault("incoming", []).append(sle)
else:
@@ -168,7 +193,7 @@ class SerialNo(StockController):
def after_rename(self, old, new, merge=False):
"""rename serial_no text fields"""
for dt in frappe.db.sql("""select parent from tabDocField
- where fieldname='serial_no' and fieldtype in ('Text', 'Small Text')"""):
+ where fieldname='serial_no' and fieldtype in ('Text', 'Small Text', 'Long Text')"""):
for item in frappe.db.sql("""select name, serial_no from `tab%s`
where serial_no like %s""" % (dt[0], frappe.db.escape('%' + old + '%'))):
@@ -178,12 +203,12 @@ class SerialNo(StockController):
where name=%s""" % (dt[0], '%s', '%s'),
('\n'.join(list(serial_nos)), item[0]))
- def on_stock_ledger_entry(self):
- if self.via_stock_ledger and not self.get("__islocal"):
- last_sle = self.get_last_sle()
- self.set_purchase_details(last_sle.get("purchase_sle"))
- self.set_sales_details(last_sle.get("delivery_sle"))
- self.set_maintenance_status()
+ def update_serial_no_reference(self, serial_no=None):
+ last_sle = self.get_last_sle(serial_no)
+ self.set_purchase_details(last_sle.get("purchase_sle"))
+ self.set_sales_details(last_sle.get("delivery_sle"))
+ self.set_maintenance_status()
+ self.set_status()
def process_serial_no(sle):
item_det = get_item_details(sle.item_code)
@@ -192,6 +217,7 @@ def process_serial_no(sle):
def validate_serial_no(sle, item_det):
serial_nos = get_serial_nos(sle.serial_no) if sle.serial_no else []
+ validate_material_transfer_entry(sle)
if item_det.has_serial_no==0:
if serial_nos:
@@ -211,7 +237,9 @@ def validate_serial_no(sle, item_det):
for serial_no in serial_nos:
if frappe.db.exists("Serial No", serial_no):
- sr = frappe.get_doc("Serial No", serial_no)
+ sr = frappe.db.get_value("Serial No", serial_no, ["name", "item_code", "batch_no", "sales_order",
+ "delivery_document_no", "delivery_document_type", "warehouse",
+ "purchase_document_no", "company"], as_dict=1)
if sr.item_code!=sle.item_code:
if not allow_serial_nos_with_different_item(serial_no, sle):
@@ -292,6 +320,19 @@ def validate_serial_no(sle, item_det):
frappe.throw(_("Cannot cancel {0} {1} because Serial No {2} does not belong to the warehouse {3}")
.format(sle.voucher_type, sle.voucher_no, serial_no, sle.warehouse))
+def validate_material_transfer_entry(sle_doc):
+ sle_doc.update({
+ "skip_update_serial_no": False,
+ "skip_serial_no_validaiton": False
+ })
+
+ if (sle_doc.voucher_type == "Stock Entry" and sle_doc.is_cancelled == "No" and
+ frappe.get_cached_value("Stock Entry", sle_doc.voucher_no, "purpose") == "Material Transfer"):
+ if sle_doc.actual_qty < 0:
+ sle_doc.skip_update_serial_no = True
+ else:
+ sle_doc.skip_serial_no_validaiton = True
+
def validate_so_serial_no(sr, sales_order,):
if not sr.sales_order or sr.sales_order!= sales_order:
frappe.throw(_("""Sales Order {0} has reservation for item {1}, you can
@@ -299,7 +340,8 @@ def validate_so_serial_no(sr, sales_order,):
be delivered""").format(sales_order, sr.item_code, sr.name))
def has_duplicate_serial_no(sn, sle):
- if sn.warehouse and sle.voucher_type != 'Stock Reconciliation':
+ if (sn.warehouse and not sle.skip_serial_no_validaiton
+ and sle.voucher_type != 'Stock Reconciliation'):
return True
if sn.company != sle.company:
@@ -324,7 +366,7 @@ def allow_serial_nos_with_different_item(sle_serial_no, sle):
"""
allow_serial_nos = False
if sle.voucher_type=="Stock Entry" and cint(sle.actual_qty) > 0:
- stock_entry = frappe.get_doc("Stock Entry", sle.voucher_no)
+ stock_entry = frappe.get_cached_doc("Stock Entry", sle.voucher_no)
if stock_entry.purpose in ("Repack", "Manufacture"):
for d in stock_entry.get("items"):
if d.serial_no and (d.s_warehouse if sle.is_cancelled=="No" else d.t_warehouse):
@@ -335,6 +377,7 @@ def allow_serial_nos_with_different_item(sle_serial_no, sle):
return allow_serial_nos
def update_serial_nos(sle, item_det):
+ if sle.skip_update_serial_no: return
if sle.is_cancelled == "No" and not sle.serial_no and cint(sle.actual_qty) > 0 \
and item_det.has_serial_no == 1 and item_det.serial_no_series:
serial_nos = get_auto_serial_nos(item_det.serial_no_series, sle.actual_qty)
@@ -354,21 +397,16 @@ def auto_make_serial_nos(args):
serial_nos = get_serial_nos(args.get('serial_no'))
created_numbers = []
for serial_no in serial_nos:
+ is_new = False
if frappe.db.exists("Serial No", serial_no):
- sr = frappe.get_doc("Serial No", serial_no)
- sr.via_stock_ledger = True
- sr.item_code = args.get('item_code')
- sr.warehouse = args.get('warehouse') if args.get('actual_qty', 0) > 0 else None
- sr.batch_no = args.get('batch_no')
- sr.location = args.get('location')
- sr.company = args.get('company')
- sr.supplier = args.get('supplier')
- if sr.sales_order and args.get('voucher_type') == "Stock Entry" \
- and not args.get('actual_qty', 0) > 0:
- sr.sales_order = None
- sr.save(ignore_permissions=True)
+ sr = frappe.get_cached_doc("Serial No", serial_no)
elif args.get('actual_qty', 0) > 0:
- created_numbers.append(make_serial_no(serial_no, args))
+ sr = frappe.new_doc("Serial No")
+ is_new = True
+
+ sr = update_args_for_serial_no(sr, serial_no, args, is_new=is_new)
+ if is_new:
+ created_numbers.append(sr.name)
form_links = list(map(lambda d: frappe.utils.get_link_to_form('Serial No', d), created_numbers))
if len(form_links) == 1:
@@ -382,34 +420,40 @@ def get_item_details(item_code):
from tabItem where name=%s""", item_code, as_dict=True)[0]
def get_serial_nos(serial_no):
+ if isinstance(serial_no, list):
+ return serial_no
+
return [s.strip() for s in cstr(serial_no).strip().upper().replace(',', '\n').split('\n')
if s.strip()]
-def make_serial_no(serial_no, args):
- sr = frappe.new_doc("Serial No")
- sr.warehouse = None
- sr.dont_update_if_missing.append("warehouse")
- sr.flags.ignore_permissions = True
- sr.serial_no = serial_no
- sr.item_code = args.get('item_code')
- sr.company = args.get('company')
- sr.batch_no = args.get('batch_no')
- sr.via_stock_ledger = args.get('via_stock_ledger') or True
- sr.asset = args.get('asset')
- sr.location = args.get('location')
-
+def update_args_for_serial_no(serial_no_doc, serial_no, args, is_new=False):
+ serial_no_doc.update({
+ "item_code": args.get("item_code"),
+ "company": args.get("company"),
+ "batch_no": args.get("batch_no"),
+ "via_stock_ledger": args.get("via_stock_ledger") or True,
+ "supplier": args.get("supplier"),
+ "location": args.get("location"),
+ "warehouse": (args.get("warehouse")
+ if args.get("actual_qty", 0) > 0 else None)
+ })
- if args.get('purchase_document_type'):
- sr.purchase_document_type = args.get('purchase_document_type')
- sr.purchase_document_no = args.get('purchase_document_no')
- sr.supplier = args.get('supplier')
+ if is_new:
+ serial_no_doc.serial_no = serial_no
- sr.insert()
- if args.get('warehouse'):
- sr.warehouse = args.get('warehouse')
- sr.save()
+ if (serial_no_doc.sales_order and args.get("voucher_type") == "Stock Entry"
+ and not args.get("actual_qty", 0) > 0):
+ serial_no_doc.sales_order = None
- return sr.name
+ serial_no_doc.validate_item()
+ serial_no_doc.update_serial_no_reference(serial_no)
+
+ if is_new:
+ serial_no_doc.db_insert()
+ else:
+ serial_no_doc.db_update()
+
+ return serial_no_doc
def update_serial_nos_after_submit(controller, parentfield):
stock_ledger_entries = frappe.db.sql("""select voucher_detail_no, serial_no, actual_qty, warehouse
@@ -474,11 +518,15 @@ def get_delivery_note_serial_no(item_code, qty, delivery_note):
return serial_nos
@frappe.whitelist()
-def auto_fetch_serial_number(qty, item_code, warehouse):
- serial_numbers = frappe.get_list("Serial No", filters={
+def auto_fetch_serial_number(qty, item_code, warehouse, batch_nos=None):
+ import json
+ filters = {
"item_code": item_code,
"warehouse": warehouse,
"delivery_document_no": "",
"sales_invoice": ""
- }, limit=qty, order_by="creation")
+ }
+ if batch_nos: filters["batch_no"] = ["in", json.loads(batch_nos)]
+
+ serial_numbers = frappe.get_list("Serial No", filters=filters, limit=qty, order_by="creation")
return [item['name'] for item in serial_numbers]
diff --git a/erpnext/stock/doctype/serial_no/serial_no_list.js b/erpnext/stock/doctype/serial_no/serial_no_list.js
index 5b1e312f68f..7526d1d8a5c 100644
--- a/erpnext/stock/doctype/serial_no/serial_no_list.js
+++ b/erpnext/stock/doctype/serial_no/serial_no_list.js
@@ -1,14 +1,14 @@
frappe.listview_settings['Serial No'] = {
- add_fields: ["is_cancelled", "item_code", "warehouse", "warranty_expiry_date", "delivery_document_type"],
+ add_fields: ["item_code", "warehouse", "warranty_expiry_date", "delivery_document_type"],
get_indicator: (doc) => {
- if (doc.is_cancelled) {
- return [__("Cancelled"), "red", "is_cancelled,=,Yes"];
- } else if (doc.delivery_document_type) {
- return [__("Delivered"), "green", "delivery_document_type,is,set|is_cancelled,=,No"];
+ if (doc.delivery_document_type) {
+ return [__("Delivered"), "green", "delivery_document_type,is,set"];
} else if (doc.warranty_expiry_date && frappe.datetime.get_diff(doc.warranty_expiry_date, frappe.datetime.nowdate()) <= 0) {
- return [__("Expired"), "red", "warranty_expiry_date,not in,|warranty_expiry_date,<=,Today|delivery_document_type,is,not set|is_cancelled,=,No"];
+ return [__("Expired"), "red", "warranty_expiry_date,not in,|warranty_expiry_date,<=,Today|delivery_document_type,is,not set"];
+ } else if (!doc.warehouse) {
+ return [__("Inactive"), "grey", "warehouse,is,not set"];
} else {
- return [__("Active"), "green", "delivery_document_type,is,not set|is_cancelled,=,No"];
+ return [__("Active"), "green", "delivery_document_type,is,not set"];
}
}
};
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js
index 6220b1e9135..2d32999c5c5 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.js
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.js
@@ -60,7 +60,8 @@ frappe.ui.form.on('Stock Entry', {
}
}
- if(item.s_warehouse) filters["warehouse"] = item.s_warehouse;
+ filters["warehouse"] = item.s_warehouse || item.t_warehouse;
+
return {
query : "erpnext.controllers.queries.get_batch_no",
filters: filters
@@ -219,8 +220,8 @@ frappe.ui.form.on('Stock Entry', {
},
get_query_filters: {
docstatus: 1,
- material_request_type: "Material Transfer",
- status: ['!=', 'Transferred']
+ material_request_type: ["in", ["Material Transfer", "Material Issue"]],
+ status: ["not in", ["Transferred", "Issued"]]
}
})
}, __("Get items from"));
@@ -302,10 +303,9 @@ frappe.ui.form.on('Stock Entry', {
method: "erpnext.stock.get_item_details.get_serial_no",
args: {"args": args},
callback: function(r) {
- if (!r.exe){
+ if (!r.exe && r.message){
frappe.model.set_value(cdt, cdn, "serial_no", r.message);
}
-
if (callback) {
callback();
}
@@ -424,9 +424,10 @@ frappe.ui.form.on('Stock Entry', {
item.amount = flt(item.basic_amount + flt(item.additional_cost),
precision("amount", item));
- item.valuation_rate = flt(flt(item.basic_rate)
- + (flt(item.additional_cost) / flt(item.transfer_qty)),
- precision("valuation_rate", item));
+ if (flt(item.transfer_qty)) {
+ item.valuation_rate = flt(flt(item.basic_rate) + (flt(item.additional_cost) / flt(item.transfer_qty)),
+ precision("valuation_rate", item));
+ }
}
refresh_field('items');
@@ -536,10 +537,15 @@ frappe.ui.form.on('Stock Entry Detail', {
if(r.message) {
var d = locals[cdt][cdn];
$.each(r.message, function(k, v) {
- frappe.model.set_value(cdt, cdn, k, v); // qty and it's subsequent fields weren't triggered
+ if (v) {
+ frappe.model.set_value(cdt, cdn, k, v); // qty and it's subsequent fields weren't triggered
+ }
});
refresh_field("items");
- erpnext.stock.select_batch_and_serial_no(frm, d);
+
+ if (!d.serial_no) {
+ erpnext.stock.select_batch_and_serial_no(frm, d);
+ }
}
}
});
@@ -785,39 +791,17 @@ erpnext.stock.StockEntry = erpnext.stock.StockController.extend({
if(!row.t_warehouse) row.t_warehouse = this.frm.doc.to_warehouse;
},
- source_mandatory: ["Material Issue", "Material Transfer", "Send to Subcontractor",
- "Material Transfer for Manufacture", "Send to Warehouse", "Receive at Warehouse"],
- target_mandatory: ["Material Receipt", "Material Transfer", "Send to Subcontractor",
- "Material Transfer for Manufacture", "Send to Warehouse", "Receive at Warehouse"],
-
from_warehouse: function(doc) {
- var me = this;
- this.set_warehouse_if_different("s_warehouse", doc.from_warehouse, function(row) {
- return me.source_mandatory.indexOf(me.frm.doc.purpose)!==-1;
- });
+ this.set_warehouse_in_children(doc.items, "s_warehouse", doc.from_warehouse);
},
to_warehouse: function(doc) {
- var me = this;
- this.set_warehouse_if_different("t_warehouse", doc.to_warehouse, function(row) {
- return me.target_mandatory.indexOf(me.frm.doc.purpose)!==-1;
- });
+ this.set_warehouse_in_children(doc.items, "t_warehouse", doc.to_warehouse);
},
- set_warehouse_if_different: function(fieldname, value, condition) {
- var changed = false;
- for (var i=0, l=(this.frm.doc.items || []).length; i
{
}
}
- if(item && item.has_serial_no
- && frm.doc.purpose === 'Material Receipt') {
- return;
- }
+ if(item && !item.has_serial_no && !item.has_batch_no) return;
+ if (frm.doc.purpose === 'Material Receipt') return;
frappe.require("assets/erpnext/js/utils/serial_no_batch_selector.js", function() {
new erpnext.SerialNoBatchSelector({
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py
index 3f62d185cba..86e4b3f8b3d 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.py
@@ -50,6 +50,7 @@ class StockEntry(StockController):
self.validate_posting_time()
self.validate_purpose()
self.validate_item()
+ self.validate_customer_provided_item()
self.validate_qty()
self.set_transfer_qty()
self.validate_uom_is_integer("uom", "qty")
@@ -110,6 +111,7 @@ class StockEntry(StockController):
self.update_cost_in_project()
self.update_transferred_qty()
self.update_quality_inspection()
+ self.delete_auto_created_batches()
def set_job_card_data(self):
if self.job_card and not self.work_order:
@@ -176,6 +178,10 @@ class StockEntry(StockController):
stock_items = self.get_stock_items()
serialized_items = self.get_serialized_items()
for item in self.get("items"):
+ if flt(item.qty) and flt(item.qty) < 0:
+ frappe.throw(_("Row {0}: The item {1}, quantity must be positive number")
+ .format(item.idx, frappe.bold(item.item_code)))
+
if item.item_code not in stock_items:
frappe.throw(_("{0} is not a stock Item").format(item.item_code))
@@ -190,7 +196,8 @@ class StockEntry(StockController):
item.set(f, item_details.get(f))
if not item.transfer_qty and item.qty:
- item.transfer_qty = item.qty * item.conversion_factor
+ item.transfer_qty = flt(flt(item.qty) * flt(item.conversion_factor),
+ self.precision("transfer_qty", item))
if (self.purpose in ("Material Transfer", "Material Transfer for Manufacture")
and not item.serial_no
@@ -198,10 +205,6 @@ class StockEntry(StockController):
frappe.throw(_("Row #{0}: Please specify Serial No for Item {1}").format(item.idx, item.item_code),
frappe.MandatoryError)
- #Customer Provided parts will have zero valuation rate
- if frappe.db.get_value('Item', item.item_code, 'is_customer_provided_item'):
- item.allow_zero_valuation_rate = 1
-
def validate_qty(self):
manufacture_purpose = ["Manufacture", "Material Consumption for Manufacture"]
@@ -293,13 +296,8 @@ class StockEntry(StockController):
if validate_for_manufacture:
if d.bom_no:
d.s_warehouse = None
-
if not d.t_warehouse:
frappe.throw(_("Target warehouse is mandatory for row {0}").format(d.idx))
-
- elif self.pro_doc and (cstr(d.t_warehouse) != self.pro_doc.fg_warehouse and cstr(d.t_warehouse) != self.pro_doc.scrap_warehouse):
- frappe.throw(_("Target warehouse in row {0} must be same as Work Order").format(d.idx))
-
else:
d.t_warehouse = None
if not d.s_warehouse:
@@ -363,6 +361,9 @@ class StockEntry(StockController):
+ self.work_order + ":" + ", ".join(other_ste), DuplicateEntryForWorkOrderError)
def set_incoming_rate(self):
+ if self.purpose == "Repack":
+ self.set_basic_rate_for_finished_goods()
+
for d in self.items:
if d.s_warehouse:
args = self.get_args_for_incoming_rate(d)
@@ -470,25 +471,36 @@ class StockEntry(StockController):
"qty": item.s_warehouse and -1*flt(item.transfer_qty) or flt(item.transfer_qty),
"serial_no": item.serial_no,
"voucher_type": self.doctype,
- "voucher_no": item.name,
+ "voucher_no": self.name,
"company": self.company,
"allow_zero_valuation": item.allow_zero_valuation_rate,
})
- def set_basic_rate_for_finished_goods(self, raw_material_cost, scrap_material_cost):
+ def set_basic_rate_for_finished_goods(self, raw_material_cost=0, scrap_material_cost=0):
+ total_fg_qty = 0
+ if not raw_material_cost and self.get("items"):
+ raw_material_cost = sum([flt(row.basic_amount) for row in self.items
+ if row.s_warehouse and not row.t_warehouse])
+
+ total_fg_qty = sum([flt(row.qty) for row in self.items
+ if row.t_warehouse and not row.s_warehouse])
+
if self.purpose in ["Manufacture", "Repack"]:
for d in self.get("items"):
if (d.transfer_qty and (d.bom_no or d.t_warehouse)
and (getattr(self, "pro_doc", frappe._dict()).scrap_warehouse != d.t_warehouse)):
- if self.work_order \
- and frappe.db.get_single_value("Manufacturing Settings", "material_consumption"):
+ if (self.work_order and self.purpose == "Manufacture"
+ and frappe.db.get_single_value("Manufacturing Settings", "material_consumption")):
bom_items = self.get_bom_raw_materials(d.transfer_qty)
raw_material_cost = sum([flt(row.qty)*flt(row.rate) for row in bom_items.values()])
- if raw_material_cost:
+ if raw_material_cost and self.purpose == "Manufacture":
d.basic_rate = flt((raw_material_cost - scrap_material_cost) / flt(d.transfer_qty), d.precision("basic_rate"))
d.basic_amount = flt((raw_material_cost - scrap_material_cost), d.precision("basic_amount"))
+ elif self.purpose == "Repack" and total_fg_qty and not d.set_basic_rate_manually:
+ d.basic_rate = flt(raw_material_cost) / flt(total_fg_qty)
+ d.basic_amount = d.basic_rate * flt(d.qty)
def distribute_additional_costs(self):
if self.purpose == "Material Issue":
@@ -544,8 +556,9 @@ class StockEntry(StockController):
qty_allowance = flt(frappe.db.get_single_value("Buying Settings",
"over_transfer_allowance"))
- if (self.purpose == "Send to Subcontractor" and self.purchase_order and
- backflush_raw_materials_based_on == 'BOM'):
+ if not (self.purpose == "Send to Subcontractor" and self.purchase_order): return
+
+ if (backflush_raw_materials_based_on == 'BOM'):
purchase_order = frappe.get_doc("Purchase Order", self.purchase_order)
for se_item in self.items:
item_code = se_item.original_item or se_item.item_code
@@ -560,9 +573,7 @@ class StockEntry(StockController):
{"parent": self.purchase_order, "item_code": se_item.subcontracted_item},
"bom")
- allow_alternative_item = frappe.get_value("BOM", bom_no, "allow_alternative_item")
-
- if allow_alternative_item:
+ if se_item.allow_alternative_item:
original_item_code = frappe.get_value("Item Alternative", {"alternative_item_code": item_code}, "item_code")
required_qty = sum([flt(d.required_qty) for d in purchase_order.supplied_items \
@@ -584,6 +595,11 @@ class StockEntry(StockController):
if flt(total_supplied, precision) > flt(total_allowed, precision):
frappe.throw(_("Row {0}# Item {1} cannot be transferred more than {2} against Purchase Order {3}")
.format(se_item.idx, se_item.item_code, total_allowed, self.purchase_order))
+ elif backflush_raw_materials_based_on == "Material Transferred for Subcontract":
+ for row in self.items:
+ if not row.subcontracted_item:
+ frappe.throw(_("Row {0}: Subcontracted Item is mandatory for the raw material {1}")
+ .format(row.idx, frappe.bold(row.item_code)))
def validate_bom(self):
for d in self.get('items'):
@@ -725,7 +741,7 @@ class StockEntry(StockController):
def get_item_details(self, args=None, for_update=False):
item = frappe.db.sql("""select i.name, i.stock_uom, i.description, i.image, i.item_name, i.item_group,
- i.has_batch_no, i.sample_quantity, i.has_serial_no,
+ i.has_batch_no, i.sample_quantity, i.has_serial_no, i.allow_alternative_item,
id.expense_account, id.buying_cost_center
from `tabItem` i LEFT JOIN `tabItem Default` id ON i.name=id.parent and id.company=%s
where i.name=%s
@@ -759,6 +775,9 @@ class StockEntry(StockController):
'sample_quantity' : item.sample_quantity
})
+ if self.purpose == 'Send to Subcontractor':
+ ret["allow_alternative_item"] = item.allow_alternative_item
+
# update uom
if args.get("uom") and for_update:
ret.update(get_uom_details(args.get('item_code'), args.get('uom'), args.get('qty')))
@@ -784,6 +803,13 @@ class StockEntry(StockController):
ret.get('has_batch_no') and not args.get('batch_no')):
args.batch_no = get_batch_no(args['item_code'], args['s_warehouse'], args['qty'])
+ if self.purpose == "Send to Subcontractor" and self.get("purchase_order") and args.get('item_code'):
+ subcontract_items = frappe.get_all("Purchase Order Item Supplied",
+ {"parent": self.purchase_order, "rm_item_code": args.get('item_code')}, "main_item_code")
+
+ if subcontract_items and len(subcontract_items) == 1:
+ ret["subcontracted_item"] = subcontract_items[0].main_item_code
+
return ret
def set_items_for_stock_in(self):
@@ -865,14 +891,6 @@ class StockEntry(StockController):
self.add_to_stock_entry_detail(item_dict)
- if self.purpose != "Send to Subcontractor" and self.purpose in ["Manufacture", "Repack"]:
- scrap_item_dict = self.get_bom_scrap_material(self.fg_completed_qty)
- for item in itervalues(scrap_item_dict):
- if self.pro_doc and self.pro_doc.scrap_warehouse:
- item["to_warehouse"] = self.pro_doc.scrap_warehouse
-
- self.add_to_stock_entry_detail(scrap_item_dict, bom_no=self.bom_no)
-
# fetch the serial_no of the first stock entry for the second stock entry
if self.work_order and self.purpose == "Manufacture":
self.set_serial_nos(self.work_order)
@@ -883,9 +901,20 @@ class StockEntry(StockController):
if self.purpose in ("Manufacture", "Repack"):
self.load_items_from_bom()
+ self.set_scrap_items()
self.set_actual_qty()
self.calculate_rate_and_amount(raise_error_if_no_rate=False)
+ def set_scrap_items(self):
+ if self.purpose != "Send to Subcontractor" and self.purpose in ["Manufacture", "Repack"]:
+ scrap_item_dict = self.get_bom_scrap_material(self.fg_completed_qty)
+ for item in itervalues(scrap_item_dict):
+ item.idx = ''
+ if self.pro_doc and self.pro_doc.scrap_warehouse:
+ item["to_warehouse"] = self.pro_doc.scrap_warehouse
+
+ self.add_to_stock_entry_detail(scrap_item_dict, bom_no=self.bom_no)
+
def set_work_order_details(self):
if not getattr(self, "pro_doc", None):
self.pro_doc = frappe._dict()
@@ -1055,7 +1084,7 @@ class StockEntry(StockController):
req_qty_each = flt(req_qty / manufacturing_qty)
consumed_qty = flt(req_items[0].consumed_qty)
- if trans_qty and manufacturing_qty >= (produced_qty + flt(self.fg_completed_qty)):
+ if trans_qty and manufacturing_qty > (produced_qty + flt(self.fg_completed_qty)):
if qty >= req_qty:
qty = (req_qty/trans_qty) * flt(self.fg_completed_qty)
else:
@@ -1221,9 +1250,15 @@ class StockEntry(StockController):
#Update Supplied Qty in PO Supplied Items
frappe.db.sql("""UPDATE `tabPurchase Order Item Supplied` pos
- SET pos.supplied_qty = (SELECT ifnull(sum(transfer_qty), 0) FROM `tabStock Entry Detail` sed
- WHERE pos.name = sed.po_detail and sed.docstatus = 1)
- WHERE pos.docstatus = 1 and pos.parent = %s""", self.purchase_order)
+ SET
+ pos.supplied_qty = IFNULL((SELECT ifnull(sum(transfer_qty), 0)
+ FROM
+ `tabStock Entry Detail` sed, `tabStock Entry` se
+ WHERE
+ (pos.name = sed.po_detail OR sed.subcontracted_item = pos.main_item_code)
+ AND sed.docstatus = 1 AND se.name = sed.parent and se.purchase_order = %(po)s
+ ), 0)
+ WHERE pos.docstatus = 1 and pos.parent = %(po)s""", {"po": self.purchase_order})
#Update reserved sub contracted quantity in bin based on Supplied Item Details and
for d in self.get("items"):
diff --git a/erpnext/stock/doctype/stock_entry/test_stock_entry.py b/erpnext/stock/doctype/stock_entry/test_stock_entry.py
index ee5f2370987..84f535912d4 100644
--- a/erpnext/stock/doctype/stock_entry/test_stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/test_stock_entry.py
@@ -16,6 +16,7 @@ from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
from erpnext.accounts.doctype.account.test_account import get_inventory_account
from erpnext.stock.doctype.stock_entry.stock_entry import move_sample_to_retention_warehouse, make_stock_in_entry
from erpnext.stock.doctype.stock_reconciliation.stock_reconciliation import OpeningEntryAccountError
+from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
from six import iteritems
def get_sle(**args):
@@ -483,6 +484,100 @@ class TestStockEntry(unittest.TestCase):
serial_no = get_serial_nos(se.get("items")[0].serial_no)[0]
self.assertFalse(frappe.db.get_value("Serial No", serial_no, "warehouse"))
+ def test_serial_batch_item_stock_entry(self):
+ """
+ Behaviour: 1) Submit Stock Entry (Receipt) with Serial & Batched Item
+ 2) Cancel same Stock Entry
+ Expected Result: 1) Batch is created with Reference in Serial No
+ 2) Batch is deleted and Serial No is Inactive
+ """
+ from erpnext.stock.doctype.batch.batch import get_batch_qty
+
+ item = frappe.db.exists("Item", {'item_name': 'Batched and Serialised Item'})
+ if not item:
+ item = create_item("Batched and Serialised Item")
+ item.has_batch_no = 1
+ item.create_new_batch = 1
+ item.has_serial_no = 1
+ item.batch_number_series = "B-BATCH-.##"
+ item.serial_no_series = "S-.####"
+ item.save()
+ else:
+ item = frappe.get_doc("Item", {'item_name': 'Batched and Serialised Item'})
+
+ se = make_stock_entry(item_code=item.item_code, target="_Test Warehouse - _TC", qty=1, basic_rate=100)
+ batch_no = se.items[0].batch_no
+ serial_no = get_serial_nos(se.items[0].serial_no)[0]
+ batch_qty = get_batch_qty(batch_no, "_Test Warehouse - _TC", item.item_code)
+
+ batch_in_serial_no = frappe.db.get_value("Serial No", serial_no, "batch_no")
+ self.assertEqual(batch_in_serial_no, batch_no)
+
+ self.assertEqual(batch_qty, 1)
+
+ se.cancel()
+
+ batch_in_serial_no = frappe.db.get_value("Serial No", serial_no, "batch_no")
+ self.assertEqual(batch_in_serial_no, None)
+
+ self.assertEqual(frappe.db.get_value("Serial No", serial_no, "status"), "Inactive")
+ self.assertEqual(frappe.db.exists("Batch", batch_no), None)
+
+ def test_serial_batch_item_qty_deduction(self):
+ """
+ Behaviour: Create 2 Stock Entries, both adding Serial Nos to same batch
+ Expected Result: 1) Cancelling first Stock Entry (origin transaction of created batch)
+ should throw a LinkExistsError
+ 2) Cancelling second Stock Entry should make Serial Nos that are, linked to mentioned batch
+ and in that transaction only, Inactive.
+ """
+ from erpnext.stock.doctype.batch.batch import get_batch_qty
+
+ item = frappe.db.exists("Item", {'item_name': 'Batched and Serialised Item'})
+ if not item:
+ item = create_item("Batched and Serialised Item")
+ item.has_batch_no = 1
+ item.create_new_batch = 1
+ item.has_serial_no = 1
+ item.batch_number_series = "B-BATCH-.##"
+ item.serial_no_series = "S-.####"
+ item.save()
+ else:
+ item = frappe.get_doc("Item", {'item_name': 'Batched and Serialised Item'})
+
+ se1 = make_stock_entry(item_code=item.item_code, target="_Test Warehouse - _TC", qty=1, basic_rate=100)
+ batch_no = se1.items[0].batch_no
+ serial_no1 = get_serial_nos(se1.items[0].serial_no)[0]
+
+ # Check Source (Origin) Document of Batch
+ self.assertEqual(frappe.db.get_value("Batch", batch_no, "reference_name"), se1.name)
+
+ se2 = make_stock_entry(item_code=item.item_code, target="_Test Warehouse - _TC", qty=1, basic_rate=100,
+ batch_no=batch_no)
+ serial_no2 = get_serial_nos(se2.items[0].serial_no)[0]
+
+ batch_qty = get_batch_qty(batch_no, "_Test Warehouse - _TC", item.item_code)
+ self.assertEqual(batch_qty, 2)
+ frappe.db.commit()
+
+ # Cancelling Origin Document of Batch
+ self.assertRaises(frappe.LinkExistsError, se1.cancel)
+ frappe.db.rollback()
+
+ se2.cancel()
+
+ # Check decrease in Batch Qty
+ batch_qty = get_batch_qty(batch_no, "_Test Warehouse - _TC", item.item_code)
+ self.assertEqual(batch_qty, 1)
+
+ # Check if Serial No from Stock Entry 1 is intact
+ self.assertEqual(frappe.db.get_value("Serial No", serial_no1, "batch_no"), batch_no)
+ self.assertEqual(frappe.db.get_value("Serial No", serial_no1, "status"), "Active")
+
+ # Check if Serial No from Stock Entry 2 is Unlinked and Inactive
+ self.assertEqual(frappe.db.get_value("Serial No", serial_no2, "batch_no"), None)
+ self.assertEqual(frappe.db.get_value("Serial No", serial_no2, "status"), "Inactive")
+
def test_warehouse_company_validation(self):
company = frappe.db.get_value('Warehouse', '_Test Warehouse 2 - _TC1', 'company')
set_perpetual_inventory(0, company)
@@ -744,7 +839,7 @@ class TestStockEntry(unittest.TestCase):
def test_customer_provided_parts_se(self):
create_item('CUST-0987', is_customer_provided_item = 1, customer = '_Test Customer', is_purchase_item = 0)
- se = make_stock_entry(item_code='CUST-0987', purporse = 'Material Receipt', qty=4, to_warehouse = "_Test Warehouse - _TC")
+ se = make_stock_entry(item_code='CUST-0987', purpose = 'Material Receipt', qty=4, to_warehouse = "_Test Warehouse - _TC")
self.assertEqual(se.get("items")[0].allow_zero_valuation_rate, 1)
self.assertEqual(se.get("items")[0].amount, 0)
diff --git a/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json b/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json
index d86e68b7222..9d397df8bcd 100644
--- a/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json
+++ b/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json
@@ -16,12 +16,14 @@
"item_group",
"col_break2",
"item_name",
+ "subcontracted_item",
"section_break_8",
"description",
"column_break_10",
"image",
"image_view",
"quantity_and_rate",
+ "set_basic_rate_manually",
"qty",
"basic_rate",
"basic_amount",
@@ -55,7 +57,6 @@
"material_request",
"material_request_item",
"original_item",
- "subcontracted_item",
"reference_section",
"against_stock_entry",
"ste_detail",
@@ -413,6 +414,7 @@
"read_only": 1
},
{
+ "depends_on": "eval:parent.purpose == 'Send to Subcontractor'",
"fieldname": "subcontracted_item",
"fieldtype": "Link",
"label": "Subcontracted Item",
@@ -479,8 +481,7 @@
"fieldname": "project",
"fieldtype": "Link",
"label": "Project",
- "options": "Project",
- "read_only": 1
+ "options": "Project"
},
{
"fieldname": "po_detail",
@@ -490,11 +491,18 @@
"no_copy": 1,
"print_hide": 1,
"read_only": 1
+ },
+ {
+ "default": "0",
+ "depends_on": "eval:parent.purpose===\"Repack\" && doc.t_warehouse",
+ "fieldname": "set_basic_rate_manually",
+ "fieldtype": "Check",
+ "label": "Set Basic Rate Manually"
}
],
"idx": 1,
"istable": 1,
- "modified": "2019-08-20 14:01:02.319754",
+ "modified": "2020-09-04 12:12:35.668198",
"modified_by": "Administrator",
"module": "Stock",
"name": "Stock Entry Detail",
diff --git a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.json b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.json
index c9eba71b0d0..c03eb79eeca 100644
--- a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.json
+++ b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.json
@@ -240,6 +240,7 @@
"options": "Company",
"print_width": "150px",
"read_only": 1,
+ "search_index": 1,
"width": "150px"
},
{
@@ -274,7 +275,7 @@
"icon": "fa fa-list",
"idx": 1,
"in_create": 1,
- "modified": "2019-11-27 12:17:31.522675",
+ "modified": "2020-02-25 22:53:33.504681",
"modified_by": "Administrator",
"module": "Stock",
"name": "Stock Ledger Entry",
diff --git a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py
index 5fe89d6e227..837dfd5bd30 100644
--- a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py
+++ b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py
@@ -64,7 +64,7 @@ class StockLedgerEntry(Document):
frappe.throw(_("Actual Qty is mandatory"))
def validate_item(self):
- item_det = frappe.db.sql("""select name, has_batch_no, docstatus,
+ item_det = frappe.db.sql("""select name, item_name, has_batch_no, docstatus,
is_stock_item, has_variants, stock_uom, create_new_batch
from tabItem where name=%s""", self.item_code, as_dict=True)
@@ -79,10 +79,11 @@ class StockLedgerEntry(Document):
# check if batch number is required
if self.voucher_type != 'Stock Reconciliation':
if item_det.has_batch_no ==1:
+ batch_item = self.item_code if self.item_code == item_det.item_name else self.item_code + ":" + item_det.item_name
if not self.batch_no:
- frappe.throw(_("Batch number is mandatory for Item {0}").format(self.item_code))
+ frappe.throw(_("Batch number is mandatory for Item {0}").format(batch_item))
elif not frappe.db.get_value("Batch",{"item": self.item_code, "name": self.batch_no}):
- frappe.throw(_("{0} is not a valid Batch Number for Item {1}").format(self.batch_no, self.item_code))
+ frappe.throw(_("{0} is not a valid Batch Number for Item {1}").format(self.batch_no, batch_item))
elif item_det.has_batch_no ==0 and self.batch_no and self.is_cancelled == "No":
frappe.throw(_("The Item {0} cannot have Batch").format(self.item_code))
diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js
index 1791978a068..0475ea7a2ec 100644
--- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js
+++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js
@@ -74,6 +74,20 @@ frappe.ui.form.on("Stock Reconciliation", {
, __("Get Items"), __("Update"));
},
+ posting_date: function(frm) {
+ frm.trigger("set_valuation_rate_and_qty_for_all_items");
+ },
+
+ posting_time: function(frm) {
+ frm.trigger("set_valuation_rate_and_qty_for_all_items");
+ },
+
+ set_valuation_rate_and_qty_for_all_items: function(frm) {
+ frm.doc.items.forEach(row => {
+ frm.events.set_valuation_rate_and_qty(frm, row.doctype, row.name);
+ });
+ },
+
set_valuation_rate_and_qty: function(frm, cdt, cdn) {
var d = frappe.model.get_doc(cdt, cdn);
diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.json b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.json
index 7f4efba33f8..b7d1497319f 100644
--- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.json
+++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.json
@@ -4,6 +4,7 @@
"description": "This tool helps you to update or fix the quantity and valuation of stock in the system. It is typically used to synchronise the system values and what actually exists in your warehouses.",
"doctype": "DocType",
"document_type": "Document",
+ "engine": "InnoDB",
"field_order": [
"naming_series",
"company",
@@ -44,11 +45,11 @@
"reqd": 1
},
{
- "default": "Stock Reconciliation",
"fieldname": "purpose",
"fieldtype": "Select",
"label": "Purpose",
- "options": "Opening Stock\nStock Reconciliation"
+ "options": "\nOpening Stock\nStock Reconciliation",
+ "reqd": 1
},
{
"fieldname": "col1",
@@ -153,7 +154,7 @@
"idx": 1,
"is_submittable": 1,
"max_attachments": 1,
- "modified": "2019-05-26 09:03:09.542141",
+ "modified": "2020-04-08 17:02:47.196206",
"modified_by": "Administrator",
"module": "Stock",
"name": "Stock Reconciliation",
diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
index ca2741ccfba..ca59e67a676 100644
--- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
+++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
@@ -45,6 +45,7 @@ class StockReconciliation(StockController):
def on_cancel(self):
self.delete_and_repost_sle()
self.make_gl_entries_on_cancel()
+ self.delete_auto_created_batches()
def remove_items_with_no_change(self):
"""Remove items if qty or rate is not changed"""
@@ -164,9 +165,12 @@ class StockReconciliation(StockController):
validate_is_stock_item(item_code, item.is_stock_item, verbose=0)
# item should not be serialized
- if item.has_serial_no and not row.serial_no and not item.serial_no_series:
+ if item.has_serial_no and not row.serial_no and not item.serial_no_series and flt(row.qty) > 0:
raise frappe.ValidationError(_("Serial no(s) required for serialized item {0}").format(item_code))
+ if flt(row.qty) == 0 and row.serial_no:
+ row.serial_no = ''
+
# item managed batch-wise not allowed
if item.has_batch_no and not row.batch_no and not item.create_new_batch:
raise frappe.ValidationError(_("Batch no is required for batched item {0}").format(item_code))
@@ -183,13 +187,14 @@ class StockReconciliation(StockController):
from erpnext.stock.stock_ledger import get_previous_sle
sl_entries = []
- has_serial_no = False
+
+ serialized_items = False
for row in self.items:
- item = frappe.get_doc("Item", row.item_code)
- if item.has_serial_no or item.has_batch_no:
- has_serial_no = True
- self.get_sle_for_serialized_items(row, sl_entries)
- else:
+ item = frappe.get_cached_doc("Item", row.item_code)
+ if not (item.has_serial_no or item.has_batch_no):
+ if row.serial_no or row.batch_no:
+ frappe.throw(_("Row #{0}: Item {1} is not a Serialized/Batched Item. It cannot have a Serial No/Batch No against it.") \
+ .format(row.idx, frappe.bold(row.item_code)))
previous_sle = get_previous_sle({
"item_code": row.item_code,
"warehouse": row.warehouse,
@@ -214,75 +219,93 @@ class StockReconciliation(StockController):
sl_entries.append(self.get_sle_for_items(row))
+ else:
+ serialized_items = True
+
+ if serialized_items:
+ self.get_sle_for_serialized_items(sl_entries)
+
if sl_entries:
- if has_serial_no:
- sl_entries = self.merge_similar_item_serial_nos(sl_entries)
+ allow_negative_stock = frappe.get_cached_value("Stock Settings", None, "allow_negative_stock")
+ self.make_sl_entries(sl_entries, allow_negative_stock=allow_negative_stock)
- self.make_sl_entries(sl_entries)
+ def get_sle_for_serialized_items(self, sl_entries):
+ self.issue_existing_serial_and_batch(sl_entries)
+ self.add_new_serial_and_batch(sl_entries)
+ self.update_valuation_rate_for_serial_no()
- if has_serial_no and sl_entries:
- self.update_valuation_rate_for_serial_no()
+ if sl_entries:
+ sl_entries = self.merge_similar_item_serial_nos(sl_entries)
- def get_sle_for_serialized_items(self, row, sl_entries):
- from erpnext.stock.stock_ledger import get_previous_sle
+ def issue_existing_serial_and_batch(self, sl_entries):
+ from erpnext.stock.stock_ledger import get_stock_ledger_entries
- serial_nos = get_serial_nos(row.serial_no)
+ for row in self.items:
+ serial_nos = get_serial_nos(row.serial_no) or []
-
- # To issue existing serial nos
- if row.current_qty and (row.current_serial_no or row.batch_no):
- args = self.get_sle_for_items(row)
- args.update({
- 'actual_qty': -1 * row.current_qty,
- 'serial_no': row.current_serial_no,
- 'batch_no': row.batch_no,
- 'valuation_rate': row.current_valuation_rate
- })
-
- if row.current_serial_no:
+ # To issue existing serial nos
+ if row.current_qty and (row.current_serial_no or row.batch_no):
+ args = self.get_sle_for_items(row)
args.update({
- 'qty_after_transaction': 0,
+ 'actual_qty': -1 * row.current_qty,
+ 'serial_no': row.current_serial_no,
+ 'batch_no': row.batch_no,
+ 'valuation_rate': row.current_valuation_rate
})
- sl_entries.append(args)
+ if row.current_serial_no:
+ args.update({
+ 'qty_after_transaction': 0,
+ })
- for serial_no in serial_nos:
- args = self.get_sle_for_items(row, [serial_no])
+ sl_entries.append(args)
- previous_sle = get_previous_sle({
- "item_code": row.item_code,
- "posting_date": self.posting_date,
- "posting_time": self.posting_time,
- "serial_no": serial_no
- })
+ qty_after_transaction = 0
+ for serial_no in serial_nos:
+ args = self.get_sle_for_items(row, [serial_no])
- if previous_sle and row.warehouse != previous_sle.get("warehouse"):
- # If serial no exists in different warehouse
+ previous_sle = get_stock_ledger_entries({
+ "item_code": row.item_code,
+ "posting_date": self.posting_date,
+ "posting_time": self.posting_time,
+ "serial_no": serial_no
+ }, "<", "desc", "limit 1")
- new_args = args.copy()
- new_args.update({
- 'actual_qty': -1,
- 'qty_after_transaction': cint(previous_sle.get('qty_after_transaction')) - 1,
- 'warehouse': previous_sle.get("warehouse", '') or row.warehouse,
- 'valuation_rate': previous_sle.get("valuation_rate")
+ previous_sle = previous_sle and previous_sle[0] or {}
+
+ if previous_sle and row.warehouse != previous_sle.get("warehouse"):
+ # If serial no exists in different warehouse
+
+ warehouse = previous_sle.get("warehouse", '') or row.warehouse
+
+ if not qty_after_transaction:
+ qty_after_transaction = get_stock_balance(row.item_code,
+ warehouse, self.posting_date, self.posting_time)
+
+ qty_after_transaction -= 1
+
+ new_args = args.copy()
+ new_args.update({
+ 'actual_qty': -1,
+ 'qty_after_transaction': qty_after_transaction,
+ 'warehouse': warehouse,
+ 'valuation_rate': previous_sle.get("valuation_rate")
+ })
+
+ sl_entries.append(new_args)
+
+ def add_new_serial_and_batch(self, sl_entries):
+ for row in self.items:
+ if row.qty:
+ args = self.get_sle_for_items(row)
+
+ args.update({
+ 'actual_qty': row.qty,
+ 'incoming_rate': row.valuation_rate,
+ 'valuation_rate': row.valuation_rate
})
- sl_entries.append(new_args)
-
- if row.qty:
- args = self.get_sle_for_items(row)
-
- args.update({
- 'actual_qty': row.qty,
- 'incoming_rate': row.valuation_rate,
- 'valuation_rate': row.valuation_rate
- })
-
- sl_entries.append(args)
-
- if serial_nos == get_serial_nos(row.current_serial_no):
- # update valuation rate
- self.update_valuation_rate_for_serial_nos(row, serial_nos)
+ sl_entries.append(args)
def update_valuation_rate_for_serial_no(self):
for d in self.items:
@@ -340,17 +363,9 @@ class StockReconciliation(StockController):
where voucher_type=%s and voucher_no=%s""", (self.doctype, self.name))
sl_entries = []
-
- has_serial_no = False
- for row in self.items:
- if row.serial_no or row.batch_no or row.current_serial_no:
- has_serial_no = True
- self.get_sle_for_serialized_items(row, sl_entries)
+ self.get_sle_for_serialized_items(sl_entries)
if sl_entries:
- if has_serial_no:
- sl_entries = self.merge_similar_item_serial_nos(sl_entries)
-
sl_entries.reverse()
allow_negative_stock = frappe.db.get_value("Stock Settings", None, "allow_negative_stock")
self.make_sl_entries(sl_entries, allow_negative_stock=allow_negative_stock)
@@ -485,15 +500,17 @@ def get_stock_balance_for(item_code, warehouse,
["has_serial_no", "has_batch_no"], as_dict=1)
serial_nos = ""
- if item_dict.get("has_serial_no"):
- qty, rate, serial_nos = get_qty_rate_for_serial_nos(item_code,
- warehouse, posting_date, posting_time, item_dict)
+ with_serial_no = True if item_dict.get("has_serial_no") else False
+ data = get_stock_balance(item_code, warehouse, posting_date, posting_time,
+ with_valuation_rate=with_valuation_rate, with_serial_no=with_serial_no)
+
+ if with_serial_no:
+ qty, rate, serial_nos = data
else:
- qty, rate = get_stock_balance(item_code, warehouse,
- posting_date, posting_time, with_valuation_rate=with_valuation_rate)
+ qty, rate = data
if item_dict.get("has_batch_no"):
- qty = get_batch_qty(batch_no, warehouse) or 0
+ qty = get_batch_qty(batch_no, warehouse, posting_date=posting_date, posting_time=posting_time) or 0
return {
'qty': qty,
@@ -501,28 +518,6 @@ def get_stock_balance_for(item_code, warehouse,
'serial_nos': serial_nos
}
-def get_qty_rate_for_serial_nos(item_code, warehouse, posting_date, posting_time, item_dict):
- args = {
- "item_code": item_code,
- "warehouse": warehouse,
- "posting_date": posting_date,
- "posting_time": posting_time,
- }
-
- serial_nos_list = [serial_no.get("name")
- for serial_no in get_available_serial_nos(args)]
-
- qty = len(serial_nos_list)
- serial_nos = '\n'.join(serial_nos_list)
- args.update({
- 'qty': qty,
- "serial_nos": serial_nos
- })
-
- rate = get_incoming_rate(args, raise_error_if_no_rate=False) or 0
-
- return qty, rate, serial_nos
-
@frappe.whitelist()
def get_difference_account(purpose, company):
if purpose == 'Stock Reconciliation':
diff --git a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py
index e6d7e3fea7d..a679c9415d0 100644
--- a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py
+++ b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py
@@ -170,7 +170,7 @@ class TestStockReconciliation(unittest.TestCase):
warehouse = "_Test Warehouse for Stock Reco2 - _TC"
sr = create_stock_reconciliation(item_code=item_code,
- warehouse = warehouse, qty=5, rate=200, do_not_submit=1)
+ warehouse = warehouse, qty=5, rate=200, do_not_save=1, do_not_submit=1)
sr.save(ignore_permissions=True)
sr.submit()
@@ -204,6 +204,162 @@ class TestStockReconciliation(unittest.TestCase):
stock_doc = frappe.get_doc("Stock Reconciliation", d)
stock_doc.cancel()
+ def test_stock_reco_for_serial_and_batch_item(self):
+ set_perpetual_inventory()
+
+ item = frappe.db.exists("Item", {'item_name': 'Batched and Serialised Item'})
+ if not item:
+ item = create_item("Batched and Serialised Item")
+ item.has_batch_no = 1
+ item.create_new_batch = 1
+ item.has_serial_no = 1
+ item.batch_number_series = "B-BATCH-.##"
+ item.serial_no_series = "S-.####"
+ item.save()
+ else:
+ item = frappe.get_doc("Item", {'item_name': 'Batched and Serialised Item'})
+
+ warehouse = "_Test Warehouse for Stock Reco2 - _TC"
+
+ sr = create_stock_reconciliation(item_code=item.item_code,
+ warehouse = warehouse, qty=1, rate=100)
+
+ batch_no = sr.items[0].batch_no
+
+ serial_nos = get_serial_nos(sr.items[0].serial_no)
+ self.assertEqual(len(serial_nos), 1)
+ self.assertEqual(frappe.db.get_value("Serial No", serial_nos[0], "batch_no"), batch_no)
+
+ sr.cancel()
+
+ self.assertEqual(frappe.db.get_value("Serial No", serial_nos[0], "status"), "Inactive")
+ self.assertEqual(frappe.db.exists("Batch", batch_no), None)
+
+ if frappe.db.exists("Serial No", serial_nos[0]):
+ frappe.delete_doc("Serial No", serial_nos[0])
+
+ def test_stock_reco_for_serial_and_batch_item_with_future_dependent_entry(self):
+ """
+ Behaviour: 1) Create Stock Reconciliation, which will be the origin document
+ of a new batch having a serial no
+ 2) Create a Stock Entry that adds a serial no to the same batch following this
+ Stock Reconciliation
+ 3) Cancel Stock Reconciliation
+ 4) Cancel Stock Entry
+ Expected Result: 3) Cancelling the Stock Reco throws a LinkExistsError since
+ Stock Entry is dependent on the batch involved
+ 4) Serial No only in the Stock Entry is Inactive and Batch qty decreases
+ """
+ from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
+ from erpnext.stock.doctype.batch.batch import get_batch_qty
+
+ set_perpetual_inventory()
+
+ item = frappe.db.exists("Item", {'item_name': 'Batched and Serialised Item'})
+ if not item:
+ item = create_item("Batched and Serialised Item")
+ item.has_batch_no = 1
+ item.create_new_batch = 1
+ item.has_serial_no = 1
+ item.batch_number_series = "B-BATCH-.##"
+ item.serial_no_series = "S-.####"
+ item.save()
+ else:
+ item = frappe.get_doc("Item", {'item_name': 'Batched and Serialised Item'})
+
+ warehouse = "_Test Warehouse for Stock Reco2 - _TC"
+
+ stock_reco = create_stock_reconciliation(item_code=item.item_code,
+ warehouse = warehouse, qty=1, rate=100)
+ batch_no = stock_reco.items[0].batch_no
+ serial_no = get_serial_nos(stock_reco.items[0].serial_no)[0]
+
+ stock_entry = make_stock_entry(item_code=item.item_code, target=warehouse, qty=1, basic_rate=100,
+ batch_no=batch_no)
+ serial_no_2 = get_serial_nos(stock_entry.items[0].serial_no)[0]
+
+ # Check Batch qty after 2 transactions
+ batch_qty = get_batch_qty(batch_no, warehouse, item.item_code)
+ self.assertEqual(batch_qty, 2)
+ frappe.db.commit()
+
+ # Cancelling Origin Document of Batch
+ self.assertRaises(frappe.LinkExistsError, stock_reco.cancel)
+ frappe.db.rollback()
+
+ stock_entry.cancel()
+
+ # Check Batch qty after cancellation
+ batch_qty = get_batch_qty(batch_no, warehouse, item.item_code)
+ self.assertEqual(batch_qty, 1)
+
+ # Check if Serial No from Stock Reconcilation is intact
+ self.assertEqual(frappe.db.get_value("Serial No", serial_no, "batch_no"), batch_no)
+ self.assertEqual(frappe.db.get_value("Serial No", serial_no, "status"), "Active")
+
+ # Check if Serial No from Stock Entry is Unlinked and Inactive
+ self.assertEqual(frappe.db.get_value("Serial No", serial_no_2, "batch_no"), None)
+ self.assertEqual(frappe.db.get_value("Serial No", serial_no_2, "status"), "Inactive")
+
+ stock_reco.load_from_db()
+ stock_reco.cancel()
+
+ for sn in (serial_no, serial_no_2):
+ if frappe.db.exists("Serial No", sn):
+ frappe.delete_doc("Serial No", sn)
+
+ def test_stock_reco_for_same_item_with_multiple_batches(self):
+ from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
+
+ set_perpetual_inventory()
+
+ item_code = "Stock-Reco-batch-Item-2"
+ warehouse = "_Test Warehouse for Stock Reco3 - _TC"
+
+ create_warehouse("_Test Warehouse for Stock Reco3", {"is_group": 0,
+ "parent_warehouse": "_Test Warehouse Group - _TC", "company": "_Test Company"})
+
+ batch_item_doc = create_item(item_code, is_stock_item=1)
+ if not batch_item_doc.has_batch_no:
+ frappe.db.set_value("Item", item_code, {
+ "has_batch_no": 1,
+ "create_new_batch": 1,
+ "batch_number_series": "Test-C.####"
+ })
+
+ # inward entries with different batch and valuation rate
+ ste1=make_stock_entry(posting_date="2012-12-15", posting_time="02:00", item_code=item_code,
+ target=warehouse, qty=6, basic_rate=700)
+ ste2=make_stock_entry(posting_date="2012-12-16", posting_time="02:00", item_code=item_code,
+ target=warehouse, qty=3, basic_rate=200)
+ ste3=make_stock_entry(posting_date="2012-12-17", posting_time="02:00", item_code=item_code,
+ target=warehouse, qty=2, basic_rate=500)
+ ste4=make_stock_entry(posting_date="2012-12-17", posting_time="02:00", item_code=item_code,
+ target=warehouse, qty=4, basic_rate=100)
+
+ batchwise_item_details = {}
+ for stock_doc in [ste1, ste2, ste3, ste4]:
+ self.assertEqual(item_code, stock_doc.items[0].item_code)
+ batchwise_item_details[stock_doc.items[0].batch_no] = [stock_doc.items[0].qty, 0.01]
+
+ stock_balance = frappe.get_all("Stock Ledger Entry",
+ filters = {"item_code": item_code, "warehouse": warehouse},
+ fields=["sum(stock_value_difference)"], as_list=1)
+
+ self.assertEqual(flt(stock_balance[0][0]), 6200.00)
+
+ sr = create_stock_reconciliation(item_code=item_code,
+ warehouse = warehouse, batch_details = batchwise_item_details)
+
+ stock_balance = frappe.get_all("Stock Ledger Entry",
+ filters = {"item_code": item_code, "warehouse": warehouse},
+ fields=["sum(stock_value_difference)"], as_list=1)
+
+ self.assertEqual(flt(stock_balance[0][0]), 0.15)
+
+ for doc in [sr, ste1, ste2, ste3, ste4]:
+ doc.cancel()
+ frappe.delete_doc(doc.doctype, doc.name)
def insert_existing_sle(warehouse):
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
@@ -240,6 +396,7 @@ def create_batch_or_serial_no_items():
def create_stock_reconciliation(**args):
args = frappe._dict(args)
sr = frappe.new_doc("Stock Reconciliation")
+ sr.purpose = args.purpose or "Stock Reconciliation"
sr.posting_date = args.posting_date or nowdate()
sr.posting_time = args.posting_time or nowtime()
sr.set_posting_time = 1
@@ -250,20 +407,33 @@ def create_stock_reconciliation(**args):
or frappe.get_cached_value("Company", sr.company, "cost_center") \
or "_Test Cost Center - _TC"
- sr.append("items", {
- "item_code": args.item_code or "_Test Item",
- "warehouse": args.warehouse or "_Test Warehouse - _TC",
- "qty": args.qty,
- "valuation_rate": args.rate,
- "serial_no": args.serial_no,
- "batch_no": args.batch_no
- })
+ if not args.batch_details:
+ sr.append("items", {
+ "item_code": args.item_code or "_Test Item",
+ "warehouse": args.warehouse or "_Test Warehouse - _TC",
+ "qty": args.qty,
+ "valuation_rate": args.rate,
+ "serial_no": args.serial_no,
+ "batch_no": args.batch_no
+ })
+ elif args.batch_details:
+ for batch, data in args.batch_details.items():
+ sr.append("items", {
+ "item_code": args.item_code or "_Test Item",
+ "warehouse": args.warehouse or "_Test Warehouse - _TC",
+ "qty": data[0],
+ "valuation_rate": data[1],
+ "batch_no": batch
+ })
+
+ if not args.do_not_save:
+ sr.insert()
+ try:
+ if not args.do_not_submit:
+ sr.submit()
+ except EmptyStockReconciliationItemsError:
+ pass
- try:
- if not args.do_not_submit:
- sr.submit()
- except EmptyStockReconciliationItemsError:
- pass
return sr
def set_valuation_method(item_code, valuation_method):
diff --git a/erpnext/stock/doctype/stock_settings/stock_settings.js b/erpnext/stock/doctype/stock_settings/stock_settings.js
index 49ce3d8ef7a..cc0e2cfc425 100644
--- a/erpnext/stock/doctype/stock_settings/stock_settings.js
+++ b/erpnext/stock/doctype/stock_settings/stock_settings.js
@@ -3,6 +3,15 @@
frappe.ui.form.on('Stock Settings', {
refresh: function(frm) {
+ let filters = function() {
+ return {
+ filters : {
+ is_group : 0
+ }
+ };
+ };
+ frm.set_query("default_warehouse", filters);
+ frm.set_query("sample_retention_warehouse", filters);
}
});
diff --git a/erpnext/stock/doctype/stock_settings/stock_settings.py b/erpnext/stock/doctype/stock_settings/stock_settings.py
index 65de2e58d3a..93b5eee75c2 100644
--- a/erpnext/stock/doctype/stock_settings/stock_settings.py
+++ b/erpnext/stock/doctype/stock_settings/stock_settings.py
@@ -30,9 +30,17 @@ class StockSettings(Document):
frappe.make_property_setter({'fieldname': name, 'property': 'hidden',
'value': 0 if self.show_barcode_field else 1})
+ self.validate_warehouses()
self.cant_change_valuation_method()
self.validate_clean_description_html()
+ def validate_warehouses(self):
+ warehouse_fields = ["default_warehouse", "sample_retention_warehouse"]
+ for field in warehouse_fields:
+ if frappe.db.get_value("Warehouse", self.get(field), "is_group"):
+ frappe.throw(_("Group Warehouses cannot be used in transactions. Please change the value of {0}") \
+ .format(frappe.bold(self.meta.get_field(field).label)), title =_("Incorrect Warehouse"))
+
def cant_change_valuation_method(self):
db_valuation_method = frappe.db.get_single_value("Stock Settings", "valuation_method")
diff --git a/erpnext/stock/doctype/warehouse/warehouse.json b/erpnext/stock/doctype/warehouse/warehouse.json
index 646725956dc..6e40bc53d07 100644
--- a/erpnext/stock/doctype/warehouse/warehouse.json
+++ b/erpnext/stock/doctype/warehouse/warehouse.json
@@ -5,15 +5,18 @@
"description": "A logical Warehouse against which stock entries are made.",
"doctype": "DocType",
"document_type": "Setup",
+ "engine": "InnoDB",
"field_order": [
"warehouse_detail",
"warehouse_name",
+ "section_break_3",
+ "warehouse_type",
+ "parent_warehouse",
"is_group",
- "company",
- "disabled",
"column_break_4",
"account",
- "warehouse_type",
+ "company",
+ "disabled",
"address_and_contact",
"address_html",
"column_break_10",
@@ -29,7 +32,6 @@
"state",
"pin",
"tree_details",
- "parent_warehouse",
"lft",
"rgt",
"old_parent"
@@ -42,7 +44,6 @@
"oldfieldtype": "Section Break"
},
{
- "description": "If blank, parent Warehouse Account or company default will be considered",
"fieldname": "warehouse_name",
"fieldtype": "Data",
"label": "Warehouse Name",
@@ -83,12 +84,14 @@
"fieldtype": "Column Break"
},
{
+ "description": "If blank, parent Warehouse Account or company default will be considered in transactions",
"fieldname": "account",
"fieldtype": "Link",
"label": "Account",
"options": "Account"
},
{
+ "depends_on": "eval:!doc.__islocal",
"fieldname": "address_and_contact",
"fieldtype": "Section Break",
"label": "Address and Contact"
@@ -222,14 +225,21 @@
"fieldtype": "Link",
"label": "Warehouse Type",
"options": "Warehouse Type"
+ },
+ {
+ "fieldname": "section_break_3",
+ "fieldtype": "Section Break"
}
],
"icon": "fa fa-building",
"idx": 1,
- "modified": "2019-05-22 11:17:23.357490",
+ "is_tree": 1,
+ "links": [],
+ "modified": "2020-08-03 18:41:52.442502",
"modified_by": "Administrator",
"module": "Stock",
"name": "Warehouse",
+ "nsm_parent_field": "parent_warehouse",
"owner": "Administrator",
"permissions": [
{
@@ -279,8 +289,8 @@
"role": "Manufacturing User"
}
],
- "quick_entry": 1,
"show_name_in_global_search": 1,
+ "sort_field": "modified",
"sort_order": "DESC",
"title_field": "warehouse_name"
}
\ No newline at end of file
diff --git a/erpnext/stock/doctype/warehouse/warehouse.py b/erpnext/stock/doctype/warehouse/warehouse.py
index 6ed6044f329..312488984cd 100644
--- a/erpnext/stock/doctype/warehouse/warehouse.py
+++ b/erpnext/stock/doctype/warehouse/warehouse.py
@@ -177,10 +177,10 @@ def convert_to_group_or_ledger():
return frappe.get_doc("Warehouse", args.docname).convert_to_group_or_ledger()
def get_child_warehouses(warehouse):
- lft, rgt = frappe.get_cached_value("Warehouse", warehouse, [lft, rgt])
+ lft, rgt = frappe.get_cached_value("Warehouse", warehouse, ["lft", "rgt"])
return frappe.db.sql_list("""select name from `tabWarehouse`
- where lft >= %s and rgt =< %s""", (lft, rgt))
+ where lft >= %s and rgt <= %s""", (lft, rgt))
def get_warehouses_based_on_account(account, company=None):
warehouses = []
diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py
index b76a9b064a6..50c8c4e53f4 100644
--- a/erpnext/stock/get_item_details.py
+++ b/erpnext/stock/get_item_details.py
@@ -239,26 +239,13 @@ def get_basic_details(args, item, overwrite_warehouse=True):
item_group_defaults = get_item_group_defaults(item.name, args.company)
brand_defaults = get_brand_defaults(item.name, args.company)
- if overwrite_warehouse or not args.warehouse:
- warehouse = (
- args.get("set_warehouse") or
- item_defaults.get("default_warehouse") or
- item_group_defaults.get("default_warehouse") or
- brand_defaults.get("default_warehouse") or
- args.warehouse
- )
+ defaults = frappe._dict({
+ 'item_defaults': item_defaults,
+ 'item_group_defaults': item_group_defaults,
+ 'brand_defaults': brand_defaults
+ })
- if not warehouse:
- defaults = frappe.defaults.get_defaults() or {}
- warehouse_exists = frappe.db.exists("Warehouse", {
- 'name': defaults.default_warehouse,
- 'company': args.company
- })
- if defaults.get("default_warehouse") and warehouse_exists:
- warehouse = defaults.default_warehouse
-
- else:
- warehouse = args.warehouse
+ warehouse = get_item_warehouse(item, args, overwrite_warehouse, defaults)
if args.get('doctype') == "Material Request" and not args.get('material_request_type'):
args['material_request_type'] = frappe.db.get_value('Material Request',
@@ -271,7 +258,7 @@ def get_basic_details(args, item, overwrite_warehouse=True):
expense_account = get_asset_category_account(fieldname = "fixed_asset_account", item = args.item_code, company= args.company)
#Set the UOM to the Default Sales UOM or Default Purchase UOM if configured in the Item Master
- if not args.uom:
+ if not args.get('uom'):
if args.get('doctype') in sales_doctypes:
args.uom = item.sales_uom if item.sales_uom else item.stock_uom
elif (args.get('doctype') in ['Purchase Order', 'Purchase Receipt', 'Purchase Invoice']) or \
@@ -291,7 +278,7 @@ def get_basic_details(args, item, overwrite_warehouse=True):
"cost_center": get_default_cost_center(args, item_defaults, item_group_defaults, brand_defaults),
'has_serial_no': item.has_serial_no,
'has_batch_no': item.has_batch_no,
- "batch_no": None,
+ "batch_no": args.get("batch_no"),
"uom": args.uom,
"min_order_qty": flt(item.min_order_qty) if args.doctype == "Material Request" else "",
"qty": flt(args.qty) or 1.0,
@@ -352,6 +339,15 @@ def get_basic_details(args, item, overwrite_warehouse=True):
else:
out["manufacturer_part_no"] = None
out["manufacturer"] = None
+ else:
+ data = frappe.get_value("Item", item.name,
+ ["default_item_manufacturer", "default_manufacturer_part_no"] , as_dict=1)
+
+ if data:
+ out.update({
+ "manufacturer": data.default_item_manufacturer,
+ "manufacturer_part_no": data.default_manufacturer_part_no
+ })
child_doctype = args.doctype + ' Item'
meta = frappe.get_meta(child_doctype)
@@ -360,6 +356,37 @@ def get_basic_details(args, item, overwrite_warehouse=True):
return out
+def get_item_warehouse(item, args, overwrite_warehouse, defaults={}):
+ if not defaults:
+ defaults = frappe._dict({
+ 'item_defaults' : get_item_defaults(item.name, args.company),
+ 'item_group_defaults' : get_item_group_defaults(item.name, args.company),
+ 'brand_defaults' : get_brand_defaults(item.name, args.company)
+ })
+
+ if overwrite_warehouse or not args.warehouse:
+ warehouse = (
+ args.get("set_warehouse") or
+ defaults.item_defaults.get("default_warehouse") or
+ defaults.item_group_defaults.get("default_warehouse") or
+ defaults.brand_defaults.get("default_warehouse") or
+ args.get('warehouse')
+ )
+
+ if not warehouse:
+ defaults = frappe.defaults.get_defaults() or {}
+ warehouse_exists = frappe.db.exists("Warehouse", {
+ 'name': defaults.default_warehouse,
+ 'company': args.company
+ })
+ if defaults.get("default_warehouse") and warehouse_exists:
+ warehouse = defaults.default_warehouse
+
+ else:
+ warehouse = args.get('warehouse')
+
+ return warehouse
+
def update_barcode_value(out):
from erpnext.accounts.doctype.sales_invoice.pos import get_barcode_data
barcode_data = get_barcode_data([out])
@@ -597,7 +624,7 @@ def get_item_price(args, item_code, ignore_party=False):
elif args.get("supplier"):
conditions += " and supplier=%(supplier)s"
else:
- conditions += " and (customer is null or customer = '') and (supplier is null or supplier = '')"
+ conditions += "and (customer is null or customer = '') and (supplier is null or supplier = '')"
if args.get('transaction_date'):
conditions += """ and %(transaction_date)s between
diff --git a/erpnext/stock/reorder_item.py b/erpnext/stock/reorder_item.py
index 39fb0240236..4c721acdc12 100644
--- a/erpnext/stock/reorder_item.py
+++ b/erpnext/stock/reorder_item.py
@@ -4,6 +4,7 @@
from __future__ import unicode_literals
import frappe
import erpnext
+import json
from frappe.utils import flt, nowdate, add_days, cint
from frappe import _
@@ -116,6 +117,8 @@ def create_material_request(material_requests):
else:
exceptions_list.append(frappe.get_traceback())
+ frappe.log_error(frappe.get_traceback())
+
for request_type in material_requests:
for company in material_requests[request_type]:
try:
@@ -158,6 +161,7 @@ def create_material_request(material_requests):
schedule_dates = [d.schedule_date for d in mr.items]
mr.schedule_date = max(schedule_dates or [nowdate()])
+ mr.flags.ignore_mandatory = True
mr.insert()
mr.submit()
mr_list.append(mr)
@@ -195,19 +199,16 @@ def send_email_notification(mr_list):
subject=_('Auto Material Requests Generated'), message = msg)
def notify_errors(exceptions_list):
- subject = "[Important] [ERPNext] Auto Reorder Errors"
- content = """Dear System Manager,
+ subject = _("[Important] [ERPNext] Auto Reorder Errors")
+ content = _("Dear System Manager,") + " " + _("An error occured for certain Items while creating Material Requests based on Re-order level. \
+ Please rectify these issues :") + " "
-An error occured for certain Items while creating Material Requests based on Re-order level.
+ for exception in exceptions_list:
+ exception = json.loads(exception)
+ error_message = """{0}
""".format(_(exception.get("message")))
+ content += error_message
-Please rectify these issues:
----
-
-%s
-
----
-Regards,
-Administrator""" % ("\n\n".join(exceptions_list),)
+ content += _("Regards,") + " " + _("Administrator")
from frappe.email import sendmail_to_system_managers
sendmail_to_system_managers(subject, content)
diff --git a/erpnext/stock/report/purchase_order_items_to_be_received_or_billed/purchase_order_items_to_be_received_or_billed.json b/erpnext/stock/report/purchase_order_items_to_be_received_or_billed/purchase_order_items_to_be_received_or_billed.json
index 48c0f423fd9..6714b2e02c9 100644
--- a/erpnext/stock/report/purchase_order_items_to_be_received_or_billed/purchase_order_items_to_be_received_or_billed.json
+++ b/erpnext/stock/report/purchase_order_items_to_be_received_or_billed/purchase_order_items_to_be_received_or_billed.json
@@ -1,5 +1,5 @@
{
- "add_total_row": 0,
+ "add_total_row": 1,
"creation": "2019-09-16 14:10:33.102865",
"disable_prepared_report": 0,
"disabled": 0,
@@ -7,7 +7,7 @@
"doctype": "Report",
"idx": 0,
"is_standard": "Yes",
- "modified": "2019-09-21 15:19:55.710578",
+ "modified": "2020-05-13 15:27:45.228418",
"modified_by": "Administrator",
"module": "Stock",
"name": "Purchase Order Items To Be Received or Billed",
diff --git a/erpnext/stock/report/stock_ageing/stock_ageing.py b/erpnext/stock/report/stock_ageing/stock_ageing.py
index 803a5c81a3b..99d816c4a24 100644
--- a/erpnext/stock/report/stock_ageing/stock_ageing.py
+++ b/erpnext/stock/report/stock_ageing/stock_ageing.py
@@ -178,14 +178,14 @@ def get_fifo_queue(filters, sle=None):
qty_to_pop = abs(d.actual_qty)
while qty_to_pop:
batch = fifo_queue[0] if fifo_queue else [0, None]
- if 0 < batch[0] <= qty_to_pop:
+ if 0 < flt(batch[0]) <= qty_to_pop:
# if batch qty > 0
# not enough or exactly same qty in current batch, clear batch
- qty_to_pop -= batch[0]
+ qty_to_pop -= flt(batch[0])
transferred_item_details[(d.voucher_no, d.name)].append(fifo_queue.pop(0))
else:
# all from current batch
- batch[0] -= qty_to_pop
+ batch[0] = flt(batch[0]) - qty_to_pop
transferred_item_details[(d.voucher_no, d.name)].append([qty_to_pop, batch[1]])
qty_to_pop = 0
diff --git a/erpnext/stock/report/stock_and_account_value_comparison/stock_and_account_value_comparison.py b/erpnext/stock/report/stock_and_account_value_comparison/stock_and_account_value_comparison.py
index 94ec314e8c9..1af68dd7f22 100644
--- a/erpnext/stock/report/stock_and_account_value_comparison/stock_and_account_value_comparison.py
+++ b/erpnext/stock/report/stock_and_account_value_comparison/stock_and_account_value_comparison.py
@@ -35,7 +35,7 @@ def get_data(report_filters):
gl_data = voucher_wise_gl_data.get(key) or {}
d.account_value = gl_data.get("account_value", 0)
d.difference_value = (d.stock_value - d.account_value)
- if abs(d.difference_value) > 1.0/10 ** currency_precision:
+ if abs(d.difference_value) > 0.1:
data.append(d)
return data
diff --git a/erpnext/stock/report/stock_balance/stock_balance.js b/erpnext/stock/report/stock_balance/stock_balance.js
index 537fa7c04b8..7d22823eb80 100644
--- a/erpnext/stock/report/stock_balance/stock_balance.js
+++ b/erpnext/stock/report/stock_balance/stock_balance.js
@@ -3,6 +3,14 @@
frappe.query_reports["Stock Balance"] = {
"filters": [
+ {
+ "fieldname": "company",
+ "label": __("Company"),
+ "fieldtype": "Link",
+ "width": "80",
+ "options": "Company",
+ "default": frappe.defaults.get_default("company")
+ },
{
"fieldname":"from_date",
"label": __("From Date"),
@@ -26,12 +34,6 @@ frappe.query_reports["Stock Balance"] = {
"width": "80",
"options": "Item Group"
},
- {
- "fieldname":"brand",
- "label": __("Brand"),
- "fieldtype": "Link",
- "options": "Brand"
- },
{
"fieldname": "item_code",
"label": __("Item"),
@@ -84,5 +86,18 @@ frappe.query_reports["Stock Balance"] = {
"label": __('Show Stock Ageing Data'),
"fieldtype": 'Check'
},
- ]
+ ],
+
+ "formatter": function (value, row, column, data, default_formatter) {
+ value = default_formatter(value, row, column, data);
+
+ if (column.fieldname == "out_qty" && data && data.out_qty > 0) {
+ value = "" + value + " ";
+ }
+ else if (column.fieldname == "in_qty" && data && data.in_qty > 0) {
+ value = "" + value + " ";
+ }
+
+ return value;
+ }
};
diff --git a/erpnext/stock/report/stock_balance/stock_balance.json b/erpnext/stock/report/stock_balance/stock_balance.json
index 2f20b202350..8c45f0c2f47 100644
--- a/erpnext/stock/report/stock_balance/stock_balance.json
+++ b/erpnext/stock/report/stock_balance/stock_balance.json
@@ -1,24 +1,26 @@
{
- "add_total_row": 1,
- "creation": "2014-10-10 17:58:11.577901",
- "disabled": 0,
- "docstatus": 0,
- "doctype": "Report",
- "idx": 2,
- "is_standard": "Yes",
- "modified": "2018-08-14 15:24:41.395557",
- "modified_by": "Administrator",
- "module": "Stock",
- "name": "Stock Balance",
- "owner": "Administrator",
- "prepared_report": 1,
- "ref_doctype": "Stock Ledger Entry",
- "report_name": "Stock Balance",
- "report_type": "Script Report",
+ "add_total_row": 1,
+ "creation": "2014-10-10 17:58:11.577901",
+ "disable_prepared_report": 0,
+ "disabled": 0,
+ "docstatus": 0,
+ "doctype": "Report",
+ "idx": 2,
+ "is_standard": "Yes",
+ "modified": "2020-04-30 13:46:14.680354",
+ "modified_by": "Administrator",
+ "module": "Stock",
+ "name": "Stock Balance",
+ "owner": "Administrator",
+ "prepared_report": 1,
+ "query": "",
+ "ref_doctype": "Stock Ledger Entry",
+ "report_name": "Stock Balance",
+ "report_type": "Script Report",
"roles": [
{
"role": "Stock User"
- },
+ },
{
"role": "Accounts Manager"
}
diff --git a/erpnext/stock/report/stock_balance/stock_balance.py b/erpnext/stock/report/stock_balance/stock_balance.py
index ff03381389c..042087a4a77 100644
--- a/erpnext/stock/report/stock_balance/stock_balance.py
+++ b/erpnext/stock/report/stock_balance/stock_balance.py
@@ -2,7 +2,7 @@
# License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
-import frappe
+import frappe, erpnext
from frappe import _
from frappe.utils import flt, cint, getdate, now, date_diff
from erpnext.stock.utils import add_additional_uom_columns
@@ -20,6 +20,11 @@ def execute(filters=None):
from_date = filters.get('from_date')
to_date = filters.get('to_date')
+ if filters.get("company"):
+ company_currency = erpnext.get_company_currency(filters.get("company"))
+ else:
+ company_currency = frappe.db.get_single_value("Global Defaults", "default_currency")
+
include_uom = filters.get("include_uom")
columns = get_columns(filters)
items = get_items(filters)
@@ -52,6 +57,7 @@ def execute(filters=None):
item_reorder_qty = item_reorder_detail_map[item + warehouse]["warehouse_reorder_qty"]
report_data = {
+ 'currency': company_currency,
'item_code': item,
'warehouse': warehouse,
'company': company,
@@ -89,24 +95,21 @@ def execute(filters=None):
def get_columns(filters):
"""return columns"""
-
columns = [
{"label": _("Item"), "fieldname": "item_code", "fieldtype": "Link", "options": "Item", "width": 100},
{"label": _("Item Name"), "fieldname": "item_name", "width": 150},
{"label": _("Item Group"), "fieldname": "item_group", "fieldtype": "Link", "options": "Item Group", "width": 100},
- {"label": _("Brand"), "fieldname": "brand", "fieldtype": "Link", "options": "Brand", "width": 90},
- {"label": _("Description"), "fieldname": "description", "width": 140},
{"label": _("Warehouse"), "fieldname": "warehouse", "fieldtype": "Link", "options": "Warehouse", "width": 100},
{"label": _("Stock UOM"), "fieldname": "stock_uom", "fieldtype": "Link", "options": "UOM", "width": 90},
{"label": _("Balance Qty"), "fieldname": "bal_qty", "fieldtype": "Float", "width": 100, "convertible": "qty"},
- {"label": _("Balance Value"), "fieldname": "bal_val", "fieldtype": "Currency", "width": 100},
+ {"label": _("Balance Value"), "fieldname": "bal_val", "fieldtype": "Currency", "width": 100, "options": "currency"},
{"label": _("Opening Qty"), "fieldname": "opening_qty", "fieldtype": "Float", "width": 100, "convertible": "qty"},
- {"label": _("Opening Value"), "fieldname": "opening_val", "fieldtype": "Float", "width": 110},
+ {"label": _("Opening Value"), "fieldname": "opening_val", "fieldtype": "Currency", "width": 110, "options": "currency"},
{"label": _("In Qty"), "fieldname": "in_qty", "fieldtype": "Float", "width": 80, "convertible": "qty"},
{"label": _("In Value"), "fieldname": "in_val", "fieldtype": "Float", "width": 80},
{"label": _("Out Qty"), "fieldname": "out_qty", "fieldtype": "Float", "width": 80, "convertible": "qty"},
{"label": _("Out Value"), "fieldname": "out_val", "fieldtype": "Float", "width": 80},
- {"label": _("Valuation Rate"), "fieldname": "val_rate", "fieldtype": "Currency", "width": 90, "convertible": "rate"},
+ {"label": _("Valuation Rate"), "fieldname": "val_rate", "fieldtype": "Currency", "width": 90, "convertible": "rate", "options": "currency"},
{"label": _("Reorder Level"), "fieldname": "reorder_level", "fieldtype": "Float", "width": 80, "convertible": "qty"},
{"label": _("Reorder Qty"), "fieldname": "reorder_qty", "fieldtype": "Float", "width": 80, "convertible": "qty"},
{"label": _("Company"), "fieldname": "company", "fieldtype": "Link", "options": "Company", "width": 100}
@@ -132,6 +135,9 @@ def get_conditions(filters):
else:
frappe.throw(_("'To Date' is required"))
+ if filters.get("company"):
+ conditions += " and sle.company = %s" % frappe.db.escape(filters.get("company"))
+
if filters.get("warehouse"):
warehouse_details = frappe.db.get_value("Warehouse",
filters.get("warehouse"), ["lft", "rgt"], as_dict=1)
@@ -170,6 +176,8 @@ def get_item_warehouse_map(filters, sle):
from_date = getdate(filters.get("from_date"))
to_date = getdate(filters.get("to_date"))
+ float_precision = cint(frappe.db.get_default("float_precision")) or 3
+
for d in sle:
key = (d.company, d.item_code, d.warehouse)
if key not in iwb_map:
@@ -184,7 +192,7 @@ def get_item_warehouse_map(filters, sle):
qty_dict = iwb_map[(d.company, d.item_code, d.warehouse)]
if d.voucher_type == "Stock Reconciliation":
- qty_diff = flt(d.qty_after_transaction) - qty_dict.bal_qty
+ qty_diff = flt(d.qty_after_transaction) - flt(qty_dict.bal_qty)
else:
qty_diff = flt(d.actual_qty)
@@ -195,7 +203,7 @@ def get_item_warehouse_map(filters, sle):
qty_dict.opening_val += value_diff
elif d.posting_date >= from_date and d.posting_date <= to_date:
- if qty_diff > 0:
+ if flt(qty_diff, float_precision) >= 0:
qty_dict.in_qty += qty_diff
qty_dict.in_val += value_diff
else:
@@ -206,16 +214,15 @@ def get_item_warehouse_map(filters, sle):
qty_dict.bal_qty += qty_diff
qty_dict.bal_val += value_diff
- iwb_map = filter_items_with_no_transactions(iwb_map)
+ iwb_map = filter_items_with_no_transactions(iwb_map, float_precision)
return iwb_map
-def filter_items_with_no_transactions(iwb_map):
+def filter_items_with_no_transactions(iwb_map, float_precision):
for (company, item, warehouse) in sorted(iwb_map):
qty_dict = iwb_map[(company, item, warehouse)]
no_transactions = True
- float_precision = cint(frappe.db.get_default("float_precision")) or 3
for key, val in iteritems(qty_dict):
val = flt(val, float_precision)
qty_dict[key] = val
@@ -232,8 +239,6 @@ def get_items(filters):
if filters.get("item_code"):
conditions.append("item.name=%(item_code)s")
else:
- if filters.get("brand"):
- conditions.append("item.brand=%(brand)s")
if filters.get("item_group"):
conditions.append(get_item_group_condition(filters.get("item_group")))
diff --git a/erpnext/stock/report/stock_ledger/stock_ledger.js b/erpnext/stock/report/stock_ledger/stock_ledger.js
index 3fab3273b9e..1813c2b5bd7 100644
--- a/erpnext/stock/report/stock_ledger/stock_ledger.js
+++ b/erpnext/stock/report/stock_ledger/stock_ledger.js
@@ -29,7 +29,13 @@ frappe.query_reports["Stock Ledger"] = {
"fieldname":"warehouse",
"label": __("Warehouse"),
"fieldtype": "Link",
- "options": "Warehouse"
+ "options": "Warehouse",
+ "get_query": function() {
+ const company = frappe.query_report.get_filter_value('company');
+ return {
+ filters: { 'company': company }
+ }
+ }
},
{
"fieldname":"item_code",
diff --git a/erpnext/stock/report/stock_ledger/stock_ledger.py b/erpnext/stock/report/stock_ledger/stock_ledger.py
index d757ecb293d..6a265ec4cc5 100644
--- a/erpnext/stock/report/stock_ledger/stock_ledger.py
+++ b/erpnext/stock/report/stock_ledger/stock_ledger.py
@@ -4,6 +4,7 @@
from __future__ import unicode_literals
import frappe
from frappe import _
+from frappe.utils import cint, flt
from erpnext.stock.utils import update_included_uom_in_report
def execute(filters=None):
@@ -13,6 +14,7 @@ def execute(filters=None):
sl_entries = get_stock_ledger_entries(filters, items)
item_details = get_item_details(items, sl_entries, include_uom)
opening_row = get_opening_balance(filters, columns)
+ precision = cint(frappe.db.get_single_value("System Settings", "float_precision"))
data = []
conversion_factors = []
@@ -27,10 +29,10 @@ def execute(filters=None):
sle.update(item_detail)
if filters.get("batch_no"):
- actual_qty += sle.actual_qty
+ actual_qty += flt(sle.actual_qty, precision)
stock_value += sle.stock_value_difference
- if sle.voucher_type == 'Stock Reconciliation':
+ if sle.voucher_type == 'Stock Reconciliation' and not sle.actual_qty:
actual_qty = sle.qty_after_transaction
stock_value = sle.stock_value
diff --git a/erpnext/stock/report/stock_projected_qty/stock_projected_qty.py b/erpnext/stock/report/stock_projected_qty/stock_projected_qty.py
index 913d7d848ed..c8efb1637f9 100644
--- a/erpnext/stock/report/stock_projected_qty/stock_projected_qty.py
+++ b/erpnext/stock/report/stock_projected_qty/stock_projected_qty.py
@@ -44,7 +44,9 @@ def execute(filters=None):
re_order_level = d.warehouse_reorder_level
re_order_qty = d.warehouse_reorder_qty
- shortage_qty = re_order_level - flt(bin.projected_qty) if (re_order_level or re_order_qty) else 0
+ shortage_qty = 0
+ if (re_order_level or re_order_qty) and re_order_level > bin.projected_qty:
+ shortage_qty = re_order_level - flt(bin.projected_qty)
data.append([item.name, item.item_name, item.description, item.item_group, item.brand, bin.warehouse,
item.stock_uom, bin.actual_qty, bin.planned_qty, bin.indented_qty, bin.ordered_qty,
diff --git a/erpnext/stock/report/supplier_wise_sales_analytics/supplier_wise_sales_analytics.py b/erpnext/stock/report/supplier_wise_sales_analytics/supplier_wise_sales_analytics.py
index 6a86889aa3d..5873a7a3008 100644
--- a/erpnext/stock/report/supplier_wise_sales_analytics/supplier_wise_sales_analytics.py
+++ b/erpnext/stock/report/supplier_wise_sales_analytics/supplier_wise_sales_analytics.py
@@ -21,7 +21,7 @@ def execute(filters=None):
for cd in consumed_details.get(item_code):
if (cd.voucher_no not in material_transfer_vouchers):
- if cd.voucher_type=="Delivery Note":
+ if cd.voucher_type in ["Delivery Note", "Sales Invoice"]:
delivered_qty += abs(flt(cd.actual_qty))
delivered_amount += abs(flt(cd.stock_value_difference))
elif cd.voucher_type!="Delivery Note":
diff --git a/erpnext/stock/stock_balance.py b/erpnext/stock/stock_balance.py
index e5dc6b12df7..56973153609 100644
--- a/erpnext/stock/stock_balance.py
+++ b/erpnext/stock/stock_balance.py
@@ -113,13 +113,30 @@ def get_reserved_qty(item_code, warehouse):
return flt(reserved_qty[0][0]) if reserved_qty else 0
def get_indented_qty(item_code, warehouse):
- indented_qty = frappe.db.sql("""select sum((mr_item.qty - mr_item.ordered_qty) * mr_item.conversion_factor)
+ # Ordered Qty is always maintained in stock UOM
+ inward_qty = frappe.db.sql("""
+ select sum(mr_item.stock_qty - mr_item.ordered_qty)
from `tabMaterial Request Item` mr_item, `tabMaterial Request` mr
where mr_item.item_code=%s and mr_item.warehouse=%s
- and mr_item.qty > mr_item.ordered_qty and mr_item.parent=mr.name
- and mr.status!='Stopped' and mr.docstatus=1""", (item_code, warehouse))
+ and mr.material_request_type in ('Purchase', 'Manufacture', 'Customer Provided', 'Material Transfer')
+ and mr_item.stock_qty > mr_item.ordered_qty and mr_item.parent=mr.name
+ and mr.status!='Stopped' and mr.docstatus=1
+ """, (item_code, warehouse))
+ inward_qty = flt(inward_qty[0][0]) if inward_qty else 0
- return flt(indented_qty[0][0]) if indented_qty else 0
+ outward_qty = frappe.db.sql("""
+ select sum(mr_item.stock_qty - mr_item.ordered_qty)
+ from `tabMaterial Request Item` mr_item, `tabMaterial Request` mr
+ where mr_item.item_code=%s and mr_item.warehouse=%s
+ and mr.material_request_type = 'Material Issue'
+ and mr_item.stock_qty > mr_item.ordered_qty and mr_item.parent=mr.name
+ and mr.status!='Stopped' and mr.docstatus=1
+ """, (item_code, warehouse))
+ outward_qty = flt(outward_qty[0][0]) if outward_qty else 0
+
+ requested_qty = inward_qty - outward_qty
+
+ return requested_qty
def get_ordered_qty(item_code, warehouse):
ordered_qty = frappe.db.sql("""
@@ -145,9 +162,9 @@ def update_bin_qty(item_code, warehouse, qty_dict=None):
from erpnext.stock.utils import get_bin
bin = get_bin(item_code, warehouse)
mismatch = False
- for fld, val in qty_dict.items():
- if flt(bin.get(fld)) != flt(val):
- bin.set(fld, flt(val))
+ for field, value in qty_dict.items():
+ if flt(bin.get(field)) != flt(value):
+ bin.set(field, flt(value))
mismatch = True
if mismatch:
diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py
index b100f453273..5c4bba730e3 100644
--- a/erpnext/stock/stock_ledger.py
+++ b/erpnext/stock/stock_ledger.py
@@ -428,7 +428,7 @@ class update_entries_after(object):
frappe.get_desk_link(self.exceptions[0]["voucher_type"], self.exceptions[0]["voucher_no"]))
if self.verbose:
- frappe.throw(msg, NegativeStockError, title='Insufficent Stock')
+ frappe.throw(msg, NegativeStockError, title='Insufficient Stock')
else:
raise NegativeStockError(msg)
@@ -460,7 +460,13 @@ def get_stock_ledger_entries(previous_sle, operator=None,
conditions += " and " + previous_sle.get("warehouse_condition")
if check_serial_no and previous_sle.get("serial_no"):
- conditions += " and serial_no like {}".format(frappe.db.escape('%{0}%'.format(previous_sle.get("serial_no"))))
+ serial_no = previous_sle.get("serial_no")
+ conditions += """ and (
+ serial_no = '{0}'
+ OR serial_no like '{0}\n%%'
+ OR serial_no like '%%\n{0}'
+ OR serial_no like '%%\n{0}\n%%'
+ ) and actual_qty > 0""".format(serial_no)
if not previous_sle.get("posting_date"):
previous_sle["posting_date"] = "1900-01-01"
@@ -527,7 +533,16 @@ def get_valuation_rate(item_code, warehouse, voucher_type, voucher_no,
if not allow_zero_rate and not valuation_rate and raise_error_if_no_rate \
and cint(erpnext.is_perpetual_inventory_enabled(company)):
frappe.local.message_log = []
- frappe.throw(_("Valuation rate not found for the Item {0}, which is required to do accounting entries for {1} {2}. If the item is transacting as a zero valuation rate item in the {1}, please mention that in the {1} Item table. Otherwise, please create an incoming stock transaction for the item or mention valuation rate in the Item record, and then try submiting / cancelling this entry.")
- .format(item_code, voucher_type, voucher_no))
+ form_link = frappe.utils.get_link_to_form("Item", item_code)
+
+ message = _("Valuation Rate for the Item {0}, is required to do accounting entries for {1} {2}.").format(form_link, voucher_type, voucher_no)
+ message += " " + _(" Here are the options to proceed:")
+ solutions = "" + _("If the item is transacting as a Zero Valuation Rate item in this entry, please enable 'Allow Zero Valuation Rate' in the {0} Item table.").format(voucher_type) + " "
+ solutions += "" + _("If not, you can Cancel / Submit this entry ") + _("{0}").format(frappe.bold("after")) + _(" performing either one below:") + " "
+ sub_solutions = "" + _("Create an incoming stock transaction for the Item.") + " "
+ sub_solutions += "" + _("Mention Valuation Rate in the Item master.") + " "
+ msg = message + solutions + sub_solutions + ""
+
+ frappe.throw(msg=msg, title=_("Valuation Rate Missing"))
return valuation_rate
diff --git a/erpnext/stock/utils.py b/erpnext/stock/utils.py
index 2c6c95393bc..db39bae8a63 100644
--- a/erpnext/stock/utils.py
+++ b/erpnext/stock/utils.py
@@ -74,7 +74,8 @@ def get_stock_value_on(warehouse=None, posting_date=None, item_code=None):
return sum(sle_map.values())
@frappe.whitelist()
-def get_stock_balance(item_code, warehouse, posting_date=None, posting_time=None, with_valuation_rate=False):
+def get_stock_balance(item_code, warehouse, posting_date=None, posting_time=None,
+ with_valuation_rate=False, with_serial_no=False):
"""Returns stock balance quantity at given warehouse on given posting date or current date.
If `with_valuation_rate` is True, will return tuple (qty, rate)"""
@@ -84,17 +85,47 @@ def get_stock_balance(item_code, warehouse, posting_date=None, posting_time=None
if not posting_date: posting_date = nowdate()
if not posting_time: posting_time = nowtime()
- last_entry = get_previous_sle({
+ args = {
"item_code": item_code,
"warehouse":warehouse,
"posting_date": posting_date,
- "posting_time": posting_time })
+ "posting_time": posting_time
+ }
+
+ last_entry = get_previous_sle(args)
if with_valuation_rate:
- return (last_entry.qty_after_transaction, last_entry.valuation_rate) if last_entry else (0.0, 0.0)
+ if with_serial_no:
+ serial_nos = get_serial_nos_data_after_transactions(args)
+
+ return ((last_entry.qty_after_transaction, last_entry.valuation_rate, serial_nos)
+ if last_entry else (0.0, 0.0, 0.0))
+ else:
+ return (last_entry.qty_after_transaction, last_entry.valuation_rate) if last_entry else (0.0, 0.0)
else:
return last_entry.qty_after_transaction if last_entry else 0.0
+def get_serial_nos_data_after_transactions(args):
+ serial_nos = []
+ data = frappe.db.sql(""" SELECT serial_no, actual_qty
+ FROM `tabStock Ledger Entry`
+ WHERE
+ item_code = %(item_code)s and warehouse = %(warehouse)s
+ and timestamp(posting_date, posting_time) < timestamp(%(posting_date)s, %(posting_time)s)
+ order by posting_date, posting_time asc """, args, as_dict=1)
+
+ for d in data:
+ if d.actual_qty > 0:
+ serial_nos.extend(get_serial_nos_data(d.serial_no))
+ else:
+ serial_nos = list(set(serial_nos) - set(get_serial_nos_data(d.serial_no)))
+
+ return '\n'.join(serial_nos)
+
+def get_serial_nos_data(serial_nos):
+ from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
+ return get_serial_nos(serial_nos)
+
@frappe.whitelist()
def get_latest_stock_qty(item_code, warehouse=None):
values, condition = [item_code], ""
@@ -136,7 +167,7 @@ def get_bin(item_code, warehouse):
bin_obj.flags.ignore_permissions = 1
bin_obj.insert()
else:
- bin_obj = frappe.get_doc('Bin', bin)
+ bin_obj = frappe.get_cached_doc('Bin', bin)
bin_obj.flags.ignore_permissions = True
return bin_obj
@@ -195,12 +226,12 @@ def get_valuation_method(item_code):
def get_fifo_rate(previous_stock_queue, qty):
"""get FIFO (average) Rate from Queue"""
- if qty >= 0:
+ if flt(qty) >= 0:
total = sum(f[0] for f in previous_stock_queue)
return sum(flt(f[0]) * flt(f[1]) for f in previous_stock_queue) / flt(total) if total else 0.0
else:
available_qty_for_outgoing, outgoing_cost = 0, 0
- qty_to_pop = abs(qty)
+ qty_to_pop = abs(flt(qty))
while qty_to_pop and previous_stock_queue:
batch = previous_stock_queue[0]
if 0 < batch[0] <= qty_to_pop:
diff --git a/erpnext/support/doctype/issue/issue.json b/erpnext/support/doctype/issue/issue.json
index 222554bda13..51b49e8049b 100644
--- a/erpnext/support/doctype/issue/issue.json
+++ b/erpnext/support/doctype/issue/issue.json
@@ -363,8 +363,9 @@
],
"icon": "fa fa-ticket",
"idx": 7,
- "modified": "2019-09-11 09:03:57.465623",
- "modified_by": "himanshu@erpnext.com",
+ "links": [],
+ "modified": "2020-02-26 02:19:49.477928",
+ "modified_by": "Administrator",
"module": "Support",
"name": "Issue",
"owner": "Administrator",
@@ -384,9 +385,9 @@
"quick_entry": 1,
"search_fields": "status,customer,subject,raised_by",
"sort_field": "modified",
- "sort_order": "ASC",
+ "sort_order": "DESC",
"timeline_field": "customer",
"title_field": "subject",
"track_changes": 1,
"track_seen": 1
-}
\ No newline at end of file
+}
diff --git a/erpnext/support/doctype/issue/issue.py b/erpnext/support/doctype/issue/issue.py
index b748e3fa46e..11fbc82a356 100644
--- a/erpnext/support/doctype/issue/issue.py
+++ b/erpnext/support/doctype/issue/issue.py
@@ -338,8 +338,13 @@ def get_issue_list(doctype, txt, filters, limit_start, limit_page_length=20, ord
ignore_permissions = False
if is_website_user():
- if not filters: filters = []
- filters.append(("Issue", "customer", "=", customer)) if customer else filters.append(("Issue", "raised_by", "=", user))
+ if not filters: filters = {}
+
+ if customer:
+ filters["customer"] = customer
+ else:
+ filters["raised_by"] = user
+
ignore_permissions = True
return get_list(doctype, txt, filters, limit_start, limit_page_length, ignore_permissions=ignore_permissions)
diff --git a/erpnext/support/web_form/issues/issues.json b/erpnext/support/web_form/issues/issues.json
index 652114f7382..1c0c8ff1584 100644
--- a/erpnext/support/web_form/issues/issues.json
+++ b/erpnext/support/web_form/issues/issues.json
@@ -18,7 +18,7 @@
"is_standard": 1,
"login_required": 1,
"max_attachment_size": 0,
- "modified": "2019-06-27 22:58:49.609672",
+ "modified": "2020-05-19 15:08:45.361878",
"modified_by": "Administrator",
"module": "Support",
"name": "issues",
@@ -58,7 +58,7 @@
"options": "Open\nReplied\nHold\nClosed",
"read_only": 1,
"reqd": 0,
- "show_in_filter": 0
+ "show_in_filter": 1
},
{
"allow_read_on_all_link_options": 0,
@@ -76,7 +76,7 @@
{
"allow_read_on_all_link_options": 0,
"fieldname": "description",
- "fieldtype": "Text",
+ "fieldtype": "Text Editor",
"hidden": 0,
"label": "Description",
"max_length": 0,
diff --git a/erpnext/templates/generators/item/item_configure.html b/erpnext/templates/generators/item/item_configure.html
index 04f89eca9db..b8b0d98bdc2 100644
--- a/erpnext/templates/generators/item/item_configure.html
+++ b/erpnext/templates/generators/item/item_configure.html
@@ -2,7 +2,7 @@
{% set cart_settings = shopping_cart.cart_settings %}
- {% if cart_settings.show_configure_button | int %}
+ {% if cart_settings.enable_variants | int %}
${__('Add to cart')}`;
+ product_action = in_stock ? add_to_cart : `${__('Not in Stock')} `;
+ } else {
+ product_info = '';
+ }
+
const item_add_to_cart = one_item ? `
${one_item} ${product_info && product_info.price ? '(' + product_info.price.formatted_price_sales_uom + ')' : ''}
-
- ${__('Add to cart')}
-
+ ${product_action}
`: '';
diff --git a/erpnext/templates/generators/item/item_inquiry.js b/erpnext/templates/generators/item/item_inquiry.js
index 52ddae2624c..e7db3a368df 100644
--- a/erpnext/templates/generators/item/item_inquiry.js
+++ b/erpnext/templates/generators/item/item_inquiry.js
@@ -20,6 +20,13 @@ frappe.ready(() => {
options: 'Email',
reqd: 1
},
+ {
+ fieldtype: 'Data',
+ label: __('Phone Number'),
+ fieldname: 'phone',
+ options: 'Phone',
+ reqd: 1
+ },
{
fieldtype: 'Data',
label: __('Subject'),
diff --git a/erpnext/templates/generators/student_admission.html b/erpnext/templates/generators/student_admission.html
index ae70df8b08b..8b153448eea 100644
--- a/erpnext/templates/generators/student_admission.html
+++ b/erpnext/templates/generators/student_admission.html
@@ -14,12 +14,12 @@
{%- if introduction -%}
{{ introduction }}
-{% endif %}
+{% endif %}
-{%- if application_form_route -%}
+{%- if doc.enable_admission_application -%}
+ href='/student-applicant'>
{{ _("Apply Now") }}
{% endif %}
diff --git a/erpnext/templates/includes/cart.js b/erpnext/templates/includes/cart.js
index 456bc7e4136..c6dfd35e29e 100644
--- a/erpnext/templates/includes/cart.js
+++ b/erpnext/templates/includes/cart.js
@@ -26,15 +26,14 @@ $.extend(shopping_cart, {
bind_address_select: function() {
$(".cart-addresses").on('click', '.address-card', function(e) {
const $card = $(e.currentTarget);
- const address_fieldname = $card.closest('[data-fieldname]').attr('data-fieldname');
+ const address_type = $card.closest('[data-address-type]').attr('data-address-type');
const address_name = $card.closest('[data-address-name]').attr('data-address-name');
-
return frappe.call({
type: "POST",
method: "erpnext.shopping_cart.cart.update_cart_address",
freeze: true,
args: {
- address_fieldname,
+ address_type,
address_name
},
callback: function(r) {
diff --git a/erpnext/templates/includes/cart/cart_address.html b/erpnext/templates/includes/cart/cart_address.html
index f7f35483208..60de3af17bf 100644
--- a/erpnext/templates/includes/cart/cart_address.html
+++ b/erpnext/templates/includes/cart/cart_address.html
@@ -18,7 +18,7 @@
{{ _("Shipping Address") }}
{% for address in shipping_addresses %}
-
+
{% include "templates/includes/cart/address_card.html" %}
{% endfor %}
@@ -28,7 +28,7 @@
{{ _("Billing Address") }}
{% for address in billing_addresses %}
-
+
{% include "templates/includes/cart/address_card.html" %}
{% endfor %}
@@ -123,9 +123,19 @@ frappe.ready(() => {
primary_action: (values) => {
frappe.call('erpnext.shopping_cart.cart.add_new_address', { doc: values })
.then(r => {
- d.hide();
- window.location.reload();
+ frappe.call({
+ method: "erpnext.shopping_cart.cart.update_cart_address",
+ args: {
+ address_type: r.message.address_type,
+ address_name: r.message.name
+ },
+ callback: function (r) {
+ d.hide();
+ window.location.reload();
+ }
+ });
});
+
}
})
diff --git a/erpnext/templates/includes/footer/footer_powered.html b/erpnext/templates/includes/footer/footer_powered.html
index cf7661ee3fa..f0f65a37618 100644
--- a/erpnext/templates/includes/footer/footer_powered.html
+++ b/erpnext/templates/includes/footer/footer_powered.html
@@ -10,9 +10,17 @@
'Agriculture': '/agriculture',
'Hospitality': ''
} %}
+
{% set link = '' %}
{% if domains %}
- {% set link = links[domains[0].domain] %}
+ {% set label = domains[0].domain %}
+ {% set link = links[label] %}
{% endif %}
-
Powered by ERPNext - {{ '' if domains else 'Open Source' }} ERP Software {{ ('for ' + domains[0].domain + ' Companies') if domains else '' }}
+{% if label == "Services" %}
+ {% set label = "Service" %}
+{% endif %}
+
+
+
+
Powered by ERPNext - {{ '' if domains else 'Open Source' }} ERP Software {{ ('for ' + label + ' Companies') if domains else '' }}
diff --git a/erpnext/templates/includes/itemised_tax_breakup.html b/erpnext/templates/includes/itemised_tax_breakup.html
index c27e4cede0d..c2f13539cdf 100644
--- a/erpnext/templates/includes/itemised_tax_breakup.html
+++ b/erpnext/templates/includes/itemised_tax_breakup.html
@@ -16,7 +16,11 @@
{{ item }}
- {{ frappe.utils.fmt_money(itemised_taxable_amount.get(item, 0), None, currency) }}
+ {% if doc.get('is_return') %}
+ {{ frappe.utils.fmt_money((itemised_taxable_amount.get(item, 0))|abs, None, doc.currency) }}
+ {% else %}
+ {{ frappe.utils.fmt_money(itemised_taxable_amount.get(item, 0), None, doc.currency) }}
+ {% endif %}
{% for tax_account in tax_accounts %}
{% set tax_details = taxes.get(tax_account) %}
@@ -25,7 +29,11 @@
{% if tax_details.tax_rate or not tax_details.tax_amount %}
({{ tax_details.tax_rate }}%)
{% endif %}
- {{ frappe.utils.fmt_money(tax_details.tax_amount / conversion_rate, None, currency) }}
+ {% if doc.get('is_return') %}
+ {{ frappe.utils.fmt_money((tax_details.tax_amount / doc.conversion_rate)|abs, None, doc.currency) }}
+ {% else %}
+ {{ frappe.utils.fmt_money(tax_details.tax_amount / doc.conversion_rate, None, doc.currency) }}
+ {% endif %}
{% else %}
diff --git a/erpnext/templates/includes/order/order_taxes.html b/erpnext/templates/includes/order/order_taxes.html
index 4a32aa47446..ebec838d03e 100644
--- a/erpnext/templates/includes/order/order_taxes.html
+++ b/erpnext/templates/includes/order/order_taxes.html
@@ -10,16 +10,16 @@
{% endif %}
{% for d in doc.taxes %}
-{% if d.base_tax_amount > 0 %}
-
-
- {{ d.description }}
-
-
- {{ d.get_formatted("base_tax_amount") }}
-
-
-{% endif %}
+ {% if d.base_tax_amount %}
+
+
+ {{ d.description }}
+
+
+ {{ d.get_formatted("base_tax_amount") }}
+
+
+ {% endif %}
{% endfor %}
{% if doc.doctype == 'Quotation' %}
diff --git a/erpnext/templates/includes/projects/project_row.html b/erpnext/templates/includes/projects/project_row.html
index 55b02e20045..73c83ef560b 100644
--- a/erpnext/templates/includes/projects/project_row.html
+++ b/erpnext/templates/includes/projects/project_row.html
@@ -1,6 +1,6 @@
{% if doc.status=="Open" %}
-
+
diff --git a/erpnext/templates/pages/cart.html b/erpnext/templates/pages/cart.html
index 912702e386d..3033d1587d6 100644
--- a/erpnext/templates/pages/cart.html
+++ b/erpnext/templates/pages/cart.html
@@ -12,16 +12,6 @@
{% block header_actions %}
-{% if doc.items and cart_settings.enable_checkout %}
-
- {{ _("Place Order") }}
-
-{% endif %}
-{% if doc.items and not cart_settings.enable_checkout %}
-
- {{ _("Request for Quotation") }}
-
-{% endif %}
{% endblock %}
{% block page_content %}
@@ -55,6 +45,20 @@
{{ _('Your cart is Empty') }}
{% endif %}
+ {% if doc.items %}
+
+ {% if cart_settings.enable_checkout %}
+
+ {{ _("Place Order") }}
+
+ {% else %}
+
+ {{ _("Request for Quotation") }}
+
+ {% endif %}
+
+ {% endif %}
+
{% if doc.items %}
{% if doc.tc_name %}
diff --git a/erpnext/tests/test_woocommerce.py b/erpnext/tests/test_woocommerce.py
index ce0f47d685f..df715ab2027 100644
--- a/erpnext/tests/test_woocommerce.py
+++ b/erpnext/tests/test_woocommerce.py
@@ -24,7 +24,7 @@ class TestWoocommerce(unittest.TestCase):
woo_settings.creation_user = "Administrator"
woo_settings.save(ignore_permissions=True)
- def test_sales_order_for_woocommerece(self):
+ def test_sales_order_for_woocommerce(self):
frappe.flags.woocomm_test_order_data = {"id":75,"parent_id":0,"number":"74","order_key":"wc_order_5aa1281c2dacb","created_via":"checkout","version":"3.3.3","status":"processing","currency":"INR","date_created":"2018-03-08T12:10:04","date_created_gmt":"2018-03-08T12:10:04","date_modified":"2018-03-08T12:10:04","date_modified_gmt":"2018-03-08T12:10:04","discount_total":"0.00","discount_tax":"0.00","shipping_total":"150.00","shipping_tax":"0.00","cart_tax":"0.00","total":"649.00","total_tax":"0.00","prices_include_tax":False,"customer_id":12,"customer_ip_address":"103.54.99.5","customer_user_agent":"mozilla\\/5.0 (x11; linux x86_64) applewebkit\\/537.36 (khtml, like gecko) chrome\\/64.0.3282.186 safari\\/537.36","customer_note":"","billing":{"first_name":"Tony","last_name":"Stark","company":"Woocommerce","address_1":"Mumbai","address_2":"","city":"Dadar","state":"MH","postcode":"123","country":"IN","email":"tony@gmail.com","phone":"123457890"},"shipping":{"first_name":"Tony","last_name":"Stark","company":"","address_1":"Mumbai","address_2":"","city":"Dadar","state":"MH","postcode":"123","country":"IN"},"payment_method":"cod","payment_method_title":"Cash on delivery","transaction_id":"","date_paid":"","date_paid_gmt":"","date_completed":"","date_completed_gmt":"","cart_hash":"8e76b020d5790066496f244860c4703f","meta_data":[],"line_items":[{"id":80,"name":"Marvel","product_id":56,"variation_id":0,"quantity":1,"tax_class":"","subtotal":"499.00","subtotal_tax":"0.00","total":"499.00","total_tax":"0.00","taxes":[],"meta_data":[],"sku":"","price":499}],"tax_lines":[],"shipping_lines":[{"id":81,"method_title":"Flat rate","method_id":"flat_rate:1","total":"150.00","total_tax":"0.00","taxes":[],"meta_data":[{"id":623,"key":"Items","value":"Marvel × 1"}]}],"fee_lines":[],"coupon_lines":[],"refunds":[]}
order()
diff --git a/erpnext/utilities/product.py b/erpnext/utilities/product.py
index a2867c8806a..c23c1f7096d 100644
--- a/erpnext/utilities/product.py
+++ b/erpnext/utilities/product.py
@@ -124,3 +124,12 @@ def get_price(item_code, price_list, customer_group, company, qty=1):
price_obj["formatted_price"] = ""
return price_obj
+
+def get_non_stock_item_status(item_code, item_warehouse_field):
+#if item belongs to product bundle, check if bundle items are in stock
+ if frappe.db.exists("Product Bundle", item_code):
+ items = frappe.get_doc("Product Bundle", item_code).get_all_children()
+ bundle_warehouse = frappe.db.get_value('Item', item_code, item_warehouse_field)
+ return all([ get_qty_in_stock(d.item_code, item_warehouse_field, bundle_warehouse).in_stock for d in items ])
+ else:
+ return 1
diff --git a/erpnext/utilities/transaction_base.py b/erpnext/utilities/transaction_base.py
index 20998108467..44fdc2f0a49 100644
--- a/erpnext/utilities/transaction_base.py
+++ b/erpnext/utilities/transaction_base.py
@@ -5,7 +5,7 @@ from __future__ import unicode_literals
import frappe
import frappe.share
from frappe import _
-from frappe.utils import cstr, now_datetime, cint, flt, get_time
+from frappe.utils import cstr, now_datetime, cint, flt, get_time, get_link_to_form
from erpnext.controllers.status_updater import StatusUpdater
from six import string_types
@@ -117,14 +117,26 @@ class TransactionBase(StatusUpdater):
def validate_rate_with_reference_doc(self, ref_details):
+ buying_doctypes = ["Purchase Order", "Purchase Invoice", "Purchase Receipt"]
+
+ if self.doctype in buying_doctypes:
+ to_disable = "Maintain same rate throughout Purchase cycle"
+ settings_page = "Buying Settings"
+ else:
+ to_disable = "Maintain same rate throughout Sales cycle"
+ settings_page = "Selling Settings"
+
for ref_dt, ref_dn_field, ref_link_field in ref_details:
for d in self.get("items"):
if d.get(ref_link_field):
ref_rate = frappe.db.get_value(ref_dt + " Item", d.get(ref_link_field), "rate")
if abs(flt(d.rate - ref_rate, d.precision("rate"))) >= .01:
- frappe.throw(_("Row #{0}: Rate must be same as {1}: {2} ({3} / {4}) ")
+ frappe.msgprint(_("Row #{0}: Rate must be same as {1}: {2} ({3} / {4}) ")
.format(d.idx, ref_dt, d.get(ref_dn_field), d.rate, ref_rate))
+ frappe.throw(_("To allow different rates, disable the {0} checkbox in {1}.")
+ .format(frappe.bold(_(to_disable)),
+ get_link_to_form(settings_page, settings_page, frappe.bold(settings_page))))
def get_link_filters(self, for_doctype):
if hasattr(self, "prev_link_mapper") and self.prev_link_mapper.get(for_doctype):
@@ -176,4 +188,6 @@ def validate_uom_is_integer(doc, uom_field, qty_fields, child_dt=None):
qty = d.get(f)
if qty:
if abs(cint(qty) - flt(qty)) > 0.0000001:
- frappe.throw(_("Quantity ({0}) cannot be a fraction in row {1}").format(qty, d.idx), UOMMustBeIntegerError)
+ frappe.throw(_("Row {1}: Quantity ({0}) cannot be a fraction. To allow this, disable '{2}' in UOM {3}.") \
+ .format(qty, d.idx, frappe.bold(_("Must be Whole Number")), frappe.bold(d.get(uom_field))),
+ UOMMustBeIntegerError)
diff --git a/erpnext/www/lms/content.html b/erpnext/www/lms/content.html
index 5607c0814d4..dc9b6d80fb5 100644
--- a/erpnext/www/lms/content.html
+++ b/erpnext/www/lms/content.html
@@ -59,25 +59,25 @@
{% macro title() %}
-
{{ content.name }} ({{ position + 1 }}/{{length}})
+ {{ content.name }} ({{ position + 1 }}/{{length}})
{% endmacro %}
{% macro navigation() %}
{% if previous %}
-
Previous
+
{{_('Previous')}}
{% else %}
-
Back to Course
+
{{ _('Back to Course') }}
{% endif %}
{% if next %}
-
Next
+
{{_('Next')}}
{% else %}
-
Finish Topic
+
{{_('Finish Topic')}}
{% endif %}
{% endmacro %}
@@ -86,7 +86,7 @@
{{ title() }}
{% if content.duration %}
- {{ content.duration }} Mins
+ {{ content.duration }} {{_('Mins')}}
{% endif %}
{% if content.publish_date and content.duration%}
@@ -94,7 +94,7 @@
{% endif %}
{% if content.publish_date %}
- Published on {{ content.publish_date.strftime('%d, %b %Y') }}
+ {{_('Published on')}} {{ content.publish_date.strftime('%d, %b %Y') }}
{% endif %}
@@ -109,13 +109,13 @@
{{ title() }}
{% if content.author or content.publish_date %}
- Published
+ {{_('Published')}}
{% endif %}
{% if content.author %}
- by {{ content.author }}
+ {{_('by')}} {{ content.author }}
{% endif %}
{% if content.publish_date %}
- on {{ content.publish_date.strftime('%d, %b %Y') }}
+ {{_('on')}} {{ content.publish_date.strftime('%d, %b %Y') }}
{% endif %}
@@ -205,4 +205,4 @@
{% endif %}
-{% endblock %}
\ No newline at end of file
+{% endblock %}
diff --git a/erpnext/www/lms/course.html b/erpnext/www/lms/course.html
index f2fd9363e8c..0d70ed5cefd 100644
--- a/erpnext/www/lms/course.html
+++ b/erpnext/www/lms/course.html
@@ -72,11 +72,11 @@
{% if has_access %}
diff --git a/erpnext/www/lms/index.html b/erpnext/www/lms/index.html
index ffb4419f367..7ce3521273f 100644
--- a/erpnext/www/lms/index.html
+++ b/erpnext/www/lms/index.html
@@ -45,7 +45,7 @@
{{ education_settings.description }}
{% if frappe.session.user == 'Guest' %}
- Sign Up
+ {{_('Sign Up')}}
{% endif %}
diff --git a/erpnext/www/lms/macros/card.html b/erpnext/www/lms/macros/card.html
index 076061d41b3..dc8fc5c72c7 100644
--- a/erpnext/www/lms/macros/card.html
+++ b/erpnext/www/lms/macros/card.html
@@ -15,8 +15,8 @@
{% if has_access or program.intro_video%}
{% endif %}
diff --git a/erpnext/www/lms/macros/hero.html b/erpnext/www/lms/macros/hero.html
index 66bb861c467..94f239eb8ed 100644
--- a/erpnext/www/lms/macros/hero.html
+++ b/erpnext/www/lms/macros/hero.html
@@ -2,16 +2,16 @@
{{ title }}
{{ description or ''}}
{% if frappe.session.user == 'Guest' %}
- Sign Up
+ {{_('Sign Up')}}
{% elif not has_access %}
- Enroll
+ {{_('Enroll')}}
{% endif %}
@@ -28,7 +28,7 @@
let btn = document.getElementById('enroll');
btn.disbaled = true;
- btn.innerText = 'Enrolling...'
+ btn.innerText = __('Enrolling...')
let opts = {
method: 'erpnext.education.utils.enroll_in_program',
@@ -44,7 +44,7 @@
window.location.reload()
}
})
- success_dialog.set_message('You have successfully enrolled for the program ');
+ success_dialog.set_message(__('You have successfully enrolled for the program '));
success_dialog.$message.show()
success_dialog.show();
btn.disbaled = false;
diff --git a/erpnext/www/lms/profile.html b/erpnext/www/lms/profile.html
index 9508daedb71..5755dfe6d8e 100644
--- a/erpnext/www/lms/profile.html
+++ b/erpnext/www/lms/profile.html
@@ -30,7 +30,7 @@
@@ -43,11 +43,11 @@
{{ student.first_name }} {{ student.last_name or '' }}
@@ -61,4 +61,4 @@
-{% endblock %}
\ No newline at end of file
+{% endblock %}
diff --git a/erpnext/www/lms/program.html b/erpnext/www/lms/program.html
index 271b7813bb4..7ad618630a4 100644
--- a/erpnext/www/lms/program.html
+++ b/erpnext/www/lms/program.html
@@ -55,11 +55,11 @@
{% if has_access and progress[course.name] %}
{% endif %}
diff --git a/erpnext/www/lms/topic.html b/erpnext/www/lms/topic.html
index 1f0d1876646..cd24616cd45 100644
--- a/erpnext/www/lms/topic.html
+++ b/erpnext/www/lms/topic.html
@@ -23,13 +23,13 @@
{% if has_access %}
diff --git a/package.json b/package.json
index 0e09cebf56e..9701bf3a21e 100644
--- a/package.json
+++ b/package.json
@@ -1,5 +1,14 @@
{
- "dependencies": {
- "cypress": "^3.1.4"
+ "name": "erpnext",
+ "description": "Open Source ERP System powered by the Frappe Framework",
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/frappe/erpnext.git"
+ },
+ "homepage": "https://erpnext.com",
+ "author": "Frappe Technologies Pvt. Ltd.",
+ "license": "GPL-3.0",
+ "bugs": {
+ "url": "https://github.com/frappe/erpnext/issues"
}
}
diff --git a/requirements.txt b/requirements.txt
index 28ba9f676ff..c277545fab5 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,10 +1,10 @@
+braintree==3.57.1
frappe
-unidecode
-pygithub
-googlemaps
-python-stdnum
-braintree
-gocardless_pro
-woocommerce
-pandas
-plaid-python
\ No newline at end of file
+gocardless-pro==1.11.0
+googlemaps==3.1.1
+pandas==0.24.2
+plaid-python==3.4.0
+PyGithub==1.44.1
+python-stdnum==1.12
+Unidecode==1.1.1
+WooCommerce==2.1.1
diff --git a/yarn.lock b/yarn.lock
index 6f1ff10839c..fb57ccd13af 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2,1305 +2,3 @@
# yarn lockfile v1
-"@cypress/listr-verbose-renderer@0.4.1":
- version "0.4.1"
- resolved "https://registry.yarnpkg.com/@cypress/listr-verbose-renderer/-/listr-verbose-renderer-0.4.1.tgz#a77492f4b11dcc7c446a34b3e28721afd33c642a"
- integrity sha1-p3SS9LEdzHxEajSz4ochr9M8ZCo=
- dependencies:
- chalk "^1.1.3"
- cli-cursor "^1.0.2"
- date-fns "^1.27.2"
- figures "^1.7.0"
-
-"@cypress/xvfb@1.2.3":
- version "1.2.3"
- resolved "https://registry.yarnpkg.com/@cypress/xvfb/-/xvfb-1.2.3.tgz#6319afdcdcff7d1505daeeaa84484d0596189860"
- integrity sha512-yYrK+/bgL3hwoRHMZG4r5fyLniCy1pXex5fimtewAY6vE/jsVs8Q37UsEO03tFlcmiLnQ3rBNMaZBYTi/+C1cw==
- dependencies:
- debug "^3.1.0"
- lodash.once "^4.1.1"
-
-"@types/blob-util@1.3.3":
- version "1.3.3"
- resolved "https://registry.yarnpkg.com/@types/blob-util/-/blob-util-1.3.3.tgz#adba644ae34f88e1dd9a5864c66ad651caaf628a"
- integrity sha512-4ahcL/QDnpjWA2Qs16ZMQif7HjGP2cw3AGjHabybjw7Vm1EKu+cfQN1D78BaZbS1WJNa1opSMF5HNMztx7lR0w==
-
-"@types/bluebird@3.5.18":
- version "3.5.18"
- resolved "https://registry.yarnpkg.com/@types/bluebird/-/bluebird-3.5.18.tgz#6a60435d4663e290f3709898a4f75014f279c4d6"
- integrity sha512-OTPWHmsyW18BhrnG5x8F7PzeZ2nFxmHGb42bZn79P9hl+GI5cMzyPgQTwNjbem0lJhoru/8vtjAFCUOu3+gE2w==
-
-"@types/chai-jquery@1.1.35":
- version "1.1.35"
- resolved "https://registry.yarnpkg.com/@types/chai-jquery/-/chai-jquery-1.1.35.tgz#9a8f0a39ec0851b2768a8f8c764158c2a2568d04"
- integrity sha512-7aIt9QMRdxuagLLI48dPz96YJdhu64p6FCa6n4qkGN5DQLHnrIjZpD9bXCvV2G0NwgZ1FAmfP214dxc5zNCfgQ==
- dependencies:
- "@types/chai" "*"
- "@types/jquery" "*"
-
-"@types/chai@*":
- version "4.1.7"
- resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.1.7.tgz#1b8e33b61a8c09cbe1f85133071baa0dbf9fa71a"
- integrity sha512-2Y8uPt0/jwjhQ6EiluT0XCri1Dbplr0ZxfFXUz+ye13gaqE8u5gL5ppao1JrUYr9cIip5S6MvQzBS7Kke7U9VA==
-
-"@types/chai@4.0.8":
- version "4.0.8"
- resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.0.8.tgz#d27600e9ba2f371e08695d90a0fe0408d89c7be7"
- integrity sha512-m812CONwdZn/dMzkIJEY0yAs4apyTkTORgfB2UsMOxgkUbC205AHnm4T8I0I5gPg9MHrFc1dJ35iS75c0CJkjg==
-
-"@types/jquery@*":
- version "3.3.29"
- resolved "https://registry.yarnpkg.com/@types/jquery/-/jquery-3.3.29.tgz#680a2219ce3c9250483722fccf5570d1e2d08abd"
- integrity sha512-FhJvBninYD36v3k6c+bVk1DSZwh7B5Dpb/Pyk3HKVsiohn0nhbefZZ+3JXbWQhFyt0MxSl2jRDdGQPHeOHFXrQ==
- dependencies:
- "@types/sizzle" "*"
-
-"@types/jquery@3.3.6":
- version "3.3.6"
- resolved "https://registry.yarnpkg.com/@types/jquery/-/jquery-3.3.6.tgz#5932ead926307ca21e5b36808257f7c926b06565"
- integrity sha512-403D4wN95Mtzt2EoQHARf5oe/jEPhzBOBNrunk+ydQGW8WmkQ/E8rViRAEB1qEt/vssfGfNVD6ujP4FVeegrLg==
-
-"@types/lodash@4.14.87":
- version "4.14.87"
- resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.87.tgz#55f92183b048c2c64402afe472f8333f4e319a6b"
- integrity sha512-AqRC+aEF4N0LuNHtcjKtvF9OTfqZI0iaBoe3dA6m/W+/YZJBZjBmW/QIZ8fBeXC6cnytSY9tBoFBqZ9uSCeVsw==
-
-"@types/minimatch@3.0.3":
- version "3.0.3"
- resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d"
- integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==
-
-"@types/mocha@2.2.44":
- version "2.2.44"
- resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-2.2.44.tgz#1d4a798e53f35212fd5ad4d04050620171cd5b5e"
- integrity sha512-k2tWTQU8G4+iSMvqKi0Q9IIsWAp/n8xzdZS4Q4YVIltApoMA00wFBFdlJnmoaK1/z7B0Cy0yPe6GgXteSmdUNw==
-
-"@types/sinon-chai@2.7.29":
- version "2.7.29"
- resolved "https://registry.yarnpkg.com/@types/sinon-chai/-/sinon-chai-2.7.29.tgz#4db01497e2dd1908b2bd30d1782f456353f5f723"
- integrity sha512-EkI/ZvJT4hglWo7Ipf9SX+J+R9htNOMjW8xiOhce7+0csqvgoF5IXqY5Ae1GqRgNtWCuaywR5HjVa1snkTqpOw==
- dependencies:
- "@types/chai" "*"
- "@types/sinon" "*"
-
-"@types/sinon@*":
- version "7.0.2"
- resolved "https://registry.yarnpkg.com/@types/sinon/-/sinon-7.0.2.tgz#234c03dd39bfa97616b28215caea3de043c63310"
- integrity sha512-YvJOqPk4kh1eQyxuASDD4MDK27XWAhtw6hJ7rRayEOkkTpZkqDWpDb4OjLVzFGdapOuUgZdnqO+71Q3utCJtcA==
-
-"@types/sinon@7.0.0":
- version "7.0.0"
- resolved "https://registry.yarnpkg.com/@types/sinon/-/sinon-7.0.0.tgz#84e707e157ec17d3e4c2a137f41fc3f416c0551e"
- integrity sha512-kcYoPw0uKioFVC/oOqafk2yizSceIQXCYnkYts9vJIwQklFRsMubTObTDrjQamUyBRd47332s85074cd/hCwxg==
-
-"@types/sizzle@*":
- version "2.3.2"
- resolved "https://registry.yarnpkg.com/@types/sizzle/-/sizzle-2.3.2.tgz#a811b8c18e2babab7d542b3365887ae2e4d9de47"
- integrity sha512-7EJYyKTL7tFR8+gDbB6Wwz/arpGa0Mywk1TJbNzKzHtzbwVmY4HR9WqS5VV7dsBUKQmPNr192jHr/VpBluj/hg==
-
-ajv@^5.1.0:
- version "5.5.2"
- resolved "https://registry.yarnpkg.com/ajv/-/ajv-5.5.2.tgz#73b5eeca3fab653e3d3f9422b341ad42205dc965"
- integrity sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=
- dependencies:
- co "^4.6.0"
- fast-deep-equal "^1.0.0"
- fast-json-stable-stringify "^2.0.0"
- json-schema-traverse "^0.3.0"
-
-ansi-escapes@^1.0.0:
- version "1.4.0"
- resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-1.4.0.tgz#d3a8a83b319aa67793662b13e761c7911422306e"
- integrity sha1-06ioOzGapneTZisT52HHkRQiMG4=
-
-ansi-regex@^2.0.0:
- version "2.1.1"
- resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df"
- integrity sha1-w7M6te42DYbg5ijwRorn7yfWVN8=
-
-ansi-styles@^2.2.1:
- version "2.2.1"
- resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe"
- integrity sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=
-
-ansi-styles@^3.2.1:
- version "3.2.1"
- resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d"
- integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==
- dependencies:
- color-convert "^1.9.0"
-
-asn1@~0.2.3:
- version "0.2.4"
- resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136"
- integrity sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==
- dependencies:
- safer-buffer "~2.1.0"
-
-assert-plus@1.0.0, assert-plus@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525"
- integrity sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=
-
-async@2.4.0:
- version "2.4.0"
- resolved "https://registry.yarnpkg.com/async/-/async-2.4.0.tgz#4990200f18ea5b837c2cc4f8c031a6985c385611"
- integrity sha1-SZAgDxjqW4N8LMT4wDGmmFw4VhE=
- dependencies:
- lodash "^4.14.0"
-
-asynckit@^0.4.0:
- version "0.4.0"
- resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
- integrity sha1-x57Zf380y48robyXkLzDZkdLS3k=
-
-aws-sign2@~0.7.0:
- version "0.7.0"
- resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8"
- integrity sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=
-
-aws4@^1.6.0:
- version "1.8.0"
- resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.8.0.tgz#f0e003d9ca9e7f59c7a508945d7b2ef9a04a542f"
- integrity sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==
-
-babel-runtime@^6.18.0:
- version "6.26.0"
- resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe"
- integrity sha1-llxwWGaOgrVde/4E/yM3vItWR/4=
- dependencies:
- core-js "^2.4.0"
- regenerator-runtime "^0.11.0"
-
-balanced-match@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
- integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c=
-
-bcrypt-pbkdf@^1.0.0:
- version "1.0.2"
- resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e"
- integrity sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=
- dependencies:
- tweetnacl "^0.14.3"
-
-bluebird@3.5.0:
- version "3.5.0"
- resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.0.tgz#791420d7f551eea2897453a8a77653f96606d67c"
- integrity sha1-eRQg1/VR7qKJdFOop3ZT+WYG1nw=
-
-brace-expansion@^1.1.7:
- version "1.1.11"
- resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
- integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==
- dependencies:
- balanced-match "^1.0.0"
- concat-map "0.0.1"
-
-buffer-crc32@~0.2.3:
- version "0.2.13"
- resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242"
- integrity sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=
-
-cachedir@1.3.0:
- version "1.3.0"
- resolved "https://registry.yarnpkg.com/cachedir/-/cachedir-1.3.0.tgz#5e01928bf2d95b5edd94b0942188246740e0dbc4"
- integrity sha512-O1ji32oyON9laVPJL1IZ5bmwd2cB46VfpxkDequezH+15FDzzVddEyrGEeX4WusDSqKxdyFdDQDEG1yo1GoWkg==
- dependencies:
- os-homedir "^1.0.1"
-
-caseless@~0.12.0:
- version "0.12.0"
- resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc"
- integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=
-
-chalk@2.4.1, chalk@^2.0.1:
- version "2.4.1"
- resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.1.tgz#18c49ab16a037b6eb0152cc83e3471338215b66e"
- integrity sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==
- dependencies:
- ansi-styles "^3.2.1"
- escape-string-regexp "^1.0.5"
- supports-color "^5.3.0"
-
-chalk@^1.0.0, chalk@^1.1.1, chalk@^1.1.3:
- version "1.1.3"
- resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98"
- integrity sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=
- dependencies:
- ansi-styles "^2.2.1"
- escape-string-regexp "^1.0.2"
- has-ansi "^2.0.0"
- strip-ansi "^3.0.0"
- supports-color "^2.0.0"
-
-check-more-types@2.24.0:
- version "2.24.0"
- resolved "https://registry.yarnpkg.com/check-more-types/-/check-more-types-2.24.0.tgz#1420ffb10fd444dcfc79b43891bbfffd32a84600"
- integrity sha1-FCD/sQ/URNz8ebQ4kbv//TKoRgA=
-
-ci-info@^1.0.0:
- version "1.6.0"
- resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-1.6.0.tgz#2ca20dbb9ceb32d4524a683303313f0304b1e497"
- integrity sha512-vsGdkwSCDpWmP80ncATX7iea5DWQemg1UgCW5J8tqjU3lYw4FBYuj89J0CTVomA7BEfvSZd84GmHko+MxFQU2A==
-
-cli-cursor@^1.0.2:
- version "1.0.2"
- resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-1.0.2.tgz#64da3f7d56a54412e59794bd62dc35295e8f2987"
- integrity sha1-ZNo/fValRBLll5S9Ytw1KV6PKYc=
- dependencies:
- restore-cursor "^1.0.1"
-
-cli-spinners@^0.1.2:
- version "0.1.2"
- resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-0.1.2.tgz#bb764d88e185fb9e1e6a2a1f19772318f605e31c"
- integrity sha1-u3ZNiOGF+54eaiofGXcjGPYF4xw=
-
-cli-truncate@^0.2.1:
- version "0.2.1"
- resolved "https://registry.yarnpkg.com/cli-truncate/-/cli-truncate-0.2.1.tgz#9f15cfbb0705005369216c626ac7d05ab90dd574"
- integrity sha1-nxXPuwcFAFNpIWxiasfQWrkN1XQ=
- dependencies:
- slice-ansi "0.0.4"
- string-width "^1.0.1"
-
-co@^4.6.0:
- version "4.6.0"
- resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184"
- integrity sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=
-
-code-point-at@^1.0.0:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77"
- integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=
-
-color-convert@^1.9.0:
- version "1.9.3"
- resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8"
- integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==
- dependencies:
- color-name "1.1.3"
-
-color-name@1.1.3:
- version "1.1.3"
- resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25"
- integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=
-
-combined-stream@^1.0.6, combined-stream@~1.0.5:
- version "1.0.7"
- resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.7.tgz#2d1d24317afb8abe95d6d2c0b07b57813539d828"
- integrity sha512-brWl9y6vOB1xYPZcpZde3N9zDByXTosAeMDo4p1wzo6UMOX4vumB+TP1RZ76sfE6Md68Q0NJSrE/gbezd4Ul+w==
- dependencies:
- delayed-stream "~1.0.0"
-
-commander@2.11.0:
- version "2.11.0"
- resolved "https://registry.yarnpkg.com/commander/-/commander-2.11.0.tgz#157152fd1e7a6c8d98a5b715cf376df928004563"
- integrity sha512-b0553uYA5YAEGgyYIGYROzKQ7X5RAqedkfjiZxwi0kL1g3bOaBNNZfYkzt/CL0umgD5wc9Jec2FbB98CjkMRvQ==
-
-common-tags@1.4.0:
- version "1.4.0"
- resolved "https://registry.yarnpkg.com/common-tags/-/common-tags-1.4.0.tgz#1187be4f3d4cf0c0427d43f74eef1f73501614c0"
- integrity sha1-EYe+Tz1M8MBCfUP3Tu8fc1AWFMA=
- dependencies:
- babel-runtime "^6.18.0"
-
-concat-map@0.0.1:
- version "0.0.1"
- resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
- integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=
-
-concat-stream@1.6.0:
- version "1.6.0"
- resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.0.tgz#0aac662fd52be78964d5532f694784e70110acf7"
- integrity sha1-CqxmL9Ur54lk1VMvaUeE5wEQrPc=
- dependencies:
- inherits "^2.0.3"
- readable-stream "^2.2.2"
- typedarray "^0.0.6"
-
-core-js@^2.4.0:
- version "2.6.1"
- resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.1.tgz#87416ae817de957a3f249b3b5ca475d4aaed6042"
- integrity sha512-L72mmmEayPJBejKIWe2pYtGis5r0tQ5NaJekdhyXgeMQTpJoBsH0NL4ElY2LfSoV15xeQWKQ+XTTOZdyero5Xg==
-
-core-util-is@1.0.2, core-util-is@~1.0.0:
- version "1.0.2"
- resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
- integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=
-
-cross-spawn@^6.0.0:
- version "6.0.5"
- resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4"
- integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==
- dependencies:
- nice-try "^1.0.4"
- path-key "^2.0.1"
- semver "^5.5.0"
- shebang-command "^1.2.0"
- which "^1.2.9"
-
-cypress@^3.1.4:
- version "3.1.4"
- resolved "https://registry.yarnpkg.com/cypress/-/cypress-3.1.4.tgz#2af04da05e09f9d3871d05713b364472744c4216"
- integrity sha512-8VJYtCAFqHXMnRDo4vdomR2CqfmhtReoplmbkXVspeKhKxU8WsZl0Nh5yeil8txxhq+YQwDrInItUqIm35Vw+g==
- dependencies:
- "@cypress/listr-verbose-renderer" "0.4.1"
- "@cypress/xvfb" "1.2.3"
- "@types/blob-util" "1.3.3"
- "@types/bluebird" "3.5.18"
- "@types/chai" "4.0.8"
- "@types/chai-jquery" "1.1.35"
- "@types/jquery" "3.3.6"
- "@types/lodash" "4.14.87"
- "@types/minimatch" "3.0.3"
- "@types/mocha" "2.2.44"
- "@types/sinon" "7.0.0"
- "@types/sinon-chai" "2.7.29"
- bluebird "3.5.0"
- cachedir "1.3.0"
- chalk "2.4.1"
- check-more-types "2.24.0"
- commander "2.11.0"
- common-tags "1.4.0"
- debug "3.1.0"
- execa "0.10.0"
- executable "4.1.1"
- extract-zip "1.6.6"
- fs-extra "4.0.1"
- getos "3.1.0"
- glob "7.1.2"
- is-ci "1.0.10"
- is-installed-globally "0.1.0"
- lazy-ass "1.6.0"
- listr "0.12.0"
- lodash "4.17.11"
- log-symbols "2.2.0"
- minimist "1.2.0"
- moment "2.22.2"
- ramda "0.24.1"
- request "2.87.0"
- request-progress "0.3.1"
- supports-color "5.1.0"
- tmp "0.0.31"
- url "0.11.0"
- yauzl "2.8.0"
-
-dashdash@^1.12.0:
- version "1.14.1"
- resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0"
- integrity sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=
- dependencies:
- assert-plus "^1.0.0"
-
-date-fns@^1.27.2:
- version "1.30.1"
- resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-1.30.1.tgz#2e71bf0b119153dbb4cc4e88d9ea5acfb50dc05c"
- integrity sha512-hBSVCvSmWC+QypYObzwGOd9wqdDpOt+0wl0KbU+R+uuZBS1jN8VsD1ss3irQDknRj5NvxiTF6oj/nDRnN/UQNw==
-
-debug@2.6.9:
- version "2.6.9"
- resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
- integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==
- dependencies:
- ms "2.0.0"
-
-debug@3.1.0:
- version "3.1.0"
- resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
- integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==
- dependencies:
- ms "2.0.0"
-
-debug@^3.1.0:
- version "3.2.6"
- resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b"
- integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==
- dependencies:
- ms "^2.1.1"
-
-delayed-stream@~1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
- integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk=
-
-ecc-jsbn@~0.1.1:
- version "0.1.2"
- resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9"
- integrity sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=
- dependencies:
- jsbn "~0.1.0"
- safer-buffer "^2.1.0"
-
-elegant-spinner@^1.0.1:
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/elegant-spinner/-/elegant-spinner-1.0.1.tgz#db043521c95d7e303fd8f345bedc3349cfb0729e"
- integrity sha1-2wQ1IcldfjA/2PNFvtwzSc+wcp4=
-
-escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5:
- version "1.0.5"
- resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
- integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=
-
-execa@0.10.0:
- version "0.10.0"
- resolved "https://registry.yarnpkg.com/execa/-/execa-0.10.0.tgz#ff456a8f53f90f8eccc71a96d11bdfc7f082cb50"
- integrity sha512-7XOMnz8Ynx1gGo/3hyV9loYNPWM94jG3+3T3Y8tsfSstFmETmENCMU/A/zj8Lyaj1lkgEepKepvd6240tBRvlw==
- dependencies:
- cross-spawn "^6.0.0"
- get-stream "^3.0.0"
- is-stream "^1.1.0"
- npm-run-path "^2.0.0"
- p-finally "^1.0.0"
- signal-exit "^3.0.0"
- strip-eof "^1.0.0"
-
-executable@4.1.1:
- version "4.1.1"
- resolved "https://registry.yarnpkg.com/executable/-/executable-4.1.1.tgz#41532bff361d3e57af4d763b70582db18f5d133c"
- integrity sha512-8iA79xD3uAch729dUG8xaaBBFGaEa0wdD2VkYLFHwlqosEj/jT66AzcreRDSgV7ehnNLBW2WR5jIXwGKjVdTLg==
- dependencies:
- pify "^2.2.0"
-
-exit-hook@^1.0.0:
- version "1.1.1"
- resolved "https://registry.yarnpkg.com/exit-hook/-/exit-hook-1.1.1.tgz#f05ca233b48c05d54fff07765df8507e95c02ff8"
- integrity sha1-8FyiM7SMBdVP/wd2XfhQfpXAL/g=
-
-extend@~3.0.1:
- version "3.0.2"
- resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa"
- integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==
-
-extract-zip@1.6.6:
- version "1.6.6"
- resolved "https://registry.yarnpkg.com/extract-zip/-/extract-zip-1.6.6.tgz#1290ede8d20d0872b429fd3f351ca128ec5ef85c"
- integrity sha1-EpDt6NINCHK0Kf0/NRyhKOxe+Fw=
- dependencies:
- concat-stream "1.6.0"
- debug "2.6.9"
- mkdirp "0.5.0"
- yauzl "2.4.1"
-
-extsprintf@1.3.0:
- version "1.3.0"
- resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05"
- integrity sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=
-
-extsprintf@^1.2.0:
- version "1.4.0"
- resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f"
- integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8=
-
-fast-deep-equal@^1.0.0:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz#c053477817c86b51daa853c81e059b733d023614"
- integrity sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=
-
-fast-json-stable-stringify@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz#d5142c0caee6b1189f87d3a76111064f86c8bbf2"
- integrity sha1-1RQsDK7msRifh9OnYREGT4bIu/I=
-
-fd-slicer@~1.0.1:
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.0.1.tgz#8b5bcbd9ec327c5041bf9ab023fd6750f1177e65"
- integrity sha1-i1vL2ewyfFBBv5qwI/1nUPEXfmU=
- dependencies:
- pend "~1.2.0"
-
-figures@^1.7.0:
- version "1.7.0"
- resolved "https://registry.yarnpkg.com/figures/-/figures-1.7.0.tgz#cbe1e3affcf1cd44b80cadfed28dc793a9701d2e"
- integrity sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=
- dependencies:
- escape-string-regexp "^1.0.5"
- object-assign "^4.1.0"
-
-forever-agent@~0.6.1:
- version "0.6.1"
- resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91"
- integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=
-
-form-data@~2.3.1:
- version "2.3.3"
- resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6"
- integrity sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==
- dependencies:
- asynckit "^0.4.0"
- combined-stream "^1.0.6"
- mime-types "^2.1.12"
-
-fs-extra@4.0.1:
- version "4.0.1"
- resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-4.0.1.tgz#7fc0c6c8957f983f57f306a24e5b9ddd8d0dd880"
- integrity sha1-f8DGyJV/mD9X8waiTlud3Y0N2IA=
- dependencies:
- graceful-fs "^4.1.2"
- jsonfile "^3.0.0"
- universalify "^0.1.0"
-
-fs.realpath@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
- integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8=
-
-get-stream@^3.0.0:
- version "3.0.0"
- resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14"
- integrity sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=
-
-getos@3.1.0:
- version "3.1.0"
- resolved "https://registry.yarnpkg.com/getos/-/getos-3.1.0.tgz#db3aa4df15a3295557ce5e81aa9e3e5cdfaa6567"
- integrity sha512-i9vrxtDu5DlLVFcrbqUqGWYlZN/zZ4pGMICCAcZoYsX3JA54nYp8r5EThw5K+m2q3wszkx4Th746JstspB0H4Q==
- dependencies:
- async "2.4.0"
-
-getpass@^0.1.1:
- version "0.1.7"
- resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa"
- integrity sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=
- dependencies:
- assert-plus "^1.0.0"
-
-glob@7.1.2:
- version "7.1.2"
- resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15"
- integrity sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==
- dependencies:
- fs.realpath "^1.0.0"
- inflight "^1.0.4"
- inherits "2"
- minimatch "^3.0.4"
- once "^1.3.0"
- path-is-absolute "^1.0.0"
-
-global-dirs@^0.1.0:
- version "0.1.1"
- resolved "https://registry.yarnpkg.com/global-dirs/-/global-dirs-0.1.1.tgz#b319c0dd4607f353f3be9cca4c72fc148c49f445"
- integrity sha1-sxnA3UYH81PzvpzKTHL8FIxJ9EU=
- dependencies:
- ini "^1.3.4"
-
-graceful-fs@^4.1.2, graceful-fs@^4.1.6:
- version "4.1.15"
- resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.15.tgz#ffb703e1066e8a0eeaa4c8b80ba9253eeefbfb00"
- integrity sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==
-
-har-schema@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92"
- integrity sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=
-
-har-validator@~5.0.3:
- version "5.0.3"
- resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.0.3.tgz#ba402c266194f15956ef15e0fcf242993f6a7dfd"
- integrity sha1-ukAsJmGU8VlW7xXg/PJCmT9qff0=
- dependencies:
- ajv "^5.1.0"
- har-schema "^2.0.0"
-
-has-ansi@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91"
- integrity sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=
- dependencies:
- ansi-regex "^2.0.0"
-
-has-flag@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-2.0.0.tgz#e8207af1cc7b30d446cc70b734b5e8be18f88d51"
- integrity sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=
-
-has-flag@^3.0.0:
- version "3.0.0"
- resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd"
- integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0=
-
-http-signature@~1.2.0:
- version "1.2.0"
- resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1"
- integrity sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=
- dependencies:
- assert-plus "^1.0.0"
- jsprim "^1.2.2"
- sshpk "^1.7.0"
-
-indent-string@^2.1.0:
- version "2.1.0"
- resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-2.1.0.tgz#8e2d48348742121b4a8218b7a137e9a52049dc80"
- integrity sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=
- dependencies:
- repeating "^2.0.0"
-
-indent-string@^3.0.0:
- version "3.2.0"
- resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-3.2.0.tgz#4a5fd6d27cc332f37e5419a504dbb837105c9289"
- integrity sha1-Sl/W0nzDMvN+VBmlBNu4NxBckok=
-
-inflight@^1.0.4:
- version "1.0.6"
- resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9"
- integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=
- dependencies:
- once "^1.3.0"
- wrappy "1"
-
-inherits@2, inherits@^2.0.3, inherits@~2.0.3:
- version "2.0.3"
- resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
- integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=
-
-ini@^1.3.4:
- version "1.3.5"
- resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927"
- integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==
-
-is-ci@1.0.10:
- version "1.0.10"
- resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-1.0.10.tgz#f739336b2632365061a9d48270cd56ae3369318e"
- integrity sha1-9zkzayYyNlBhqdSCcM1WrjNpMY4=
- dependencies:
- ci-info "^1.0.0"
-
-is-finite@^1.0.0:
- version "1.0.2"
- resolved "https://registry.yarnpkg.com/is-finite/-/is-finite-1.0.2.tgz#cc6677695602be550ef11e8b4aa6305342b6d0aa"
- integrity sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko=
- dependencies:
- number-is-nan "^1.0.0"
-
-is-fullwidth-code-point@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb"
- integrity sha1-754xOG8DGn8NZDr4L95QxFfvAMs=
- dependencies:
- number-is-nan "^1.0.0"
-
-is-installed-globally@0.1.0:
- version "0.1.0"
- resolved "https://registry.yarnpkg.com/is-installed-globally/-/is-installed-globally-0.1.0.tgz#0dfd98f5a9111716dd535dda6492f67bf3d25a80"
- integrity sha1-Df2Y9akRFxbdU13aZJL2e/PSWoA=
- dependencies:
- global-dirs "^0.1.0"
- is-path-inside "^1.0.0"
-
-is-path-inside@^1.0.0:
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-1.0.1.tgz#8ef5b7de50437a3fdca6b4e865ef7aa55cb48036"
- integrity sha1-jvW33lBDej/cprToZe96pVy0gDY=
- dependencies:
- path-is-inside "^1.0.1"
-
-is-promise@^2.1.0:
- version "2.1.0"
- resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.1.0.tgz#79a2a9ece7f096e80f36d2b2f3bc16c1ff4bf3fa"
- integrity sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=
-
-is-stream@^1.1.0:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44"
- integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ=
-
-is-typedarray@~1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a"
- integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=
-
-isarray@~1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
- integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=
-
-isexe@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
- integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=
-
-isstream@~0.1.2:
- version "0.1.2"
- resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a"
- integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=
-
-jsbn@~0.1.0:
- version "0.1.1"
- resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513"
- integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM=
-
-json-schema-traverse@^0.3.0:
- version "0.3.1"
- resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz#349a6d44c53a51de89b40805c5d5e59b417d3340"
- integrity sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=
-
-json-schema@0.2.3:
- version "0.2.3"
- resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13"
- integrity sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=
-
-json-stringify-safe@~5.0.1:
- version "5.0.1"
- resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb"
- integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=
-
-jsonfile@^3.0.0:
- version "3.0.1"
- resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-3.0.1.tgz#a5ecc6f65f53f662c4415c7675a0331d0992ec66"
- integrity sha1-pezG9l9T9mLEQVx2daAzHQmS7GY=
- optionalDependencies:
- graceful-fs "^4.1.6"
-
-jsprim@^1.2.2:
- version "1.4.1"
- resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2"
- integrity sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=
- dependencies:
- assert-plus "1.0.0"
- extsprintf "1.3.0"
- json-schema "0.2.3"
- verror "1.10.0"
-
-lazy-ass@1.6.0:
- version "1.6.0"
- resolved "https://registry.yarnpkg.com/lazy-ass/-/lazy-ass-1.6.0.tgz#7999655e8646c17f089fdd187d150d3324d54513"
- integrity sha1-eZllXoZGwX8In90YfRUNMyTVRRM=
-
-listr-silent-renderer@^1.1.1:
- version "1.1.1"
- resolved "https://registry.yarnpkg.com/listr-silent-renderer/-/listr-silent-renderer-1.1.1.tgz#924b5a3757153770bf1a8e3fbf74b8bbf3f9242e"
- integrity sha1-kktaN1cVN3C/Go4/v3S4u/P5JC4=
-
-listr-update-renderer@^0.2.0:
- version "0.2.0"
- resolved "https://registry.yarnpkg.com/listr-update-renderer/-/listr-update-renderer-0.2.0.tgz#ca80e1779b4e70266807e8eed1ad6abe398550f9"
- integrity sha1-yoDhd5tOcCZoB+ju0a1qvjmFUPk=
- dependencies:
- chalk "^1.1.3"
- cli-truncate "^0.2.1"
- elegant-spinner "^1.0.1"
- figures "^1.7.0"
- indent-string "^3.0.0"
- log-symbols "^1.0.2"
- log-update "^1.0.2"
- strip-ansi "^3.0.1"
-
-listr-verbose-renderer@^0.4.0:
- version "0.4.1"
- resolved "https://registry.yarnpkg.com/listr-verbose-renderer/-/listr-verbose-renderer-0.4.1.tgz#8206f4cf6d52ddc5827e5fd14989e0e965933a35"
- integrity sha1-ggb0z21S3cWCfl/RSYng6WWTOjU=
- dependencies:
- chalk "^1.1.3"
- cli-cursor "^1.0.2"
- date-fns "^1.27.2"
- figures "^1.7.0"
-
-listr@0.12.0:
- version "0.12.0"
- resolved "https://registry.yarnpkg.com/listr/-/listr-0.12.0.tgz#6bce2c0f5603fa49580ea17cd6a00cc0e5fa451a"
- integrity sha1-a84sD1YD+klYDqF81qAMwOX6RRo=
- dependencies:
- chalk "^1.1.3"
- cli-truncate "^0.2.1"
- figures "^1.7.0"
- indent-string "^2.1.0"
- is-promise "^2.1.0"
- is-stream "^1.1.0"
- listr-silent-renderer "^1.1.1"
- listr-update-renderer "^0.2.0"
- listr-verbose-renderer "^0.4.0"
- log-symbols "^1.0.2"
- log-update "^1.0.2"
- ora "^0.2.3"
- p-map "^1.1.1"
- rxjs "^5.0.0-beta.11"
- stream-to-observable "^0.1.0"
- strip-ansi "^3.0.1"
-
-lodash.once@^4.1.1:
- version "4.1.1"
- resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac"
- integrity sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=
-
-lodash@4.17.11, lodash@^4.14.0:
- version "4.17.11"
- resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d"
- integrity sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==
-
-log-symbols@2.2.0:
- version "2.2.0"
- resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-2.2.0.tgz#5740e1c5d6f0dfda4ad9323b5332107ef6b4c40a"
- integrity sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==
- dependencies:
- chalk "^2.0.1"
-
-log-symbols@^1.0.2:
- version "1.0.2"
- resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-1.0.2.tgz#376ff7b58ea3086a0f09facc74617eca501e1a18"
- integrity sha1-N2/3tY6jCGoPCfrMdGF+ylAeGhg=
- dependencies:
- chalk "^1.0.0"
-
-log-update@^1.0.2:
- version "1.0.2"
- resolved "https://registry.yarnpkg.com/log-update/-/log-update-1.0.2.tgz#19929f64c4093d2d2e7075a1dad8af59c296b8d1"
- integrity sha1-GZKfZMQJPS0ucHWh2tivWcKWuNE=
- dependencies:
- ansi-escapes "^1.0.0"
- cli-cursor "^1.0.2"
-
-mime-db@~1.37.0:
- version "1.37.0"
- resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.37.0.tgz#0b6a0ce6fdbe9576e25f1f2d2fde8830dc0ad0d8"
- integrity sha512-R3C4db6bgQhlIhPU48fUtdVmKnflq+hRdad7IyKhtFj06VPNVdk2RhiYL3UjQIlso8L+YxAtFkobT0VK+S/ybg==
-
-mime-types@^2.1.12, mime-types@~2.1.17:
- version "2.1.21"
- resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.21.tgz#28995aa1ecb770742fe6ae7e58f9181c744b3f96"
- integrity sha512-3iL6DbwpyLzjR3xHSFNFeb9Nz/M8WDkX33t1GFQnFOllWk8pOrh/LSrB5OXlnlW5P9LH73X6loW/eogc+F5lJg==
- dependencies:
- mime-db "~1.37.0"
-
-minimatch@^3.0.4:
- version "3.0.4"
- resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
- integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==
- dependencies:
- brace-expansion "^1.1.7"
-
-minimist@0.0.8:
- version "0.0.8"
- resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d"
- integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=
-
-minimist@1.2.0:
- version "1.2.0"
- resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284"
- integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=
-
-mkdirp@0.5.0:
- version "0.5.0"
- resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.0.tgz#1d73076a6df986cd9344e15e71fcc05a4c9abf12"
- integrity sha1-HXMHam35hs2TROFecfzAWkyavxI=
- dependencies:
- minimist "0.0.8"
-
-moment@2.22.2:
- version "2.22.2"
- resolved "https://registry.yarnpkg.com/moment/-/moment-2.22.2.tgz#3c257f9839fc0e93ff53149632239eb90783ff66"
- integrity sha1-PCV/mDn8DpP/UxSWMiOeuQeD/2Y=
-
-ms@2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
- integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=
-
-ms@^2.1.1:
- version "2.1.1"
- resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a"
- integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==
-
-nice-try@^1.0.4:
- version "1.0.5"
- resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366"
- integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==
-
-npm-run-path@^2.0.0:
- version "2.0.2"
- resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f"
- integrity sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=
- dependencies:
- path-key "^2.0.0"
-
-number-is-nan@^1.0.0:
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d"
- integrity sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=
-
-oauth-sign@~0.8.2:
- version "0.8.2"
- resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.8.2.tgz#46a6ab7f0aead8deae9ec0565780b7d4efeb9d43"
- integrity sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=
-
-object-assign@^4.0.1, object-assign@^4.1.0:
- version "4.1.1"
- resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
- integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=
-
-once@^1.3.0:
- version "1.4.0"
- resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
- integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E=
- dependencies:
- wrappy "1"
-
-onetime@^1.0.0:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/onetime/-/onetime-1.1.0.tgz#a1f7838f8314c516f05ecefcbc4ccfe04b4ed789"
- integrity sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=
-
-ora@^0.2.3:
- version "0.2.3"
- resolved "https://registry.yarnpkg.com/ora/-/ora-0.2.3.tgz#37527d220adcd53c39b73571d754156d5db657a4"
- integrity sha1-N1J9Igrc1Tw5tzVx11QVbV22V6Q=
- dependencies:
- chalk "^1.1.1"
- cli-cursor "^1.0.2"
- cli-spinners "^0.1.2"
- object-assign "^4.0.1"
-
-os-homedir@^1.0.1:
- version "1.0.2"
- resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3"
- integrity sha1-/7xJiDNuDoM94MFox+8VISGqf7M=
-
-os-tmpdir@~1.0.1:
- version "1.0.2"
- resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274"
- integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=
-
-p-finally@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae"
- integrity sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=
-
-p-map@^1.1.1:
- version "1.2.0"
- resolved "https://registry.yarnpkg.com/p-map/-/p-map-1.2.0.tgz#e4e94f311eabbc8633a1e79908165fca26241b6b"
- integrity sha512-r6zKACMNhjPJMTl8KcFH4li//gkrXWfbD6feV8l6doRHlzljFWGJ2AP6iKaCJXyZmAUMOPtvbW7EXkbWO/pLEA==
-
-path-is-absolute@^1.0.0:
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
- integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18=
-
-path-is-inside@^1.0.1:
- version "1.0.2"
- resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53"
- integrity sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=
-
-path-key@^2.0.0, path-key@^2.0.1:
- version "2.0.1"
- resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40"
- integrity sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=
-
-pend@~1.2.0:
- version "1.2.0"
- resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50"
- integrity sha1-elfrVQpng/kRUzH89GY9XI4AelA=
-
-performance-now@^2.1.0:
- version "2.1.0"
- resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b"
- integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=
-
-pify@^2.2.0:
- version "2.3.0"
- resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c"
- integrity sha1-7RQaasBDqEnqWISY59yosVMw6Qw=
-
-process-nextick-args@~2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.0.tgz#a37d732f4271b4ab1ad070d35508e8290788ffaa"
- integrity sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==
-
-punycode@1.3.2:
- version "1.3.2"
- resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d"
- integrity sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=
-
-punycode@^1.4.1:
- version "1.4.1"
- resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e"
- integrity sha1-wNWmOycYgArY4esPpSachN1BhF4=
-
-qs@~6.5.1:
- version "6.5.2"
- resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36"
- integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==
-
-querystring@0.2.0:
- version "0.2.0"
- resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620"
- integrity sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=
-
-ramda@0.24.1:
- version "0.24.1"
- resolved "https://registry.yarnpkg.com/ramda/-/ramda-0.24.1.tgz#c3b7755197f35b8dc3502228262c4c91ddb6b857"
- integrity sha1-w7d1UZfzW43DUCIoJixMkd22uFc=
-
-readable-stream@^2.2.2:
- version "2.3.6"
- resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf"
- integrity sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==
- dependencies:
- core-util-is "~1.0.0"
- inherits "~2.0.3"
- isarray "~1.0.0"
- process-nextick-args "~2.0.0"
- safe-buffer "~5.1.1"
- string_decoder "~1.1.1"
- util-deprecate "~1.0.1"
-
-regenerator-runtime@^0.11.0:
- version "0.11.1"
- resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9"
- integrity sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==
-
-repeating@^2.0.0:
- version "2.0.1"
- resolved "https://registry.yarnpkg.com/repeating/-/repeating-2.0.1.tgz#5214c53a926d3552707527fbab415dbc08d06dda"
- integrity sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=
- dependencies:
- is-finite "^1.0.0"
-
-request-progress@0.3.1:
- version "0.3.1"
- resolved "https://registry.yarnpkg.com/request-progress/-/request-progress-0.3.1.tgz#0721c105d8a96ac6b2ce8b2c89ae2d5ecfcf6b3a"
- integrity sha1-ByHBBdipasayzossia4tXs/Pazo=
- dependencies:
- throttleit "~0.0.2"
-
-request@2.87.0:
- version "2.87.0"
- resolved "https://registry.yarnpkg.com/request/-/request-2.87.0.tgz#32f00235cd08d482b4d0d68db93a829c0ed5756e"
- integrity sha512-fcogkm7Az5bsS6Sl0sibkbhcKsnyon/jV1kF3ajGmF0c8HrttdKTPRT9hieOaQHA5HEq6r8OyWOo/o781C1tNw==
- dependencies:
- aws-sign2 "~0.7.0"
- aws4 "^1.6.0"
- caseless "~0.12.0"
- combined-stream "~1.0.5"
- extend "~3.0.1"
- forever-agent "~0.6.1"
- form-data "~2.3.1"
- har-validator "~5.0.3"
- http-signature "~1.2.0"
- is-typedarray "~1.0.0"
- isstream "~0.1.2"
- json-stringify-safe "~5.0.1"
- mime-types "~2.1.17"
- oauth-sign "~0.8.2"
- performance-now "^2.1.0"
- qs "~6.5.1"
- safe-buffer "^5.1.1"
- tough-cookie "~2.3.3"
- tunnel-agent "^0.6.0"
- uuid "^3.1.0"
-
-restore-cursor@^1.0.1:
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-1.0.1.tgz#34661f46886327fed2991479152252df92daa541"
- integrity sha1-NGYfRohjJ/7SmRR5FSJS35LapUE=
- dependencies:
- exit-hook "^1.0.0"
- onetime "^1.0.0"
-
-rxjs@^5.0.0-beta.11:
- version "5.5.12"
- resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-5.5.12.tgz#6fa61b8a77c3d793dbaf270bee2f43f652d741cc"
- integrity sha512-xx2itnL5sBbqeeiVgNPVuQQ1nC8Jp2WfNJhXWHmElW9YmrpS9UVnNzhP3EH3HFqexO5Tlp8GhYY+WEcqcVMvGw==
- dependencies:
- symbol-observable "1.0.1"
-
-safe-buffer@^5.0.1, safe-buffer@^5.1.1, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
- version "5.1.2"
- resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
- integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
-
-safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0:
- version "2.1.2"
- resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
- integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
-
-semver@^5.5.0:
- version "5.6.0"
- resolved "https://registry.yarnpkg.com/semver/-/semver-5.6.0.tgz#7e74256fbaa49c75aa7c7a205cc22799cac80004"
- integrity sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==
-
-shebang-command@^1.2.0:
- version "1.2.0"
- resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea"
- integrity sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=
- dependencies:
- shebang-regex "^1.0.0"
-
-shebang-regex@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3"
- integrity sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=
-
-signal-exit@^3.0.0:
- version "3.0.2"
- resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d"
- integrity sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=
-
-slice-ansi@0.0.4:
- version "0.0.4"
- resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-0.0.4.tgz#edbf8903f66f7ce2f8eafd6ceed65e264c831b35"
- integrity sha1-7b+JA/ZvfOL46v1s7tZeJkyDGzU=
-
-sshpk@^1.7.0:
- version "1.16.0"
- resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.16.0.tgz#1d4963a2fbffe58050aa9084ca20be81741c07de"
- integrity sha512-Zhev35/y7hRMcID/upReIvRse+I9SVhyVre/KTJSJQWMz3C3+G+HpO7m1wK/yckEtujKZ7dS4hkVxAnmHaIGVQ==
- dependencies:
- asn1 "~0.2.3"
- assert-plus "^1.0.0"
- bcrypt-pbkdf "^1.0.0"
- dashdash "^1.12.0"
- ecc-jsbn "~0.1.1"
- getpass "^0.1.1"
- jsbn "~0.1.0"
- safer-buffer "^2.0.2"
- tweetnacl "~0.14.0"
-
-stream-to-observable@^0.1.0:
- version "0.1.0"
- resolved "https://registry.yarnpkg.com/stream-to-observable/-/stream-to-observable-0.1.0.tgz#45bf1d9f2d7dc09bed81f1c307c430e68b84cffe"
- integrity sha1-Rb8dny19wJvtgfHDB8Qw5ouEz/4=
-
-string-width@^1.0.1:
- version "1.0.2"
- resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3"
- integrity sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=
- dependencies:
- code-point-at "^1.0.0"
- is-fullwidth-code-point "^1.0.0"
- strip-ansi "^3.0.0"
-
-string_decoder@~1.1.1:
- version "1.1.1"
- resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8"
- integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==
- dependencies:
- safe-buffer "~5.1.0"
-
-strip-ansi@^3.0.0, strip-ansi@^3.0.1:
- version "3.0.1"
- resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf"
- integrity sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=
- dependencies:
- ansi-regex "^2.0.0"
-
-strip-eof@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf"
- integrity sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=
-
-supports-color@5.1.0:
- version "5.1.0"
- resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.1.0.tgz#058a021d1b619f7ddf3980d712ea3590ce7de3d5"
- integrity sha512-Ry0AwkoKjDpVKK4sV4h6o3UJmNRbjYm2uXhwfj3J56lMVdvnUNqzQVRztOOMGQ++w1K/TjNDFvpJk0F/LoeBCQ==
- dependencies:
- has-flag "^2.0.0"
-
-supports-color@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7"
- integrity sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=
-
-supports-color@^5.3.0:
- version "5.5.0"
- resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f"
- integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==
- dependencies:
- has-flag "^3.0.0"
-
-symbol-observable@1.0.1:
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.0.1.tgz#8340fc4702c3122df5d22288f88283f513d3fdd4"
- integrity sha1-g0D8RwLDEi310iKI+IKD9RPT/dQ=
-
-throttleit@~0.0.2:
- version "0.0.2"
- resolved "https://registry.yarnpkg.com/throttleit/-/throttleit-0.0.2.tgz#cfedf88e60c00dd9697b61fdd2a8343a9b680eaf"
- integrity sha1-z+34jmDADdlpe2H90qg0OptoDq8=
-
-tmp@0.0.31:
- version "0.0.31"
- resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.31.tgz#8f38ab9438e17315e5dbd8b3657e8bfb277ae4a7"
- integrity sha1-jzirlDjhcxXl29izZX6L+yd65Kc=
- dependencies:
- os-tmpdir "~1.0.1"
-
-tough-cookie@~2.3.3:
- version "2.3.4"
- resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.3.4.tgz#ec60cee38ac675063ffc97a5c18970578ee83655"
- integrity sha512-TZ6TTfI5NtZnuyy/Kecv+CnoROnyXn2DN97LontgQpCwsX2XyLYCC0ENhYkehSOwAp8rTQKc/NUIF7BkQ5rKLA==
- dependencies:
- punycode "^1.4.1"
-
-tunnel-agent@^0.6.0:
- version "0.6.0"
- resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd"
- integrity sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=
- dependencies:
- safe-buffer "^5.0.1"
-
-tweetnacl@^0.14.3, tweetnacl@~0.14.0:
- version "0.14.5"
- resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64"
- integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=
-
-typedarray@^0.0.6:
- version "0.0.6"
- resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
- integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=
-
-universalify@^0.1.0:
- version "0.1.2"
- resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66"
- integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==
-
-url@0.11.0:
- version "0.11.0"
- resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1"
- integrity sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=
- dependencies:
- punycode "1.3.2"
- querystring "0.2.0"
-
-util-deprecate@~1.0.1:
- version "1.0.2"
- resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
- integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=
-
-uuid@^3.1.0:
- version "3.3.2"
- resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131"
- integrity sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==
-
-verror@1.10.0:
- version "1.10.0"
- resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400"
- integrity sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=
- dependencies:
- assert-plus "^1.0.0"
- core-util-is "1.0.2"
- extsprintf "^1.2.0"
-
-which@^1.2.9:
- version "1.3.1"
- resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a"
- integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==
- dependencies:
- isexe "^2.0.0"
-
-wrappy@1:
- version "1.0.2"
- resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
- integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=
-
-yauzl@2.4.1:
- version "2.4.1"
- resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.4.1.tgz#9528f442dab1b2284e58b4379bb194e22e0c4005"
- integrity sha1-lSj0QtqxsihOWLQ3m7GU4i4MQAU=
- dependencies:
- fd-slicer "~1.0.1"
-
-yauzl@2.8.0:
- version "2.8.0"
- resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.8.0.tgz#79450aff22b2a9c5a41ef54e02db907ccfbf9ee2"
- integrity sha1-eUUK/yKyqcWkHvVOAtuQfM+/nuI=
- dependencies:
- buffer-crc32 "~0.2.3"
- fd-slicer "~1.0.1"