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.
 
 
 
 
 
 

504 lines
14 KiB

  1. // Copyright 2013 Web Notes Technologies Pvt Ltd
  2. // License: MIT. See license.txt
  3. wn.views.ReportFactory = wn.views.Factory.extend({
  4. make: function(route) {
  5. new wn.views.ReportViewPage(route[1], route[2]);
  6. }
  7. });
  8. wn.views.ReportViewPage = Class.extend({
  9. init: function(doctype, docname) {
  10. if(!wn.model.can_get_report(doctype)) {
  11. wn.set_route("403");
  12. return;
  13. };
  14. wn.require("js/slickgrid.min.js");
  15. this.doctype = doctype;
  16. this.docname = docname;
  17. this.page_name = wn.get_route_str();
  18. this.make_page();
  19. var me = this;
  20. wn.model.with_doctype(this.doctype, function() {
  21. me.make_report_view();
  22. if(me.docname) {
  23. wn.model.with_doc('Report', me.docname, function(r) {
  24. me.page.reportview.set_columns_and_filters(
  25. JSON.parse(wn.model.get("Report", me.docname)[0].json));
  26. me.page.reportview.run();
  27. });
  28. } else {
  29. me.page.reportview.run();
  30. }
  31. });
  32. },
  33. make_page: function() {
  34. this.page = wn.container.add_page(this.page_name);
  35. wn.ui.make_app_page({parent:this.page,
  36. single_column:true});
  37. wn.container.change_to(this.page_name);
  38. },
  39. make_report_view: function() {
  40. var module = locals.DocType[this.doctype].module;
  41. this.page.appframe.set_title(wn._(this.doctype));
  42. this.page.appframe.add_module_icon(module)
  43. this.page.appframe.set_views_for(this.doctype, "report");
  44. this.page.reportview = new wn.views.ReportView({
  45. doctype: this.doctype,
  46. docname: this.docname,
  47. page: this.page,
  48. wrapper: $(this.page).find(".layout-main")
  49. });
  50. }
  51. })
  52. wn.views.ReportView = wn.ui.Listing.extend({
  53. init: function(opts) {
  54. var me = this;
  55. $(this.page).find('.layout-main').html(wn._('Loading Report')+'...');
  56. $(this.page).find('.layout-main').empty();
  57. $.extend(this, opts);
  58. this.can_delete = wn.model.can_delete(me.doctype);
  59. this.tab_name = '`tab'+this.doctype+'`';
  60. this.setup();
  61. },
  62. set_init_columns: function() {
  63. // pre-select mandatory columns
  64. var columns = [['name'], ['owner']];
  65. $.each(wn.meta.docfield_list[this.doctype], function(i, df) {
  66. if(df.in_filter && df.fieldname!='naming_series' && df.fieldtype!='Table') {
  67. columns.push([df.fieldname]);
  68. }
  69. });
  70. this.columns = columns;
  71. },
  72. setup: function() {
  73. var me = this;
  74. this.page_title = wn._('Report')+ ': ' + wn._(this.docname ? (this.doctype + ' - ' + this.docname) : this.doctype);
  75. this.page.appframe.set_title(this.page_title)
  76. this.make({
  77. appframe: this.page.appframe,
  78. method: 'webnotes.widgets.reportview.get',
  79. get_args: this.get_args,
  80. parent: $(this.page).find('.layout-main'),
  81. start: 0,
  82. page_length: 20,
  83. show_filters: true,
  84. new_doctype: this.doctype,
  85. allow_delete: true,
  86. });
  87. this.make_delete();
  88. this.make_column_picker();
  89. this.make_sorter();
  90. this.make_export();
  91. this.set_init_columns();
  92. this.make_save();
  93. this.set_tag_and_status_filter();
  94. },
  95. set_init_columns: function() {
  96. // pre-select mandatory columns
  97. var columns = wn.defaults.get_default("_list_settings:" + this.doctype);
  98. if(!columns) {
  99. var columns = [['name', this.doctype],];
  100. $.each(wn.meta.docfield_list[this.doctype], function(i, df) {
  101. if(df.in_filter && df.fieldname!='naming_series'
  102. && !in_list(wn.model.no_value_type, df.fieldname)) {
  103. columns.push([df.fieldname, df.parent]);
  104. }
  105. });
  106. }
  107. this.columns = columns;
  108. },
  109. // preset columns and filters from saved info
  110. set_columns_and_filters: function(opts) {
  111. var me = this;
  112. if(opts.columns) this.columns = opts.columns;
  113. if(opts.filters) $.each(opts.filters, function(i, f) {
  114. // fieldname, condition, value
  115. me.filter_list.add_filter(f[0], f[1], f[2], f[3]);
  116. });
  117. // first sort
  118. if(opts.sort_by) this.sort_by_select.val(opts.sort_by);
  119. if(opts.sort_order) this.sort_order_select.val(opts.sort_order);
  120. // second sort
  121. if(opts.sort_by_next) this.sort_by_next_select.val(opts.sort_by_next);
  122. if(opts.sort_order_next) this.sort_order_next_select.val(opts.sort_order_next);
  123. },
  124. // build args for query
  125. get_args: function() {
  126. var me = this;
  127. return {
  128. doctype: this.doctype,
  129. fields: $.map(this.columns, function(v) { return me.get_full_column_name(v) }),
  130. order_by: this.get_order_by(),
  131. filters: this.filter_list.get_filters(),
  132. docstatus: ['0','1','2']
  133. }
  134. },
  135. get_order_by: function() {
  136. // first
  137. var order_by = this.get_selected_table_and_column(this.sort_by_select)
  138. + ' ' + this.sort_order_select.val();
  139. // second
  140. if(this.sort_by_next_select.val()) {
  141. order_by += ', ' + this.get_selected_table_and_column(this.sort_by_next_select)
  142. + ' ' + this.sort_order_next_select.val();
  143. }
  144. return order_by;
  145. },
  146. get_selected_table_and_column: function($select) {
  147. return this.get_full_column_name([$select.find('option:selected').attr('fieldname'),
  148. $select.find('option:selected').attr('table')])
  149. },
  150. // get table_name.column_name
  151. get_full_column_name: function(v) {
  152. return (v[1] ? ('`tab' + v[1] + '`') : this.tab_name) + '.' + v[0];
  153. },
  154. // build columns for slickgrid
  155. build_columns: function() {
  156. var me = this;
  157. return $.map(this.columns, function(c) {
  158. var docfield = wn.meta.docfield_map[c[1] || me.doctype][c[0]];
  159. if(!docfield) {
  160. var docfield = wn.model.get_std_field(c[0]);
  161. if(c[0]=="name") {
  162. docfield.options = me.doctype;
  163. }
  164. }
  165. coldef = {
  166. id: c[0],
  167. field: c[0],
  168. docfield: docfield,
  169. name: wn._(docfield ? docfield.label : toTitle(c[0])),
  170. width: (docfield ? cint(docfield.width) : 120) || 120,
  171. formatter: function(row, cell, value, columnDef, dataContext) {
  172. var docfield = columnDef.docfield;
  173. return wn.format(value, docfield, null, dataContext);
  174. }
  175. }
  176. return coldef;
  177. });
  178. },
  179. // render data
  180. render_list: function() {
  181. var me = this;
  182. var columns = this.get_columns();
  183. // add sr in data
  184. $.each(this.data, function(i, v) {
  185. // add index
  186. v._idx = i+1;
  187. v.id = v._idx;
  188. });
  189. var options = {
  190. enableCellNavigation: true,
  191. enableColumnReorder: false,
  192. };
  193. if(this.slickgrid_options) {
  194. $.extend(options, this.slickgrid_options);
  195. }
  196. this.col_defs = columns;
  197. this.dataView = new Slick.Data.DataView();
  198. this.set_data(this.data);
  199. var grid_wrapper = this.$w.find('.result-list');
  200. // set height if not auto
  201. if(!options.autoHeight)
  202. grid_wrapper.css('height', '500px');
  203. this.grid = new Slick.Grid(grid_wrapper
  204. .css('border', '1px solid #ccc')
  205. .css('border-top', '0px')
  206. .get(0), this.dataView,
  207. columns, options);
  208. this.grid.setSelectionModel(new Slick.CellSelectionModel());
  209. this.grid.registerPlugin(new Slick.CellExternalCopyManager({
  210. dataItemColumnValueExtractor: function(item, columnDef, value) {
  211. return item[columnDef.field];
  212. }
  213. }));
  214. wn.slickgrid_tools.add_property_setter_on_resize(this.grid);
  215. if(this.start!=0 && !options.autoHeight) {
  216. this.grid.scrollRowIntoView(this.data.length-1);
  217. }
  218. },
  219. set_data: function() {
  220. this.dataView.beginUpdate();
  221. this.dataView.setItems(this.data);
  222. this.dataView.endUpdate();
  223. },
  224. get_columns: function() {
  225. var std_columns = [{id:'_idx', field:'_idx', name: 'Sr.', width: 40, maxWidth: 40}];
  226. if(this.can_delete) {
  227. std_columns = std_columns.concat([{
  228. id:'_check', field:'_check', name: "", width: 30, maxWidth: 30,
  229. formatter: function(row, cell, value, columnDef, dataContext) {
  230. return repl("<input type='checkbox' \
  231. data-row='%(row)s' %(checked)s>", {
  232. row: row,
  233. checked: (dataContext._checked ? "checked" : "")
  234. });
  235. }
  236. }])
  237. }
  238. return std_columns.concat(this.build_columns());
  239. },
  240. // setup column picker
  241. make_column_picker: function() {
  242. var me = this;
  243. this.column_picker = new wn.ui.ColumnPicker(this);
  244. this.page.appframe.add_button(wn._('Pick Columns'), function() {
  245. me.column_picker.show(me.columns);
  246. }, 'icon-th-list');
  247. },
  248. set_tag_and_status_filter: function() {
  249. var me = this;
  250. this.$w.find('.result-list').on("click", ".label-info", function() {
  251. if($(this).attr("data-label")) {
  252. me.set_filter("_user_tags", $(this).attr("data-label"));
  253. }
  254. });
  255. this.$w.find('.result-list').on("click", "[data-workflow-state]", function() {
  256. if($(this).attr("data-workflow-state")) {
  257. me.set_filter(me.state_fieldname,
  258. $(this).attr("data-workflow-state"));
  259. }
  260. });
  261. },
  262. // setup sorter
  263. make_sorter: function() {
  264. var me = this;
  265. this.sort_dialog = new wn.ui.Dialog({title:'Sorting Preferences'});
  266. $(this.sort_dialog.body).html('<p class="help">Sort By</p>\
  267. <div class="sort-column"></div>\
  268. <div><select class="sort-order" style="margin-top: 10px; width: 60%;">\
  269. <option value="asc">'+wn._('Ascending')+'</option>\
  270. <option value="desc">'+wn._('Descending')+'</option>\
  271. </select></div>\
  272. <hr><p class="help">'+wn._('Then By (optional)')+'</p>\
  273. <div class="sort-column-1"></div>\
  274. <div><select class="sort-order-1" style="margin-top: 10px; width: 60%;">\
  275. <option value="asc">'+wn._('Ascending')+'</option>\
  276. <option value="desc">'+wn._('Descending')+'</option>\
  277. </select></div><hr>\
  278. <div><button class="btn btn-info">'+wn._('Update')+'</div>');
  279. // first
  280. this.sort_by_select = new wn.ui.FieldSelect($(this.sort_dialog.body).find('.sort-column'),
  281. this.doctype).$select;
  282. this.sort_by_select.css('width', '60%');
  283. this.sort_order_select = $(this.sort_dialog.body).find('.sort-order');
  284. // second
  285. this.sort_by_next_select = new wn.ui.FieldSelect($(this.sort_dialog.body).find('.sort-column-1'),
  286. this.doctype, null, true).$select;
  287. this.sort_by_next_select.css('width', '60%');
  288. this.sort_order_next_select = $(this.sort_dialog.body).find('.sort-order-1');
  289. // initial values
  290. this.sort_by_select.val(this.doctype + '.modified');
  291. this.sort_order_select.val('desc');
  292. this.sort_by_next_select.val('');
  293. this.sort_order_next_select.val('desc');
  294. // button actions
  295. this.page.appframe.add_button(wn._('Sort By'), function() {
  296. me.sort_dialog.show();
  297. }, 'icon-arrow-down');
  298. $(this.sort_dialog.body).find('.btn-info').click(function() {
  299. me.sort_dialog.hide();
  300. me.run();
  301. });
  302. },
  303. // setup export
  304. make_export: function() {
  305. var me = this;
  306. var export_btn = this.page.appframe.add_button(wn._('Export'), function() {
  307. var args = me.get_args();
  308. args.cmd = 'webnotes.widgets.reportview.export_query'
  309. open_url_post(wn.request.url, args);
  310. }, 'icon-download-alt');
  311. wn.utils.disable_export_btn(export_btn);
  312. },
  313. // save
  314. make_save: function() {
  315. var me = this;
  316. if(wn.user.is_report_manager()) {
  317. this.page.appframe.add_button(wn._('Save'), function() {
  318. // name
  319. if(me.docname) {
  320. var name = me.docname
  321. } else {
  322. var name = prompt(wn._('Select Report Name'));
  323. if(!name) {
  324. return;
  325. }
  326. }
  327. // callback
  328. wn.call({
  329. method: 'webnotes.widgets.reportview.save_report',
  330. args: {
  331. name: name,
  332. doctype: me.doctype,
  333. json: JSON.stringify({
  334. filters: me.filter_list.get_filters(),
  335. columns: me.columns,
  336. sort_by: me.sort_by_select.val(),
  337. sort_order: me.sort_order_select.val(),
  338. sort_by_next: me.sort_by_next_select.val(),
  339. sort_order_next: me.sort_order_next_select.val()
  340. })
  341. },
  342. callback: function(r) {
  343. if(r.exc) {
  344. msgprint(wn._("Report was not saved (there were errors)"));
  345. return;
  346. }
  347. if(r.message != me.docname)
  348. wn.set_route('Report', me.doctype, r.message);
  349. }
  350. });
  351. }, 'icon-upload');
  352. }
  353. },
  354. make_delete: function() {
  355. var me = this;
  356. if(this.can_delete) {
  357. $(this.page).on("click", "input[type='checkbox'][data-row]", function() {
  358. me.data[$(this).attr("data-row")]._checked
  359. = this.checked ? true : false;
  360. });
  361. this.page.appframe.add_button(wn._("Delete"), function() {
  362. var delete_list = []
  363. $.each(me.data, function(i, d) {
  364. if(d._checked) {
  365. if(d.name)
  366. delete_list.push(d.name);
  367. }
  368. });
  369. if(!delete_list.length)
  370. return;
  371. if(wn.confirm(wn._("This is PERMANENT action and you cannot undo. Continue?"),
  372. function() {
  373. wn.call({
  374. method: 'webnotes.widgets.reportview.delete_items',
  375. args: {
  376. items: delete_list,
  377. doctype: me.doctype
  378. },
  379. callback: function() {
  380. me.refresh();
  381. }
  382. });
  383. }));
  384. }, 'icon-remove');
  385. }
  386. },
  387. });
  388. wn.ui.ColumnPicker = Class.extend({
  389. init: function(list) {
  390. this.list = list;
  391. this.doctype = list.doctype;
  392. this.selects = {};
  393. },
  394. show: function(columns) {
  395. wn.require('lib/js/lib/jquery/jquery.ui.interactions.min.js');
  396. var me = this;
  397. if(!this.dialog) {
  398. this.dialog = new wn.ui.Dialog({
  399. title: wn._("Pick Columns"),
  400. width: '400'
  401. });
  402. }
  403. $(this.dialog.body).html('<div class="help">'+wn._("Drag to sort columns")+'</div>\
  404. <div class="column-list"></div>\
  405. <div><button class="btn btn-add"><i class="icon-plus"></i>\
  406. '+wn._("Add Column")+'</button></div>\
  407. <hr>\
  408. <div><button class="btn btn-info">'+wn._("Update")+'</div>');
  409. // show existing
  410. $.each(columns, function(i, c) {
  411. me.add_column(c);
  412. });
  413. $(this.dialog.body).find('.column-list').sortable();
  414. // add column
  415. $(this.dialog.body).find('.btn-add').click(function() {
  416. me.add_column(['name']);
  417. });
  418. // update
  419. $(this.dialog.body).find('.btn-info').click(function() {
  420. me.dialog.hide();
  421. // selected columns as list of [column_name, table_name]
  422. var columns = [];
  423. $(me.dialog.body).find('select').each(function() {
  424. var $selected = $(this).find('option:selected');
  425. columns.push([$selected.attr('fieldname'),
  426. $selected.attr('table')]);
  427. });
  428. wn.defaults.set_default("_list_settings:" + me.doctype, columns);
  429. me.list.columns = columns;
  430. me.list.run();
  431. });
  432. this.dialog.show();
  433. },
  434. add_column: function(c) {
  435. var w = $('<div style="padding: 5px; background-color: #eee; \
  436. width: 90%; margin-bottom: 10px; border-radius: 3px; cursor: move;">\
  437. <img src="lib/images/ui/drag-handle.png" style="margin-right: 10px;">\
  438. <a class="close" style="margin-top: 5px;">&times</a>\
  439. </div>')
  440. .appendTo($(this.dialog.body).find('.column-list'));
  441. var fieldselect = new wn.ui.FieldSelect(w, this.doctype);
  442. fieldselect.$select
  443. .css({width: '70%', 'margin-top':'5px'})
  444. .val((c[1] || this.doctype) + "." + c[0]);
  445. w.find('.close').click(function() {
  446. $(this).parent().remove();
  447. });
  448. }
  449. });