Merge branch 'develop' into supplier-quotn-comparison

This commit is contained in:
Marica 2020-09-23 12:58:35 +05:30 committed by GitHub
commit dd10c54baa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 559 additions and 13 deletions

View File

@ -165,9 +165,14 @@ def tax_account_query(doctype, txt, searchfield, start, page_len, filters):
AND company = %(company)s
AND account_currency = %(currency)s
AND `{searchfield}` LIKE %(txt)s
{mcond}
ORDER BY idx DESC, name
LIMIT %(offset)s, %(limit)s
""".format(account_type_condition=account_type_condition, searchfield=searchfield),
""".format(
account_type_condition=account_type_condition,
searchfield=searchfield,
mcond=get_match_cond(doctype)
),
dict(
account_types=filters.get("account_type"),
company=filters.get("company"),
@ -359,9 +364,21 @@ def get_batch_no(doctype, txt, searchfield, start, page_len, filters):
if filters.get("is_return"):
having_clause = ""
meta = frappe.get_meta("Batch", cached=True)
searchfields = meta.get_search_fields()
search_columns = ''
if searchfields:
search_columns = ", " + ", ".join(searchfields)
if args.get('warehouse'):
searchfields = ['batch.' + field for field in searchfields]
if searchfields:
search_columns = ", " + ", ".join(searchfields)
batch_nos = frappe.db.sql("""select sle.batch_no, round(sum(sle.actual_qty),2), sle.stock_uom,
concat('MFG-',batch.manufacturing_date), concat('EXP-',batch.expiry_date)
{search_columns}
from `tabStock Ledger Entry` sle
INNER JOIN `tabBatch` batch on sle.batch_no = batch.name
where
@ -377,6 +394,7 @@ def get_batch_no(doctype, txt, searchfield, start, page_len, filters):
group by batch_no {having_clause}
order by batch.expiry_date, sle.batch_no desc
limit %(start)s, %(page_len)s""".format(
search_columns = search_columns,
cond=cond,
match_conditions=get_match_cond(doctype),
having_clause = having_clause
@ -384,7 +402,9 @@ def get_batch_no(doctype, txt, searchfield, start, page_len, filters):
return batch_nos
else:
return frappe.db.sql("""select name, concat('MFG-', manufacturing_date), concat('EXP-',expiry_date) from `tabBatch` batch
return frappe.db.sql("""select name, concat('MFG-', manufacturing_date), concat('EXP-',expiry_date)
{search_columns}
from `tabBatch` batch
where batch.disabled = 0
and item = %(item_code)s
and (name like %(txt)s
@ -394,7 +414,7 @@ def get_batch_no(doctype, txt, searchfield, start, page_len, filters):
{0}
{match_conditions}
order by expiry_date, name desc
limit %(start)s, %(page_len)s""".format(cond, match_conditions=get_match_cond(doctype)), args)
limit %(start)s, %(page_len)s""".format(cond, search_columns = search_columns, match_conditions=get_match_cond(doctype)), args)
@frappe.whitelist()

View File

@ -282,6 +282,11 @@ auto_cancel_exempted_doctypes= [
]
scheduler_events = {
"cron": {
"0/30 * * * *": [
"erpnext.utilities.doctype.video.video.update_youtube_data",
]
},
"all": [
"erpnext.projects.doctype.project.project.project_status_update_reminder",
"erpnext.healthcare.doctype.patient_appointment.patient_appointment.send_appointment_reminder",

View File

@ -690,6 +690,7 @@ erpnext.patches.v12_0.set_valid_till_date_in_supplier_quotation
erpnext.patches.v13_0.update_old_loans
erpnext.patches.v12_0.set_serial_no_status #2020-05-21
erpnext.patches.v12_0.update_price_list_currency_in_bom
execute:frappe.reload_doctype('Dashboard')
execute:frappe.delete_doc_if_exists('Dashboard', 'Accounts')
erpnext.patches.v13_0.update_actual_start_and_end_date_in_wo
erpnext.patches.v13_0.set_company_field_in_healthcare_doctypes #2020-05-25
@ -726,3 +727,4 @@ erpnext.patches.v13_0.drop_razorpay_payload_column
erpnext.patches.v13_0.update_start_end_date_for_old_shift_assignment
erpnext.patches.v13_0.setting_custom_roles_for_some_regional_reports
erpnext.patches.v13_0.change_default_pos_print_format
erpnext.patches.v13_0.set_youtube_video_id

View File

@ -0,0 +1,10 @@
from __future__ import unicode_literals
import frappe
from erpnext.utilities.doctype.video.video import get_id_from_url
def execute():
frappe.reload_doc("utilities", "doctype","video")
for video in frappe.get_all("Video", fields=["name", "url", "youtube_video_id"]):
if video.url and not video.youtube_video_id:
frappe.db.set_value("Video", video.name, "youtube_video_id", get_id_from_url(video.url))

View File

@ -9,13 +9,15 @@ frappe.query_reports["Batch-Wise Balance History"] = {
"fieldtype": "Date",
"width": "80",
"default": frappe.sys_defaults.year_start_date,
"reqd": 1
},
{
"fieldname":"to_date",
"label": __("To Date"),
"fieldtype": "Date",
"width": "80",
"default": frappe.datetime.get_today()
"default": frappe.datetime.get_today(),
"reqd": 1
},
{
"fieldname": "item",

View File

@ -11,6 +11,9 @@ from frappe.utils import cint, flt, getdate
def execute(filters=None):
if not filters: filters = {}
if filters.from_date > filters.to_date:
frappe.throw(_("From Date must be before To Date"))
float_precision = cint(frappe.db.get_default("float_precision")) or 3
columns = get_columns(filters)

View File

@ -0,0 +1,29 @@
{
"cards": [
{
"hidden": 0,
"label": "Video",
"links": "[\n {\n \"description\": \"Video\",\n \"label\": \"Video\",\n \"name\": \"Video\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Video settings\",\n \"label\": \"Video Settings\",\n \"name\": \"Video Settings\",\n \"type\": \"doctype\"\n }\n]"
}
],
"category": "Modules",
"charts": [],
"creation": "2020-09-10 12:21:22.335307",
"developer_mode_only": 0,
"disable_user_customization": 0,
"docstatus": 0,
"doctype": "Desk Page",
"extends_another_page": 0,
"hide_custom": 0,
"idx": 0,
"is_standard": 1,
"label": "Utilities",
"modified": "2020-09-10 12:33:30.089853",
"modified_by": "user@erpnext.com",
"module": "Utilities",
"name": "Utilities",
"owner": "user@erpnext.com",
"pin_to_bottom": 1,
"pin_to_top": 0,
"shortcuts": []
}

View File

@ -2,7 +2,16 @@
// For license information, please see license.txt
frappe.ui.form.on('Video', {
// refresh: function(frm) {
refresh: function (frm) {
frm.events.toggle_youtube_statistics_section(frm);
frm.add_custom_button("Watch Video", () => frappe.help.show_video(frm.doc.url, frm.doc.title));
},
// }
toggle_youtube_statistics_section: (frm) => {
if (frm.doc.provider === "YouTube") {
frappe.db.get_single_value("Video Settings", "enable_youtube_tracking").then( val => {
frm.toggle_display("youtube_tracking_section", val);
});
}
}
});

View File

@ -11,11 +11,19 @@
"title",
"provider",
"url",
"youtube_video_id",
"column_break_4",
"publish_date",
"duration",
"youtube_tracking_section",
"like_count",
"view_count",
"col_break",
"dislike_count",
"comment_count",
"section_break_7",
"description"
"description",
"image"
],
"fields": [
{
@ -37,7 +45,6 @@
{
"fieldname": "url",
"fieldtype": "Data",
"in_list_view": 1,
"label": "URL",
"reqd": 1
},
@ -48,11 +55,12 @@
{
"fieldname": "publish_date",
"fieldtype": "Date",
"in_list_view": 1,
"label": "Publish Date"
},
{
"fieldname": "duration",
"fieldtype": "Data",
"fieldtype": "Duration",
"label": "Duration"
},
{
@ -62,13 +70,67 @@
{
"fieldname": "description",
"fieldtype": "Text Editor",
"in_list_view": 1,
"label": "Description",
"reqd": 1
},
{
"fieldname": "like_count",
"fieldtype": "Float",
"in_list_view": 1,
"label": "Likes",
"no_copy": 1,
"read_only": 1
},
{
"fieldname": "view_count",
"fieldtype": "Float",
"in_list_view": 1,
"label": "Views",
"no_copy": 1,
"read_only": 1
},
{
"fieldname": "col_break",
"fieldtype": "Column Break"
},
{
"fieldname": "dislike_count",
"fieldtype": "Float",
"label": "Dislikes",
"no_copy": 1,
"read_only": 1
},
{
"fieldname": "comment_count",
"fieldtype": "Float",
"label": "Comments",
"no_copy": 1,
"read_only": 1
},
{
"fieldname": "image",
"fieldtype": "Attach Image",
"hidden": 1,
"label": "Image",
"no_copy": 1
},
{
"depends_on": "eval:doc.provider==\"YouTube\"",
"fieldname": "youtube_tracking_section",
"fieldtype": "Section Break",
"label": "Youtube Statistics"
},
{
"fieldname": "youtube_video_id",
"fieldtype": "Data",
"hidden": 1,
"label": "Youtube ID",
"read_only": 1
}
],
"image_field": "image",
"links": [],
"modified": "2020-07-21 19:29:46.603734",
"modified": "2020-09-07 17:02:20.185794",
"modified_by": "Administrator",
"module": "Utilities",
"name": "Video",

View File

@ -3,8 +3,144 @@
# For license information, please see license.txt
from __future__ import unicode_literals
# import frappe
import frappe
import re
import pytz
from frappe.model.document import Document
from frappe import _
from datetime import datetime
from six import string_types
from pyyoutube import Api
class Video(Document):
pass
def validate(self):
if self.provider == "YouTube" and is_tracking_enabled():
self.set_video_id()
self.set_youtube_statistics()
def set_video_id(self):
if self.url and not self.get("youtube_video_id"):
self.youtube_video_id = get_id_from_url(self.url)
def set_youtube_statistics(self):
api_key = frappe.db.get_single_value("Video Settings", "api_key")
api = Api(api_key=api_key)
try:
video = api.get_video_by_id(video_id=self.youtube_video_id)
video_stats = video.items[0].to_dict().get('statistics')
self.like_count = video_stats.get('likeCount')
self.view_count = video_stats.get('viewCount')
self.dislike_count = video_stats.get('dislikeCount')
self.comment_count = video_stats.get('commentCount')
except Exception:
title = "Failed to Update YouTube Statistics for Video: {0}".format(self.name)
frappe.log_error(title + "\n\n" + frappe.get_traceback(), title=title)
def is_tracking_enabled():
return frappe.db.get_single_value("Video Settings", "enable_youtube_tracking")
def get_frequency(value):
# Return numeric value from frequency field, return 1 as fallback default value: 1 hour
if value != "Daily":
return frappe.utils.cint(value[:2].strip())
elif value:
return 24
return 1
def update_youtube_data():
# Called every 30 minutes via hooks
enable_youtube_tracking, frequency = frappe.db.get_value("Video Settings", "Video Settings", ["enable_youtube_tracking", "frequency"])
if not enable_youtube_tracking:
return
frequency = get_frequency(frequency)
time = datetime.now()
timezone = pytz.timezone(frappe.utils.get_time_zone())
site_time = time.astimezone(timezone)
if frequency == 30:
batch_update_youtube_data()
elif site_time.hour % frequency == 0 and site_time.minute < 15:
# make sure it runs within the first 15 mins of the hour
batch_update_youtube_data()
def get_formatted_ids(video_list):
# format ids to comma separated string for bulk request
ids = []
for video in video_list:
ids.append(video.youtube_video_id)
return ','.join(ids)
@frappe.whitelist()
def get_id_from_url(url):
"""
Returns video id from url
:param youtube url: String URL
"""
if not isinstance(url, string_types):
frappe.throw(_("URL can only be a string"), title=_("Invalid URL"))
pattern = re.compile(r'[a-z\:\//\.]+(youtube|youtu)\.(com|be)/(watch\?v=|embed/|.+\?v=)?([^"&?\s]{11})?')
id = pattern.match(url)
return id.groups()[-1]
@frappe.whitelist()
def batch_update_youtube_data():
def get_youtube_statistics(video_ids):
api_key = frappe.db.get_single_value("Video Settings", "api_key")
api = Api(api_key=api_key)
try:
video = api.get_video_by_id(video_id=video_ids)
video_stats = video.items
return video_stats
except Exception:
title = "Failed to Update YouTube Statistics"
frappe.log_error(title + "\n\n" + frappe.get_traceback(), title=title)
def prepare_and_set_data(video_list):
video_ids = get_formatted_ids(video_list)
stats = get_youtube_statistics(video_ids)
set_youtube_data(stats)
def set_youtube_data(entries):
for entry in entries:
video_stats = entry.to_dict().get('statistics')
video_id = entry.to_dict().get('id')
stats = {
'like_count' : video_stats.get('likeCount'),
'view_count' : video_stats.get('viewCount'),
'dislike_count' : video_stats.get('dislikeCount'),
'comment_count' : video_stats.get('commentCount'),
'video_id': video_id
}
frappe.db.sql("""
UPDATE `tabVideo`
SET
like_count = %(like_count)s,
view_count = %(view_count)s,
dislike_count = %(dislike_count)s,
comment_count = %(comment_count)s
WHERE youtube_video_id = %(video_id)s""", stats)
video_list = frappe.get_all("Video", fields=["youtube_video_id"])
if len(video_list) > 50:
# Update in batches of 50
start, end = 0, 50
while start < len(video_list):
batch = video_list[start:end]
prepare_and_set_data(batch)
start += 50
end += 50
else:
prepare_and_set_data(video_list)

View File

@ -0,0 +1,7 @@
frappe.listview_settings["Video"] = {
onload: (listview) => {
listview.page.add_menu_item(__("Video Settings"), function() {
frappe.set_route("Form","Video Settings", "Video Settings");
});
}
}

View File

@ -0,0 +1,10 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
from __future__ import unicode_literals
# import frappe
import unittest
class TestVideoSettings(unittest.TestCase):
pass

View File

@ -0,0 +1,8 @@
// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
frappe.ui.form.on('Video Settings', {
// refresh: function(frm) {
// }
});

View File

@ -0,0 +1,60 @@
{
"actions": [],
"creation": "2020-08-02 03:50:21.339609",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"enable_youtube_tracking",
"api_key",
"frequency"
],
"fields": [
{
"default": "0",
"fieldname": "enable_youtube_tracking",
"fieldtype": "Check",
"label": "Enable YouTube Tracking"
},
{
"depends_on": "eval:doc.enable_youtube_tracking",
"fieldname": "api_key",
"fieldtype": "Data",
"label": "API Key",
"mandatory_depends_on": "eval:doc.enable_youtube_tracking"
},
{
"default": "1 hr",
"depends_on": "eval:doc.enable_youtube_tracking",
"fieldname": "frequency",
"fieldtype": "Select",
"label": "Frequency",
"mandatory_depends_on": "eval:doc.enable_youtube_tracking",
"options": "30 mins\n1 hr\n6 hrs\nDaily"
}
],
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
"modified": "2020-09-07 16:09:00.360668",
"modified_by": "Administrator",
"module": "Utilities",
"name": "Video Settings",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"email": 1,
"print": 1,
"read": 1,
"role": "System Manager",
"share": 1,
"write": 1
}
],
"quick_entry": 1,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}

View File

@ -0,0 +1,22 @@
# -*- 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 import _
from frappe.model.document import Document
from apiclient.discovery import build
class VideoSettings(Document):
def validate(self):
self.validate_youtube_api_key()
def validate_youtube_api_key(self):
if self.enable_youtube_tracking and self.api_key:
try:
build("youtube", "v3", developerKey=self.api_key)
except Exception:
title = _("Failed to Authenticate the API key.")
frappe.log_error(title + "\n\n" + frappe.get_traceback(), title=title)
frappe.throw(title + " Please check the error logs.", title=_("Invalid Credentials"))

View File

View File

@ -0,0 +1,20 @@
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
/* eslint-disable */
frappe.query_reports["YouTube Interactions"] = {
"filters": [
{
fieldname: "from_date",
label: __("From Date"),
fieldtype: "Date",
default: frappe.datetime.add_months(frappe.datetime.now_date(), -12),
},
{
fieldname:"to_date",
label: __("To Date"),
fieldtype: "Date",
default: frappe.datetime.now_date(),
}
]
};

View File

@ -0,0 +1,27 @@
{
"add_total_row": 0,
"creation": "2020-08-02 05:05:00.457093",
"disable_prepared_report": 0,
"disabled": 0,
"docstatus": 0,
"doctype": "Report",
"idx": 0,
"is_standard": "Yes",
"modified": "2020-08-02 05:05:00.457093",
"modified_by": "Administrator",
"module": "Utilities",
"name": "YouTube Interactions",
"owner": "Administrator",
"prepared_report": 0,
"ref_doctype": "Video",
"report_name": "YouTube Interactions",
"report_type": "Script Report",
"roles": [
{
"role": "All"
},
{
"role": "System Manager"
}
]
}

View File

@ -0,0 +1,113 @@
# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
import frappe
from frappe import _
from frappe.utils import flt
def execute(filters=None):
if not frappe.db.get_single_value("Video Settings", "enable_youtube_tracking") or not filters:
return [], []
columns = get_columns()
data = get_data(filters)
chart_data, summary = get_chart_summary_data(data)
return columns, data, None, chart_data, summary
def get_columns():
return [
{
"label": _("Published Date"),
"fieldname": "publish_date",
"fieldtype": "Date",
"width": 100
},
{
"label": _("Title"),
"fieldname": "title",
"fieldtype": "Data",
"width": 200
},
{
"label": _("Duration"),
"fieldname": "duration",
"fieldtype": "Duration",
"width": 100
},
{
"label": _("Views"),
"fieldname": "view_count",
"fieldtype": "Float",
"width": 200
},
{
"label": _("Likes"),
"fieldname": "like_count",
"fieldtype": "Float",
"width": 200
},
{
"label": _("Dislikes"),
"fieldname": "dislike_count",
"fieldtype": "Float",
"width": 100
},
{
"label": _("Comments"),
"fieldname": "comment_count",
"fieldtype": "Float",
"width": 100
}
]
def get_data(filters):
return frappe.db.sql("""
SELECT
publish_date, title, provider, duration,
view_count, like_count, dislike_count, comment_count
FROM `tabVideo`
WHERE view_count is not null
and publish_date between %(from_date)s and %(to_date)s
ORDER BY view_count desc""", filters, as_dict=1)
def get_chart_summary_data(data):
labels, likes, views = [], [], []
total_views = 0
for row in data:
labels.append(row.get('title'))
likes.append(row.get('like_count'))
views.append(row.get('view_count'))
total_views += flt(row.get('view_count'))
chart_data = {
"data" : {
"labels" : labels,
"datasets" : [
{
"name" : "Likes",
"values" : likes
},
{
"name" : "Views",
"values" : views
}
]
},
"type": "bar",
"barOptions": {
"stacked": 1
},
}
summary = [
{
"value": total_views,
"indicator": "Blue",
"label": "Total Views",
"datatype": "Float",
}
]
return chart_data, summary

View File

@ -7,6 +7,7 @@ plaid-python==6.0.0
pycountry==19.8.18
PyGithub==1.44.1
python-stdnum==1.12
python-youtube==0.6.0
taxjar==1.9.0
tweepy==3.8.0
Unidecode==1.1.1