您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符
 
 
 
 
 
 

216 行
6.2 KiB

  1. # Copyright (c) 2013, Web Notes 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
  9. from frappe.translate import send_translations
  10. import frappe.widgets.reportview
  11. def get_report_doc(report_name):
  12. doc = frappe.get_doc("Report", report_name)
  13. if not doc.has_permission("read"):
  14. frappe.throw(_("You don't have access to Report: {0}").format(report_name), frappe.PermissionError)
  15. if not frappe.has_permission(doc.ref_doctype, "report"):
  16. frappe.throw(_("You don't have permission to get a report on: {0}").format(doc.ref_doctype),
  17. frappe.PermissionError)
  18. if doc.disabled:
  19. frappe.throw(_("Report {0} is disabled").format(report_name))
  20. return doc
  21. @frappe.whitelist()
  22. def get_script(report_name):
  23. report = get_report_doc(report_name)
  24. module = report.module or frappe.db.get_value("DocType", report.ref_doctype, "module")
  25. module_path = get_module_path(module)
  26. report_folder = os.path.join(module_path, "report", scrub(report.name))
  27. script_path = os.path.join(report_folder, scrub(report.name) + ".js")
  28. print_path = os.path.join(report_folder, scrub(report.name) + ".html")
  29. script = None
  30. if os.path.exists(script_path):
  31. with open(script_path, "r") as f:
  32. script = f.read()
  33. html_format = get_html_format(print_path)
  34. if not script and report.javascript:
  35. script = report.javascript
  36. if not script:
  37. script = "frappe.query_reports['%s']={}" % report_name
  38. # load translations
  39. if frappe.lang != "en":
  40. send_translations(frappe.get_lang_dict("report", report_name))
  41. return {
  42. "script": script,
  43. "html_format": html_format
  44. }
  45. @frappe.whitelist()
  46. def run(report_name, filters=()):
  47. report = get_report_doc(report_name)
  48. if filters and isinstance(filters, basestring):
  49. filters = json.loads(filters)
  50. if not frappe.has_permission(report.ref_doctype, "report"):
  51. frappe.msgprint(_("Must have report permission to access this report."),
  52. raise_exception=True)
  53. columns, results = [], []
  54. if report.report_type=="Query Report":
  55. if not report.query:
  56. frappe.msgprint(_("Must specify a Query to run"), raise_exception=True)
  57. if not report.query.lower().startswith("select"):
  58. frappe.msgprint(_("Query must be a SELECT"), raise_exception=True)
  59. result = [list(t) for t in frappe.db.sql(report.query, filters)]
  60. columns = [c[0] for c in frappe.db.get_description()]
  61. else:
  62. module = report.module or frappe.db.get_value("DocType", report.ref_doctype, "module")
  63. if report.is_standard=="Yes":
  64. method_name = get_report_module_dotted_path(module, report.name) + ".execute"
  65. columns, result = frappe.get_attr(method_name)(frappe._dict(filters))
  66. if report.apply_user_permissions:
  67. result = get_filtered_data(report.ref_doctype, columns, result)
  68. if cint(report.add_total_row) and result:
  69. result = add_total_row(result, columns)
  70. return {
  71. "result": result,
  72. "columns": columns
  73. }
  74. def get_report_module_dotted_path(module, report_name):
  75. return frappe.local.module_app[scrub(module)] + "." + scrub(module) \
  76. + ".report." + scrub(report_name) + "." + scrub(report_name)
  77. def add_total_row(result, columns):
  78. total_row = [""]*len(columns)
  79. has_percent = []
  80. for row in result:
  81. for i, col in enumerate(columns):
  82. fieldtype = None
  83. if isinstance(col, basestring):
  84. col = col.split(":")
  85. if len(col) > 1:
  86. fieldtype = col[1]
  87. else:
  88. fieldtype = col.get("fieldtype")
  89. if fieldtype in ["Currency", "Int", "Float", "Percent"] and flt(row[i]):
  90. total_row[i] = flt(total_row[i]) + flt(row[i])
  91. if fieldtype == "Percent" and i not in has_percent:
  92. has_percent.append(i)
  93. for i in has_percent:
  94. total_row[i] = total_row[i] / len(result)
  95. first_col_fieldtype = None
  96. if isinstance(columns[0], basestring):
  97. first_col = columns[0].split(":")
  98. if len(first_col) > 1:
  99. first_col_fieldtype = first_col[1]
  100. else:
  101. first_col_fieldtype = columns[0].get("fieldtype")
  102. if first_col_fieldtype not in ["Currency", "Int", "Float", "Percent"]:
  103. total_row[0] = "Total"
  104. result.append(total_row)
  105. return result
  106. def get_filtered_data(ref_doctype, columns, data):
  107. result = []
  108. linked_doctypes = get_linked_doctypes(columns, data)
  109. match_filters_per_doctype = get_user_match_filters(linked_doctypes, ref_doctype)
  110. if match_filters_per_doctype:
  111. for row in data:
  112. if has_match(row, linked_doctypes, match_filters_per_doctype):
  113. result.append(row)
  114. else:
  115. for row in data:
  116. result.append(row)
  117. return result
  118. def has_match(row, linked_doctypes, doctype_match_filters):
  119. resultant_match = True
  120. if not row:
  121. # allow empty rows :)
  122. return resultant_match
  123. for doctype, filter_list in doctype_match_filters.items():
  124. matched_for_doctype = False
  125. for match_filters in filter_list:
  126. match = True
  127. for dt, idx in linked_doctypes.items():
  128. if dt in match_filters and row[idx] not in match_filters[dt]:
  129. match = False
  130. break
  131. # each doctype could have multiple conflicting user permission doctypes, hence using OR
  132. # so that even if one of the sets allows a match, it is true
  133. matched_for_doctype = matched_for_doctype or match
  134. if matched_for_doctype:
  135. break
  136. # each doctype's user permissions should match the row! hence using AND
  137. resultant_match = resultant_match and matched_for_doctype
  138. if not resultant_match:
  139. break
  140. return resultant_match
  141. def get_linked_doctypes(columns, data):
  142. linked_doctypes = {}
  143. for idx, col in enumerate(columns):
  144. if isinstance(col, basestring):
  145. col = col.split(":")
  146. if len(col) > 1 and col[1].startswith("Link"):
  147. link_dt = col[1].split("/")[1]
  148. linked_doctypes[link_dt] = idx
  149. # dict
  150. elif col.get("fieldtype")=="Link" and col.get("options"):
  151. linked_doctypes[col["options"]] = col["fieldname"]
  152. # remove doctype if column is empty
  153. for doctype, key in linked_doctypes.items():
  154. if not any(d[key] for d in data):
  155. del linked_doctypes[doctype]
  156. return linked_doctypes
  157. def get_user_match_filters(doctypes, ref_doctype):
  158. match_filters = {}
  159. for dt in doctypes:
  160. filter_list = frappe.widgets.reportview.build_match_conditions(dt, False)
  161. if filter_list:
  162. match_filters[dt] = filter_list
  163. return match_filters