You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

query_report.js 12 KiB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422
  1. // Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
  2. // MIT License. See license.txt
  3. wn.provide("wn.views");
  4. wn.provide("wn.query_reports");
  5. wn.standard_pages["query-report"] = function() {
  6. var wrapper = wn.container.add_page('query-report');
  7. wn.require("js/slickgrid.min.js");
  8. wn.ui.make_app_page({
  9. parent: wrapper,
  10. title: wn._('Query Report'),
  11. single_column: true
  12. });
  13. wn.query_report = new wn.views.QueryReport({
  14. parent: wrapper,
  15. });
  16. $(wrapper).bind("show", function() {
  17. wn.query_report.load();
  18. });
  19. }
  20. wn.views.QueryReport = Class.extend({
  21. init: function(opts) {
  22. $.extend(this, opts);
  23. // globalify for slickgrid
  24. this.appframe = this.parent.appframe;
  25. this.parent.query_report = this;
  26. this.make();
  27. this.load();
  28. },
  29. slickgrid_options: {
  30. enableColumnReorder: false,
  31. showHeaderRow: true,
  32. headerRowHeight: 30,
  33. explicitInitialization: true,
  34. multiColumnSort: true
  35. },
  36. make: function() {
  37. this.wrapper = $("<div>").appendTo($(this.parent).find(".layout-main"));
  38. $('<div class="waiting-area" style="display: none;"></div>\
  39. <div class="no-report-area well" style="display: none;">\
  40. </div>\
  41. <div class="results" style="display: none;">\
  42. <div class="result-area" style="height:400px; \
  43. border: 1px solid #aaa;"></div>\
  44. <p class="text-muted"><br>\
  45. '+wn._('For comparative filters, start with')+' ">" or "<", e.g. >5 or >01-02-2012\
  46. <br>'+wn._('For ranges')+' ('+wn._('values and dates')+') use ":", \
  47. e.g. "5:10" (to filter values between 5 & 10)</p>\
  48. </div>').appendTo(this.wrapper);
  49. this.make_toolbar();
  50. },
  51. make_toolbar: function() {
  52. var me = this;
  53. this.appframe.set_title_right(wn._('Refresh'), function() { me.refresh(); });
  54. // Edit
  55. var edit_btn = this.appframe.add_primary_action(wn._('Edit'), function() {
  56. wn.set_route("Form", "Report", me.report_name);
  57. }, "icon-edit");
  58. if(!in_list(user_roles, "System Manager")) {
  59. edit_btn.prop("disabled", true)
  60. .attr("title", wn._("Only System Manager can create / edit reports"));
  61. }
  62. var export_btn = this.appframe.add_primary_action(wn._('Export'), function() { me.export_report(); },
  63. "icon-download");
  64. wn.utils.disable_export_btn(export_btn);
  65. },
  66. load: function() {
  67. // load from route
  68. var route = wn.get_route();
  69. var me = this;
  70. if(route[1]) {
  71. if(me.report_name!=route[1]) {
  72. me.report_name = route[1];
  73. this.wrapper.find(".no-report-area").toggle(false);
  74. me.appframe.set_title(wn._("Query Report")+": " + wn._(me.report_name));
  75. if(!wn.query_reports[me.report_name]) {
  76. return wn.call({
  77. method:"webnotes.widgets.query_report.get_script",
  78. args: {
  79. report_name: me.report_name
  80. },
  81. callback: function(r) {
  82. me.appframe.set_title(wn._("Query Report")+": " + wn._(me.report_name));
  83. wn.dom.eval(r.message || "");
  84. me.setup_filters();
  85. me.refresh();
  86. }
  87. })
  88. } else {
  89. me.setup_filters();
  90. me.refresh();
  91. }
  92. }
  93. } else {
  94. var msg = wn._("No Report Loaded. Please use query-report/[Report Name] to run a report.")
  95. this.wrapper.find(".no-report-area").html(msg).toggle(true);
  96. }
  97. },
  98. setup_filters: function() {
  99. this.clear_filters();
  100. var me = this;
  101. $.each(wn.query_reports[this.report_name].filters || [], function(i, df) {
  102. if(df.fieldtype==="Break") {
  103. me.appframe.add_break();
  104. } else {
  105. var f = me.appframe.add_field(df);
  106. $(f.wrapper).addClass("filters pull-left");
  107. me.filters.push(f);
  108. if(df["default"]) {
  109. f.set_input(df["default"]);
  110. }
  111. if(df.get_query) f.get_query = df.get_query;
  112. }
  113. });
  114. this.set_route_filters()
  115. this.set_filters_by_name();
  116. },
  117. clear_filters: function() {
  118. this.filters = [];
  119. this.appframe.parent.find('.appframe-form .filters').remove();
  120. },
  121. set_route_filters: function() {
  122. var me = this;
  123. if(wn.route_options) {
  124. $.each(this.filters || [], function(i, f) {
  125. if(wn.route_options[f.df.fieldname]!=null)
  126. f.set_input(wn.route_options[f.df.fieldname]);
  127. });
  128. }
  129. wn.route_options = null;
  130. },
  131. set_filters_by_name: function() {
  132. this.filters_by_name = {};
  133. for(var i in this.filters) {
  134. this.filters_by_name[this.filters[i].df.fieldname] = this.filters[i];
  135. }
  136. },
  137. refresh: function() {
  138. // Run
  139. var me =this;
  140. this.waiting = wn.messages.waiting(this.wrapper.find(".waiting-area").empty().toggle(true),
  141. "Loading Report...");
  142. this.wrapper.find(".results").toggle(false);
  143. filters = this.get_values();
  144. if(this.report_ajax) this.report_ajax.abort();
  145. this.report_ajax = wn.call({
  146. method: "webnotes.widgets.query_report.run",
  147. type: "GET",
  148. args: {
  149. "report_name": me.report_name,
  150. filters: filters
  151. },
  152. callback: function(r) {
  153. me.report_ajax = undefined;
  154. me.make_results(r.message.result, r.message.columns);
  155. }
  156. });
  157. return this.report_ajax;
  158. },
  159. get_values: function() {
  160. var filters = {};
  161. var mandatory_fields = [];
  162. $.each(this.filters || [], function(i, f) {
  163. var v = f.get_parsed_value();
  164. if(v === '%') v = null;
  165. if(f.df.reqd && !v) mandatory_fields.push(f.df.label);
  166. if(v) filters[f.df.fieldname] = v;
  167. })
  168. if(mandatory_fields.length) {
  169. wn.throw(wn._("Mandatory filters required:\n") + wn._(mandatory_fields.join("\n")));
  170. }
  171. return filters
  172. },
  173. make_results: function(result, columns) {
  174. this.wrapper.find(".waiting-area").empty().toggle(false);
  175. this.wrapper.find(".results").toggle(true);
  176. this.make_columns(columns);
  177. this.make_data(result, columns);
  178. this.render(result, columns);
  179. },
  180. render: function(result, columns) {
  181. this.columnFilters = {};
  182. this.make_dataview();
  183. this.id = wn.dom.set_unique_id(this.wrapper.find(".result-area").get(0));
  184. this.grid = new Slick.Grid("#"+this.id, this.dataView, this.columns,
  185. this.slickgrid_options);
  186. this.grid.setSelectionModel(new Slick.CellSelectionModel());
  187. this.grid.registerPlugin(new Slick.CellExternalCopyManager({
  188. dataItemColumnValueExtractor: function(item, columnDef, value) {
  189. return item[columnDef.field];
  190. }
  191. }));
  192. this.setup_header_row();
  193. this.grid.init();
  194. this.setup_sort();
  195. },
  196. make_columns: function(columns) {
  197. this.columns = [{id: "_id", field: "_id", name: "Sr No", width: 60}]
  198. .concat($.map(columns, function(c) {
  199. var col = {name:c, id: c, field: c, sortable: true, width: 80}
  200. if(c.indexOf(":")!=-1) {
  201. var opts = c.split(":");
  202. var df = {
  203. label: opts.length<=2 ? opts[0] : opts.slice(0, opts.length - 2).join(":"),
  204. fieldtype: opts.length<=2 ? opts[1] : opts[opts.length - 2],
  205. width: opts.length<=2 ? opts[2] : opts[opts.length - 1]
  206. }
  207. if(!df.fieldtype)
  208. df.fieldtype="Data";
  209. if(df.fieldtype.indexOf("/")!=-1) {
  210. var tmp = df.fieldtype.split("/");
  211. df.fieldtype = tmp[0];
  212. df.options = tmp[1];
  213. }
  214. col.df = df;
  215. col.formatter = function(row, cell, value, columnDef, dataContext) {
  216. return wn.format(value, columnDef.df, null, dataContext);
  217. }
  218. // column parameters
  219. col.name = col.id = col.field = df.label;
  220. col.name = wn._(df.label);
  221. col.fieldtype = opts[1];
  222. // width
  223. if(df.width) {
  224. col.width=parseInt(df.width);
  225. }
  226. } else {
  227. col.df = {
  228. label: c,
  229. fieldtype: "Data"
  230. }
  231. }
  232. col.name = wn._(toTitle(col.name.replace(/_/g, " ")))
  233. return col
  234. }));
  235. },
  236. make_data: function(result, columns) {
  237. var me = this;
  238. this.data = [];
  239. for(var row_idx=0, l=result.length; row_idx < l; row_idx++) {
  240. var row = result[row_idx];
  241. var newrow = {};
  242. for(var i=1, j=this.columns.length; i<j; i++) {
  243. newrow[this.columns[i].field] = row[i-1];
  244. };
  245. newrow._id = row_idx + 1;
  246. newrow.id = newrow.name ? newrow.name : ("_" + newrow._id);
  247. this.data.push(newrow);
  248. }
  249. },
  250. make_dataview: function() {
  251. // initialize the model
  252. this.dataView = new Slick.Data.DataView({ inlineFilters: true });
  253. this.dataView.beginUpdate();
  254. this.dataView.setItems(this.data);
  255. this.dataView.setFilter(this.inline_filter);
  256. this.dataView.endUpdate();
  257. var me = this;
  258. this.dataView.onRowCountChanged.subscribe(function (e, args) {
  259. me.grid.updateRowCount();
  260. me.grid.render();
  261. });
  262. this.dataView.onRowsChanged.subscribe(function (e, args) {
  263. me.grid.invalidateRows(args.rows);
  264. me.grid.render();
  265. });
  266. },
  267. inline_filter: function (item) {
  268. var me = wn.container.page.query_report;
  269. for (var columnId in me.columnFilters) {
  270. if (columnId !== undefined && me.columnFilters[columnId] !== "") {
  271. var c = me.grid.getColumns()[me.grid.getColumnIndex(columnId)];
  272. if (!me.compare_values(item[c.field], me.columnFilters[columnId],
  273. me.columns[me.grid.getColumnIndex(columnId)])) {
  274. return false;
  275. }
  276. }
  277. }
  278. return true;
  279. },
  280. compare_values: function(value, filter, columnDef) {
  281. var invert = false;
  282. // check if invert
  283. if(filter[0]=="!") {
  284. invert = true;
  285. filter = filter.substr(1);
  286. }
  287. var out = false;
  288. var cond = "=="
  289. // parse condition
  290. if(filter[0]==">") {
  291. filter = filter.substr(1);
  292. cond = ">"
  293. } else if(filter[0]=="<") {
  294. filter = filter.substr(1);
  295. cond = "<"
  296. }
  297. if(in_list(['Float', 'Currency', 'Int', 'Date'], columnDef.df.fieldtype)) {
  298. // non strings
  299. if(filter.indexOf(":")==-1) {
  300. if(columnDef.df.fieldtype=="Date") {
  301. filter = dateutil.user_to_str(filter);
  302. }
  303. if(in_list(["Float", "Currency", "Int"], columnDef.df.fieldtype)) {
  304. value = flt(value);
  305. filter = flt(filter);
  306. }
  307. out = eval("value" + cond + "filter");
  308. } else {
  309. // range
  310. filter = filter.split(":");
  311. if(columnDef.df.fieldtype=="Date") {
  312. filter[0] = dateutil.user_to_str(filter[0]);
  313. filter[1] = dateutil.user_to_str(filter[1]);
  314. }
  315. if(in_list(["Float", "Currency", "Int"], columnDef.df.fieldtype)) {
  316. value = flt(value);
  317. filter[0] = flt(filter[0]);
  318. filter[1] = flt(filter[1]);
  319. }
  320. out = value >= filter[0] && value <= filter[1];
  321. }
  322. } else {
  323. // string
  324. value = value + "";
  325. value = value.toLowerCase();
  326. filter = filter.toLowerCase();
  327. out = value.indexOf(filter) != -1;
  328. }
  329. if(invert)
  330. return !out;
  331. else
  332. return out;
  333. },
  334. setup_header_row: function() {
  335. var me = this;
  336. $(this.grid.getHeaderRow()).delegate(":input", "change keyup", function (e) {
  337. var columnId = $(this).data("columnId");
  338. if (columnId != null) {
  339. me.columnFilters[columnId] = $.trim($(this).val());
  340. me.dataView.refresh();
  341. }
  342. });
  343. this.grid.onHeaderRowCellRendered.subscribe(function(e, args) {
  344. $(args.node).empty();
  345. $("<input type='text'>")
  346. .data("columnId", args.column.id)
  347. .val(me.columnFilters[args.column.id])
  348. .appendTo(args.node);
  349. });
  350. },
  351. setup_sort: function() {
  352. var me = this;
  353. this.grid.onSort.subscribe(function (e, args) {
  354. var cols = args.sortCols;
  355. me.data.sort(function (dataRow1, dataRow2) {
  356. for (var i = 0, l = cols.length; i < l; i++) {
  357. var field = cols[i].sortCol.field;
  358. var sign = cols[i].sortAsc ? 1 : -1;
  359. var value1 = dataRow1[field], value2 = dataRow2[field];
  360. var result = (value1 == value2 ? 0 : (value1 > value2 ? 1 : -1)) * sign;
  361. if (result != 0) {
  362. return result;
  363. }
  364. }
  365. return 0;
  366. });
  367. me.dataView.beginUpdate();
  368. me.dataView.setItems(me.data);
  369. me.dataView.endUpdate();
  370. me.dataView.refresh();
  371. });
  372. },
  373. export_report: function() {
  374. var result = $.map(wn.slickgrid_tools.get_view_data(this.columns, this.dataView),
  375. function(row) {
  376. return [row.splice(1)];
  377. });
  378. this.title = this.report_name;
  379. wn.tools.downloadify(result, ["Report Manager", "System Manager"], this);
  380. return false;
  381. }
  382. })