Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.
 
 
 
 
 
 

384 linhas
11 KiB

  1. # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
  2. # MIT License. See license.txt
  3. from __future__ import unicode_literals
  4. import frappe
  5. import os, json
  6. from frappe import _
  7. from frappe.modules import scrub, get_module_path
  8. from frappe.utils import flt, cint, get_html_format, cstr
  9. from frappe.translate import send_translations
  10. import frappe.desk.reportview
  11. from frappe.permissions import get_role_permissions
  12. def get_report_doc(report_name):
  13. doc = frappe.get_doc("Report", report_name)
  14. if not doc.is_permitted():
  15. frappe.throw(_("You don't have access to Report: {0}").format(report_name), frappe.PermissionError)
  16. if not frappe.has_permission(doc.ref_doctype, "report"):
  17. frappe.throw(_("You don't have permission to get a report on: {0}").format(doc.ref_doctype),
  18. frappe.PermissionError)
  19. if doc.disabled:
  20. frappe.throw(_("Report {0} is disabled").format(report_name))
  21. return doc
  22. @frappe.whitelist()
  23. def get_script(report_name):
  24. report = get_report_doc(report_name)
  25. module = report.module or frappe.db.get_value("DocType", report.ref_doctype, "module")
  26. module_path = get_module_path(module)
  27. report_folder = os.path.join(module_path, "report", scrub(report.name))
  28. script_path = os.path.join(report_folder, scrub(report.name) + ".js")
  29. print_path = os.path.join(report_folder, scrub(report.name) + ".html")
  30. script = None
  31. if os.path.exists(script_path):
  32. with open(script_path, "r") as f:
  33. script = f.read()
  34. html_format = get_html_format(print_path)
  35. if not script and report.javascript:
  36. script = report.javascript
  37. if not script:
  38. script = "frappe.query_reports['%s']={}" % report_name
  39. # load translations
  40. if frappe.lang != "en":
  41. send_translations(frappe.get_lang_dict("report", report_name))
  42. return {
  43. "script": script,
  44. "html_format": html_format
  45. }
  46. @frappe.whitelist()
  47. def run(report_name, filters=None, user=None):
  48. report = get_report_doc(report_name)
  49. if not user:
  50. user = frappe.session.user
  51. if not filters:
  52. filters = []
  53. if filters and isinstance(filters, basestring):
  54. filters = json.loads(filters)
  55. if not frappe.has_permission(report.ref_doctype, "report"):
  56. frappe.msgprint(_("Must have report permission to access this report."),
  57. raise_exception=True)
  58. columns, result, message, chart = [], [], None, None
  59. if report.report_type=="Query Report":
  60. if not report.query:
  61. frappe.msgprint(_("Must specify a Query to run"), raise_exception=True)
  62. if not report.query.lower().startswith("select"):
  63. frappe.msgprint(_("Query must be a SELECT"), raise_exception=True)
  64. result = [list(t) for t in frappe.db.sql(report.query, filters)]
  65. columns = [cstr(c[0]) for c in frappe.db.get_description()]
  66. else:
  67. module = report.module or frappe.db.get_value("DocType", report.ref_doctype, "module")
  68. if report.is_standard=="Yes":
  69. method_name = get_report_module_dotted_path(module, report.name) + ".execute"
  70. res = frappe.get_attr(method_name)(frappe._dict(filters))
  71. columns, result = res[0], res[1]
  72. if len(res) > 2:
  73. message = res[2]
  74. if len(res) > 3:
  75. chart = res[3]
  76. if report.apply_user_permissions and result:
  77. result = get_filtered_data(report.ref_doctype, columns, result, user)
  78. if cint(report.add_total_row) and result:
  79. result = add_total_row(result, columns)
  80. return {
  81. "result": result,
  82. "columns": columns,
  83. "message": message,
  84. "chart": chart
  85. }
  86. @frappe.whitelist()
  87. def export_query():
  88. """export from query reports"""
  89. data = frappe._dict(frappe.local.form_dict)
  90. del data["cmd"]
  91. if isinstance(data.get("filters"), basestring):
  92. filters = json.loads(data["filters"])
  93. if isinstance(data.get("report_name"), basestring):
  94. report_name = data["report_name"]
  95. if isinstance(data.get("file_format_type"), basestring):
  96. file_format_type = data["file_format_type"]
  97. if isinstance(data.get("visible_idx"), basestring):
  98. visible_idx = json.loads(data.get("visible_idx"))
  99. else:
  100. visible_idx = None
  101. if file_format_type == "Excel":
  102. data = run(report_name, filters)
  103. data = frappe._dict(data)
  104. columns = get_columns_dict(data.columns)
  105. result = [[]]
  106. # add column headings
  107. for idx in range(len(data.columns)):
  108. result[0].append(columns[idx]["label"])
  109. # build table from dict
  110. if isinstance(data.result[0], dict):
  111. for row in data.result:
  112. if row:
  113. row_list = []
  114. for idx in range(len(data.columns)):
  115. row_list.append(row.get(columns[idx]["fieldname"],""))
  116. result.append(row_list)
  117. else:
  118. result = result + data.result
  119. # filter rows by slickgrid's inline filter
  120. if visible_idx:
  121. result = [x for idx, x in enumerate(result) if idx == 0 or idx in visible_idx]
  122. from frappe.utils.xlsxutils import make_xlsx
  123. xlsx_file = make_xlsx(result, "Query Report")
  124. frappe.response['filename'] = report_name + '.xlsx'
  125. frappe.response['filecontent'] = xlsx_file.getvalue()
  126. frappe.response['type'] = 'binary'
  127. def get_report_module_dotted_path(module, report_name):
  128. return frappe.local.module_app[scrub(module)] + "." + scrub(module) \
  129. + ".report." + scrub(report_name) + "." + scrub(report_name)
  130. def add_total_row(result, columns, meta = None):
  131. total_row = [""]*len(columns)
  132. has_percent = []
  133. for i, col in enumerate(columns):
  134. fieldtype, options = None, None
  135. if isinstance(col, basestring):
  136. if meta:
  137. # get fieldtype from the meta
  138. field = meta.get_field(col)
  139. if field:
  140. fieldtype = meta.get_field(col).fieldtype
  141. else:
  142. col = col.split(":")
  143. if len(col) > 1:
  144. if col[1]:
  145. fieldtype = col[1]
  146. if "/" in fieldtype:
  147. fieldtype, options = fieldtype.split("/")
  148. else:
  149. fieldtype = "Data"
  150. else:
  151. fieldtype = col.get("fieldtype")
  152. options = col.get("options")
  153. for row in result:
  154. if fieldtype in ["Currency", "Int", "Float", "Percent"] and flt(row[i]):
  155. total_row[i] = flt(total_row[i]) + flt(row[i])
  156. if fieldtype == "Percent" and i not in has_percent:
  157. has_percent.append(i)
  158. if fieldtype=="Link" and options == "Currency":
  159. total_row[i] = result[0][i]
  160. for i in has_percent:
  161. total_row[i] = total_row[i] / len(result)
  162. first_col_fieldtype = None
  163. if isinstance(columns[0], basestring):
  164. first_col = columns[0].split(":")
  165. if len(first_col) > 1:
  166. first_col_fieldtype = first_col[1].split("/")[0]
  167. else:
  168. first_col_fieldtype = columns[0].get("fieldtype")
  169. if first_col_fieldtype not in ["Currency", "Int", "Float", "Percent", "Date"]:
  170. if first_col_fieldtype == "Link":
  171. total_row[0] = "'" + _("Total") + "'"
  172. else:
  173. total_row[0] = _("Total")
  174. result.append(total_row)
  175. return result
  176. def get_filtered_data(ref_doctype, columns, data, user):
  177. result = []
  178. linked_doctypes = get_linked_doctypes(columns, data)
  179. match_filters_per_doctype = get_user_match_filters(linked_doctypes, ref_doctype)
  180. shared = frappe.share.get_shared(ref_doctype, user)
  181. columns_dict = get_columns_dict(columns)
  182. role_permissions = get_role_permissions(frappe.get_meta(ref_doctype), user)
  183. if_owner = role_permissions.get("if_owner", {}).get("report")
  184. if match_filters_per_doctype:
  185. for row in data:
  186. # Why linked_doctypes.get(ref_doctype)? because if column is empty, linked_doctypes[ref_doctype] is removed
  187. if linked_doctypes.get(ref_doctype) and shared and row[linked_doctypes[ref_doctype]] in shared:
  188. result.append(row)
  189. elif has_match(row, linked_doctypes, match_filters_per_doctype, ref_doctype, if_owner, columns_dict, user):
  190. result.append(row)
  191. else:
  192. result = list(data)
  193. return result
  194. def has_match(row, linked_doctypes, doctype_match_filters, ref_doctype, if_owner, columns_dict, user):
  195. """Returns True if after evaluating permissions for each linked doctype
  196. - There is an owner match for the ref_doctype
  197. - `and` There is a user permission match for all linked doctypes
  198. Returns True if the row is empty
  199. Note:
  200. Each doctype could have multiple conflicting user permission doctypes.
  201. Hence even if one of the sets allows a match, it is true.
  202. This behavior is equivalent to the trickling of user permissions of linked doctypes to the ref doctype.
  203. """
  204. resultant_match = True
  205. if not row:
  206. # allow empty rows :)
  207. return resultant_match
  208. for doctype, filter_list in doctype_match_filters.items():
  209. matched_for_doctype = False
  210. if doctype==ref_doctype and if_owner:
  211. idx = linked_doctypes.get("User")
  212. if (idx is not None
  213. and row[idx]==user
  214. and columns_dict[idx]==columns_dict.get("owner")):
  215. # owner match is true
  216. matched_for_doctype = True
  217. if not matched_for_doctype:
  218. for match_filters in filter_list:
  219. match = True
  220. for dt, idx in linked_doctypes.items():
  221. # case handled above
  222. if dt=="User" and columns_dict[idx]==columns_dict.get("owner"):
  223. continue
  224. if dt in match_filters and row[idx] not in match_filters[dt] and frappe.db.exists(dt, row[idx]):
  225. match = False
  226. break
  227. # each doctype could have multiple conflicting user permission doctypes, hence using OR
  228. # so that even if one of the sets allows a match, it is true
  229. matched_for_doctype = matched_for_doctype or match
  230. if matched_for_doctype:
  231. break
  232. # each doctype's user permissions should match the row! hence using AND
  233. resultant_match = resultant_match and matched_for_doctype
  234. if not resultant_match:
  235. break
  236. return resultant_match
  237. def get_linked_doctypes(columns, data):
  238. linked_doctypes = {}
  239. columns_dict = get_columns_dict(columns)
  240. for idx, col in enumerate(columns):
  241. df = columns_dict[idx]
  242. if df.get("fieldtype")=="Link":
  243. if isinstance(col, basestring):
  244. linked_doctypes[df["options"]] = idx
  245. else:
  246. # dict
  247. linked_doctypes[df["options"]] = df["fieldname"]
  248. # remove doctype if column is empty
  249. columns_with_value = []
  250. for row in data:
  251. if row:
  252. if len(row) != len(columns_with_value):
  253. if isinstance(row, (list, tuple)):
  254. row = enumerate(row)
  255. elif isinstance(row, dict):
  256. row = row.items()
  257. for col, val in row:
  258. if val and col not in columns_with_value:
  259. columns_with_value.append(col)
  260. for doctype, key in linked_doctypes.items():
  261. if key not in columns_with_value:
  262. del linked_doctypes[doctype]
  263. return linked_doctypes
  264. def get_columns_dict(columns):
  265. """Returns a dict with column docfield values as dict
  266. The keys for the dict are both idx and fieldname,
  267. so either index or fieldname can be used to search for a column's docfield properties
  268. """
  269. columns_dict = frappe._dict()
  270. for idx, col in enumerate(columns):
  271. col_dict = frappe._dict()
  272. # string
  273. if isinstance(col, basestring):
  274. col = col.split(":")
  275. if len(col) > 1:
  276. if "/" in col[1]:
  277. col_dict["fieldtype"], col_dict["options"] = col[1].split("/")
  278. else:
  279. col_dict["fieldtype"] = col[1]
  280. col_dict["label"] = col[0]
  281. col_dict["fieldname"] = frappe.scrub(col[0])
  282. # dict
  283. else:
  284. col_dict.update(col)
  285. if "fieldname" not in col_dict:
  286. col_dict["fieldname"] = frappe.scrub(col_dict["label"])
  287. columns_dict[idx] = col_dict
  288. columns_dict[col_dict["fieldname"]] = col_dict
  289. return columns_dict
  290. def get_user_match_filters(doctypes, ref_doctype):
  291. match_filters = {}
  292. for dt in doctypes:
  293. filter_list = frappe.desk.reportview.build_match_conditions(dt, False)
  294. if filter_list:
  295. match_filters[dt] = filter_list
  296. return match_filters