Merge pull request #15356 from adityahase/coverage

feature(coverage): Add test coverage to erpnext travis builds
diff --git a/erpnext/erpnext_integrations/doctype/amazon_mws_settings/amazon_methods.py b/erpnext/erpnext_integrations/doctype/amazon_mws_settings/amazon_methods.py
index cb11ece..3234e7a 100644
--- a/erpnext/erpnext_integrations/doctype/amazon_mws_settings/amazon_methods.py
+++ b/erpnext/erpnext_integrations/doctype/amazon_mws_settings/amazon_methods.py
@@ -3,8 +3,12 @@
 # For license information, please see license.txt
 
 from __future__ import unicode_literals
-import frappe, time, dateutil, math, csv, StringIO
-import amazon_mws_api as mws
+import frappe, time, dateutil, math, csv
+try:
+    from StringIO import StringIO
+except ImportError:
+    from io import StringIO
+import erpnext.erpnext_integrations.doctype.amazon_mws_settings.amazon_mws_api as mws
 from frappe import _
 
 #Get and Create Products
@@ -22,7 +26,7 @@
 			listings_response = reports.get_report(report_id=report_id)
 
 			#Get ASIN Codes
-			string_io = StringIO.StringIO(listings_response.original)
+			string_io = StringIO(listings_response.original)
 			csv_rows = list(csv.reader(string_io, delimiter=str('\t')))
 			asin_list = list(set([row[1] for row in csv_rows[1:]]))
 			#break into chunks of 10
diff --git a/erpnext/erpnext_integrations/doctype/amazon_mws_settings/amazon_mws_api.py b/erpnext/erpnext_integrations/doctype/amazon_mws_settings/amazon_mws_api.py
index 1e131e8..bf6d85b 100755
--- a/erpnext/erpnext_integrations/doctype/amazon_mws_settings/amazon_mws_api.py
+++ b/erpnext/erpnext_integrations/doctype/amazon_mws_settings/amazon_mws_api.py
@@ -9,7 +9,7 @@
 import hashlib
 import hmac
 import base64
-import xml_utils
+from erpnext.erpnext_integrations.doctype.amazon_mws_settings import xml_utils
 import re
 try:
 	from xml.etree.ElementTree import ParseError as XMLError
diff --git a/erpnext/erpnext_integrations/doctype/amazon_mws_settings/amazon_mws_settings.py b/erpnext/erpnext_integrations/doctype/amazon_mws_settings/amazon_mws_settings.py
index c0a6b8d..249a73f 100644
--- a/erpnext/erpnext_integrations/doctype/amazon_mws_settings/amazon_mws_settings.py
+++ b/erpnext/erpnext_integrations/doctype/amazon_mws_settings/amazon_mws_settings.py
@@ -7,7 +7,7 @@
 from frappe.model.document import Document
 import dateutil
 from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
-from amazon_methods import get_products_details, get_orders
+from erpnext.erpnext_integrations.doctype.amazon_mws_settings.amazon_methods import get_products_details, get_orders
 
 class AmazonMWSSettings(Document):
 	def validate(self):
diff --git a/erpnext/healthcare/utils.py b/erpnext/healthcare/utils.py
index 1045d49..1be82e2 100644
--- a/erpnext/healthcare/utils.py
+++ b/erpnext/healthcare/utils.py
@@ -307,8 +307,8 @@
 		fee_validity = create_fee_validity(appointment_doc.practitioner, appointment_doc.patient, appointment_doc.appointment_date, ref_invoice)
 		visited = fee_validity.visited
 
-	print "do_not_update: ", do_not_update
-	print "visited: ", visited
+	print("do_not_update: ", do_not_update)
+	print("visited: ", visited)
 
 	# Mark All Patient Appointment invoiced = True in the validity range do not cross the max visit
 	if (method == "on_cancel"):
@@ -410,7 +410,7 @@
 					lft > %s and rgt < %s""",
 					(each['lft'], each['rgt']))
 				for child in child_list:
-					print child[0], child[1]
+					print(child[0], child[1])
 					if not occupied:
 						occupied = 0
 					if child[1] == "Occupied":
diff --git a/erpnext/hub_node/doctype/hub_tracked_item/hub_tracked_item.json b/erpnext/hub_node/doctype/hub_tracked_item/hub_tracked_item.json
index 9384adb..2e89887 100644
--- a/erpnext/hub_node/doctype/hub_tracked_item/hub_tracked_item.json
+++ b/erpnext/hub_node/doctype/hub_tracked_item/hub_tracked_item.json
@@ -120,7 +120,7 @@
  "issingle": 0, 
  "istable": 0, 
  "max_attachments": 0, 
- "modified": "2018-08-19 22:24:06.207307", 
+ "modified": "2018-09-10 11:37:35.951019", 
  "modified_by": "Administrator", 
  "module": "Hub Node", 
  "name": "Hub Tracked Item", 
@@ -145,6 +145,25 @@
    "share": 1, 
    "submit": 0, 
    "write": 1
+  }, 
+  {
+   "amend": 0, 
+   "cancel": 0, 
+   "create": 1, 
+   "delete": 1, 
+   "email": 1, 
+   "export": 1, 
+   "if_owner": 0, 
+   "import": 0, 
+   "permlevel": 0, 
+   "print": 1, 
+   "read": 1, 
+   "report": 1, 
+   "role": "Item Manager", 
+   "set_user_permissions": 0, 
+   "share": 1, 
+   "submit": 0, 
+   "write": 1
   }
  ], 
  "quick_entry": 1, 
diff --git a/erpnext/patches/v10_0/remove_and_copy_fields_in_physician.py b/erpnext/patches/v10_0/remove_and_copy_fields_in_physician.py
index f632c94..0739671 100644
--- a/erpnext/patches/v10_0/remove_and_copy_fields_in_physician.py
+++ b/erpnext/patches/v10_0/remove_and_copy_fields_in_physician.py
@@ -5,7 +5,7 @@
 		frappe.reload_doc("healthcare", "doctype", "physician")
 		frappe.reload_doc("healthcare", "doctype", "physician_service_unit_schedule")
 
-		if frappe.db.has_column('Physician', 'physician_schedule'):
+		if frappe.db.has_column('Physician', 'physician_schedules'):
 			for doc in frappe.get_all('Physician'):
 				_doc = frappe.get_doc('Physician', doc.name)
 				if _doc.physician_schedule:
diff --git a/erpnext/patches/v11_0/make_job_card.py b/erpnext/patches/v11_0/make_job_card.py
index 4604889..9c41c0b 100644
--- a/erpnext/patches/v11_0/make_job_card.py
+++ b/erpnext/patches/v11_0/make_job_card.py
@@ -11,10 +11,16 @@
 	frappe.reload_doc('manufacturing', 'doctype', 'job_card')
 	frappe.reload_doc('manufacturing', 'doctype', 'job_card_item')
 
-	for d in frappe.db.sql("""select work_order, name from tabTimesheet
-		where (work_order is not null and work_order != '') and docstatus = 0""", as_dict=1):
-		if d.work_order:
-			doc = frappe.get_doc('Work Order', d.work_order)
+	fieldname = frappe.db.get_value('DocField', {'fieldname': 'work_order', 'parent': 'Timesheet'}, 'fieldname')
+	if not fieldname:
+		fieldname = frappe.db.get_value('DocField', {'fieldname': 'production_order', 'parent': 'Timesheet'}, 'fieldname')
+		if not fieldname: return
+
+	for d in frappe.get_all('Timesheet',
+		filters={fieldname: ['!=', ""], 'docstatus': 0},
+		fields=[fieldname, 'name']):
+		if d[fieldname]:
+			doc = frappe.get_doc('Work Order', d[fieldname])
 			for row in doc.operations:
 				create_job_card(doc, row, auto_create=True)
-			frappe.delete_doc('Timesheet', d.name)
\ No newline at end of file
+			frappe.delete_doc('Timesheet', d.name)
diff --git a/erpnext/public/js/hub/components/DetailView.vue b/erpnext/public/js/hub/components/DetailView.vue
index 2f1a941..cc09982 100644
--- a/erpnext/public/js/hub/components/DetailView.vue
+++ b/erpnext/public/js/hub/components/DetailView.vue
@@ -28,7 +28,7 @@
 			<div class="row margin-bottom">
 				<div class="col-md-3">
 					<div class="hub-item-image">
-						<img v-img-src="image">
+						<base-image :src="image" :alt="title" />
 					</div>
 				</div>
 				<div class="col-md-8">
diff --git a/erpnext/public/js/hub/components/Image.vue b/erpnext/public/js/hub/components/Image.vue
new file mode 100644
index 0000000..9acf421
--- /dev/null
+++ b/erpnext/public/js/hub/components/Image.vue
@@ -0,0 +1,40 @@
+<template>
+	<div class="hub-image">
+		<img :src="src" :alt="alt" v-show="!is_loading && !is_broken"/>
+		<div class="hub-image-loading" v-if="is_loading">
+			<span class="octicon octicon-cloud-download"></span>
+		</div>
+		<div class="hub-image-broken" v-if="is_broken">
+			<span class="octicon octicon-file-media"></span>
+		</div>
+	</div>
+</template>
+<script>
+export default {
+	name: 'Image',
+	props: ['src', 'alt'],
+	data() {
+		return {
+			is_loading: true,
+			is_broken: false
+		}
+	},
+	created() {
+		this.handle_image();
+	},
+	methods: {
+		handle_image() {
+			let img = new Image();
+			img.src = this.src;
+
+			img.onload = () => {
+				this.is_loading = false;
+			};
+			img.onerror = () => {
+				this.is_loading = false;
+				this.is_broken = true;
+			};
+		}
+	}
+};
+</script>
diff --git a/erpnext/public/js/hub/components/ItemCard.vue b/erpnext/public/js/hub/components/ItemCard.vue
index f34fddc..675ad86 100644
--- a/erpnext/public/js/hub/components/ItemCard.vue
+++ b/erpnext/public/js/hub/components/ItemCard.vue
@@ -15,7 +15,7 @@
 				</i>
 			</div>
 			<div class="hub-card-body">
-				<img class="hub-card-image" v-img-src="item.image"/>
+				<base-image class="hub-card-image" :src="item.image" :alt="title" />
 				<div class="hub-card-overlay">
 					<div v-if="is_local" class="hub-card-overlay-body">
 						<div class="hub-card-overlay-button">
diff --git a/erpnext/public/js/hub/components/ItemListCard.vue b/erpnext/public/js/hub/components/ItemListCard.vue
index 70cb566..7f6fb77 100644
--- a/erpnext/public/js/hub/components/ItemListCard.vue
+++ b/erpnext/public/js/hub/components/ItemListCard.vue
@@ -1,7 +1,7 @@
 <template>
 	<div class="hub-list-item" :data-route="item.route">
 		<div class="hub-list-left">
-			<img class="hub-list-image" v-img-src="item.image">
+			<base-image class="hub-list-image" :src="item.image" />
 			<div class="hub-list-body ellipsis">
 				<div class="hub-list-title">{{item.item_name}}</div>
 				<div class="hub-list-subtitle ellipsis">
diff --git a/erpnext/public/js/hub/vue-plugins.js b/erpnext/public/js/hub/vue-plugins.js
index 439c1f2..6e6a7cb 100644
--- a/erpnext/public/js/hub/vue-plugins.js
+++ b/erpnext/public/js/hub/vue-plugins.js
@@ -7,6 +7,7 @@
 import DetailView from './components/DetailView.vue';
 import DetailHeaderItem from './components/DetailHeaderItem.vue';
 import EmptyState from './components/EmptyState.vue';
+import Image from './components/Image.vue';
 
 Vue.prototype.__ = window.__;
 Vue.prototype.frappe = window.frappe;
@@ -17,6 +18,7 @@
 Vue.component('detail-view', DetailView);
 Vue.component('detail-header-item', DetailHeaderItem);
 Vue.component('empty-state', EmptyState);
+Vue.component('base-image', Image);
 
 Vue.directive('route', {
 	bind(el, binding) {
@@ -51,16 +53,6 @@
 	img.src = src;
 }
 
-Vue.directive('img-src', {
-	bind(el, binding) {
-		handleImage(el, binding.value);
-	},
-	update(el, binding) {
-		if (binding.value === binding.oldValue) return;
-		handleImage(el, binding.value);
-	}
-});
-
 Vue.filter('striphtml', function (text) {
 	return strip_html(text || '');
 });
\ No newline at end of file
diff --git a/erpnext/public/less/hub.less b/erpnext/public/less/hub.less
index d40926b..089915d 100644
--- a/erpnext/public/less/hub.less
+++ b/erpnext/public/less/hub.less
@@ -1,4 +1,5 @@
-@import "../../../../frappe/frappe/public/less/variables.less";
+@import "variables.less";
+@import (reference) "desk.less";
 
 body[data-route^="marketplace/"] {
 	.layout-side-section {
@@ -26,6 +27,22 @@
 		font-size: @text-medium;
 	}
 
+	.hub-image {
+		height: 200px;
+	}
+
+	.hub-image-loading, .hub-image-broken {
+		.img-background();
+		display: flex;
+		align-items: center;
+		justify-content: center;
+
+		span {
+			font-size: 32px;
+			color: @text-extra-muted;
+		}
+	}
+
 	.progress-bar {
 		background-color: #89da28;
 	}
@@ -136,6 +153,7 @@
 	}
 
 	.hub-item-image {
+		position: relative;
 		border: 1px solid @border-color;
 		border-radius: 4px;
 		overflow: hidden;