25개 이상의 토픽을 선택하실 수 없습니다. Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

494 lines
14 KiB

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