選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。
 
 
 
 
 
 

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