Nelze vybrat více než 25 témat Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a může být dlouhé až 35 znaků.

reportview.js 18 KiB

před 12 roky
před 12 roky
před 12 roky
před 12 roky
před 12 roky
před 12 roky
před 12 roky
před 12 roky
před 12 roky
před 12 roky
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639
  1. // Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
  2. // MIT License. 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.show_not_permitted(wn.get_route_str());
  12. return;
  13. };
  14. wn.require("assets/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.set_route_filters();
  27. me.page.reportview.run();
  28. });
  29. } else {
  30. me.page.reportview.set_route_filters();
  31. me.page.reportview.run();
  32. }
  33. });
  34. },
  35. make_page: function() {
  36. var me = this;
  37. this.page = wn.container.add_page(this.page_name);
  38. wn.ui.make_app_page({parent:this.page,
  39. single_column:true});
  40. wn.container.change_to(this.page_name);
  41. $(this.page).on('show', function(){
  42. if(me.page.reportview.set_route_filters())
  43. me.page.reportview.run();
  44. })
  45. },
  46. make_report_view: function() {
  47. var module = locals.DocType[this.doctype].module;
  48. this.page.appframe.set_title(wn._(this.doctype));
  49. this.page.appframe.add_module_icon(module, this.doctype)
  50. this.page.appframe.set_views_for(this.doctype, "report");
  51. this.page.reportview = new wn.views.ReportView({
  52. doctype: this.doctype,
  53. docname: this.docname,
  54. page: this.page,
  55. wrapper: $(this.page).find(".layout-main")
  56. });
  57. }
  58. });
  59. wn.views.ReportView = wn.ui.Listing.extend({
  60. init: function(opts) {
  61. var me = this;
  62. $(this.page).find('.layout-main').html(wn._('Loading Report')+'...');
  63. $(this.page).find('.layout-main').empty();
  64. $.extend(this, opts);
  65. this.can_delete = wn.model.can_delete(me.doctype);
  66. this.tab_name = '`tab'+this.doctype+'`';
  67. this.setup();
  68. },
  69. set_init_columns: function() {
  70. // pre-select mandatory columns
  71. var columns = [['name'], ['owner']];
  72. $.each(wn.meta.docfield_list[this.doctype], function(i, df) {
  73. if(df.in_filter && df.fieldname!='naming_series' && df.fieldtype!='Table') {
  74. columns.push([df.fieldname]);
  75. }
  76. });
  77. this.columns = columns;
  78. },
  79. setup: function() {
  80. var me = this;
  81. this.page_title = wn._('Report')+ ': ' + wn._(this.docname ? (this.doctype + ' - ' + this.docname) : this.doctype);
  82. this.page.appframe.set_title(this.page_title)
  83. this.make({
  84. appframe: this.page.appframe,
  85. method: 'webnotes.widgets.reportview.get',
  86. get_args: this.get_args,
  87. parent: $(this.page).find('.layout-main'),
  88. start: 0,
  89. page_length: 20,
  90. show_filters: true,
  91. new_doctype: this.doctype,
  92. allow_delete: true,
  93. });
  94. this.make_delete();
  95. this.make_column_picker();
  96. this.make_sorter();
  97. this.make_export();
  98. this.set_init_columns();
  99. this.make_save();
  100. this.make_user_restrictions();
  101. this.set_tag_and_status_filter();
  102. },
  103. set_init_columns: function() {
  104. // pre-select mandatory columns
  105. var columns = wn.defaults.get_default("_list_settings:" + this.doctype);
  106. if(!columns) {
  107. var columns = [['name', this.doctype],];
  108. $.each(wn.meta.docfield_list[this.doctype], function(i, df) {
  109. if(df.in_filter && df.fieldname!='naming_series'
  110. && !in_list(wn.model.no_value_type, df.fieldname)) {
  111. columns.push([df.fieldname, df.parent]);
  112. }
  113. });
  114. }
  115. this.columns = columns;
  116. },
  117. // preset columns and filters from saved info
  118. set_columns_and_filters: function(opts) {
  119. var me = this;
  120. if(opts.columns) this.columns = opts.columns;
  121. if(opts.filters) $.each(opts.filters, function(i, f) {
  122. // f = [doctype, fieldname, condition, value]
  123. var df = wn.meta.get_docfield(f[0], f[1]);
  124. if (df && df.fieldtype == "Check") {
  125. var value = f[3] ? "Yes" : "No";
  126. } else {
  127. var value = f[3];
  128. }
  129. me.filter_list.add_filter(f[0], f[1], f[2], value);
  130. });
  131. // first sort
  132. if(opts.sort_by) this.sort_by_select.val(opts.sort_by);
  133. if(opts.sort_order) this.sort_order_select.val(opts.sort_order);
  134. // second sort
  135. if(opts.sort_by_next) this.sort_by_next_select.val(opts.sort_by_next);
  136. if(opts.sort_order_next) this.sort_order_next_select.val(opts.sort_order_next);
  137. },
  138. set_route_filters: function() {
  139. var me = this;
  140. if(wn.route_options) {
  141. me.filter_list.clear_filters();
  142. $.each(wn.route_options, function(key, value) {
  143. me.filter_list.add_filter(me.doctype, key, "=", value);
  144. });
  145. wn.route_options = null;
  146. return true;
  147. }
  148. },
  149. // build args for query
  150. get_args: function() {
  151. var me = this;
  152. return {
  153. doctype: this.doctype,
  154. fields: $.map(this.columns, function(v) { return me.get_full_column_name(v) }),
  155. order_by: this.get_order_by(),
  156. filters: this.filter_list.get_filters(),
  157. docstatus: ['0','1','2'],
  158. with_childnames: 1
  159. }
  160. },
  161. get_order_by: function() {
  162. // first
  163. var order_by = this.get_selected_table_and_column(this.sort_by_select)
  164. + ' ' + this.sort_order_select.val();
  165. // second
  166. if(this.sort_by_next_select.val()) {
  167. order_by += ', ' + this.get_selected_table_and_column(this.sort_by_next_select)
  168. + ' ' + this.sort_order_next_select.val();
  169. }
  170. return order_by;
  171. },
  172. get_selected_table_and_column: function(select) {
  173. return select.selected_doctype ?
  174. this.get_full_column_name([select.selected_fieldname, select.selected_doctype]) : "";
  175. },
  176. // get table_name.column_name
  177. get_full_column_name: function(v) {
  178. if(!v) return;
  179. return (v[1] ? ('`tab' + v[1] + '`') : this.tab_name) + '.' + v[0];
  180. },
  181. // build columns for slickgrid
  182. build_columns: function() {
  183. var me = this;
  184. return $.map(this.columns, function(c) {
  185. var docfield = wn.meta.docfield_map[c[1] || me.doctype][c[0]];
  186. if(!docfield) {
  187. var docfield = wn.model.get_std_field(c[0]);
  188. if(c[0]=="name") {
  189. docfield.options = me.doctype;
  190. docfield.parent = me.doctype;
  191. }
  192. }
  193. coldef = {
  194. id: c[0],
  195. field: c[0],
  196. docfield: docfield,
  197. name: wn._(docfield ? docfield.label : toTitle(c[0])),
  198. width: (docfield ? cint(docfield.width) : 120) || 120,
  199. formatter: function(row, cell, value, columnDef, dataContext) {
  200. var docfield = columnDef.docfield;
  201. if(docfield.fieldname==="_user_tags") docfield.fieldtype = "Tag";
  202. if(docfield.fieldname==="_comments") docfield.fieldtype = "Comment";
  203. return wn.format(value, docfield, null, dataContext);
  204. }
  205. }
  206. return coldef;
  207. });
  208. },
  209. // render data
  210. render_list: function() {
  211. var me = this;
  212. var columns = this.get_columns();
  213. // add sr in data
  214. $.each(this.data, function(i, v) {
  215. // add index
  216. v._idx = i+1;
  217. v.id = v._idx;
  218. });
  219. var options = {
  220. enableCellNavigation: true,
  221. enableColumnReorder: false,
  222. };
  223. if(this.slickgrid_options) {
  224. $.extend(options, this.slickgrid_options);
  225. }
  226. this.col_defs = columns;
  227. this.dataView = new Slick.Data.DataView();
  228. this.set_data(this.data);
  229. var grid_wrapper = this.$w.find('.result-list');
  230. // set height if not auto
  231. if(!options.autoHeight)
  232. grid_wrapper.css('height', '500px');
  233. this.grid = new Slick.Grid(grid_wrapper
  234. .css('border', '1px solid #ccc')
  235. .css('border-top', '0px')
  236. .get(0), this.dataView,
  237. columns, options);
  238. this.grid.setSelectionModel(new Slick.CellSelectionModel());
  239. this.grid.registerPlugin(new Slick.CellExternalCopyManager({
  240. dataItemColumnValueExtractor: function(item, columnDef, value) {
  241. return item[columnDef.field];
  242. }
  243. }));
  244. wn.slickgrid_tools.add_property_setter_on_resize(this.grid);
  245. if(this.start!=0 && !options.autoHeight) {
  246. this.grid.scrollRowIntoView(this.data.length-1);
  247. }
  248. this.grid.onDblClick.subscribe(function(e, args) {
  249. var row = me.dataView.getItem(args.row);
  250. var cell = me.grid.getColumns()[args.cell];
  251. me.edit_cell(row, cell.docfield);
  252. });
  253. this.dataView.onRowsChanged.subscribe(function (e, args) {
  254. me.grid.invalidateRows(args.rows);
  255. me.grid.render();
  256. });
  257. this.grid.onHeaderClick.subscribe(function(e, args) {
  258. var df = args.column.docfield,
  259. sort_by = df.parent + "." + df.fieldname;
  260. if(sort_by===me.sort_by_select.val()) {
  261. me.sort_order_select.val(me.sort_order_select.val()==="asc" ? "desc" : "asc");
  262. } else {
  263. me.sort_by_select.val(df.parent + "." + df.fieldname);
  264. me.sort_order_select.val("asc");
  265. }
  266. me.run();
  267. });
  268. },
  269. edit_cell: function(row, docfield) {
  270. if(wn.model.std_fields_list.indexOf(docfield.fieldname)!==-1) {
  271. wn.throw(wn._("Cannot edit standard fields"));
  272. } else if(wn.boot.profile.can_write.indexOf(this.doctype)===-1) {
  273. wn.throw(wn._("No permission to edit"));
  274. }
  275. var me = this;
  276. var d = new wn.ui.Dialog({
  277. title: wn._("Edit") + " " + wn._(docfield.label),
  278. fields: [docfield, {"fieldtype": "Button", "label": "Update"}],
  279. });
  280. d.get_input(docfield.fieldname).val(row[docfield.fieldname]);
  281. d.get_input("update").on("click", function() {
  282. wn.call({
  283. method: "webnotes.client.set_value",
  284. args: {
  285. doctype: docfield.parent,
  286. name: row[docfield.parent===me.doctype ? "name" : docfield.parent+":name"],
  287. fieldname: docfield.fieldname,
  288. value: d.get_value(docfield.fieldname)
  289. },
  290. callback: function(r) {
  291. if(!r.exc) {
  292. d.hide();
  293. var doclist = r.message;
  294. $.each(me.dataView.getItems(), function(i, item) {
  295. if (item.name === doclist[0].name) {
  296. var new_item = $.extend({}, item, doclist[0]);
  297. $.each(doclist, function(i, doc) {
  298. if(item[doc.doctype + ":name"]===doc.name) {
  299. $.each(doc, function(k, v) {
  300. if(wn.model.std_fields_list.indexOf(k)===-1) {
  301. new_item[k] = v;
  302. }
  303. })
  304. }
  305. });
  306. me.dataView.updateItem(item.id, new_item);
  307. }
  308. });
  309. }
  310. }
  311. });
  312. });
  313. d.show();
  314. },
  315. set_data: function() {
  316. this.dataView.beginUpdate();
  317. this.dataView.setItems(this.data);
  318. this.dataView.endUpdate();
  319. },
  320. get_columns: function() {
  321. var std_columns = [{id:'_idx', field:'_idx', name: 'Sr.', width: 40, maxWidth: 40}];
  322. if(this.can_delete) {
  323. std_columns = std_columns.concat([{
  324. id:'_check', field:'_check', name: "", width: 30, maxWidth: 30,
  325. formatter: function(row, cell, value, columnDef, dataContext) {
  326. return repl("<input type='checkbox' \
  327. data-row='%(row)s' %(checked)s>", {
  328. row: row,
  329. checked: (dataContext._checked ? "checked=\"checked\"" : "")
  330. });
  331. }
  332. }]);
  333. }
  334. return std_columns.concat(this.build_columns());
  335. },
  336. // setup column picker
  337. make_column_picker: function() {
  338. var me = this;
  339. this.column_picker = new wn.ui.ColumnPicker(this);
  340. this.page.appframe.add_button(wn._('Pick Columns'), function() {
  341. me.column_picker.show(me.columns);
  342. }, 'icon-th-list');
  343. },
  344. set_tag_and_status_filter: function() {
  345. var me = this;
  346. this.$w.find('.result-list').on("click", ".label-info", function() {
  347. if($(this).attr("data-label")) {
  348. me.set_filter("_user_tags", $(this).attr("data-label"));
  349. }
  350. });
  351. this.$w.find('.result-list').on("click", "[data-workflow-state]", function() {
  352. if($(this).attr("data-workflow-state")) {
  353. me.set_filter(me.state_fieldname,
  354. $(this).attr("data-workflow-state"));
  355. }
  356. });
  357. },
  358. // setup sorter
  359. make_sorter: function() {
  360. var me = this;
  361. this.sort_dialog = new wn.ui.Dialog({title:'Sorting Preferences'});
  362. $(this.sort_dialog.body).html('<p class="help">Sort By</p>\
  363. <div class="sort-column"></div>\
  364. <div><select class="sort-order form-control" style="margin-top: 10px; width: 60%;">\
  365. <option value="asc">'+wn._('Ascending')+'</option>\
  366. <option value="desc">'+wn._('Descending')+'</option>\
  367. </select></div>\
  368. <hr><p class="help">'+wn._('Then By (optional)')+'</p>\
  369. <div class="sort-column-1"></div>\
  370. <div><select class="sort-order-1 form-control" style="margin-top: 10px; width: 60%;">\
  371. <option value="asc">'+wn._('Ascending')+'</option>\
  372. <option value="desc">'+wn._('Descending')+'</option>\
  373. </select></div><hr>\
  374. <div><button class="btn btn-info">'+wn._('Update')+'</div>');
  375. // first
  376. this.sort_by_select = new wn.ui.FieldSelect({
  377. parent: $(this.sort_dialog.body).find('.sort-column'),
  378. doctype: this.doctype
  379. });
  380. this.sort_by_select.$select.css('width', '60%');
  381. this.sort_order_select = $(this.sort_dialog.body).find('.sort-order');
  382. // second
  383. this.sort_by_next_select = new wn.ui.FieldSelect({
  384. parent: $(this.sort_dialog.body).find('.sort-column-1'),
  385. doctype: this.doctype,
  386. with_blank: true
  387. });
  388. this.sort_by_next_select.$select.css('width', '60%');
  389. this.sort_order_next_select = $(this.sort_dialog.body).find('.sort-order-1');
  390. // initial values
  391. this.sort_by_select.set_value(this.doctype, 'modified');
  392. this.sort_order_select.val('desc');
  393. this.sort_by_next_select.clear();
  394. this.sort_order_next_select.val('desc');
  395. // button actions
  396. this.page.appframe.add_button(wn._('Sort By'), function() {
  397. me.sort_dialog.show();
  398. }, 'icon-sort-by-alphabet');
  399. $(this.sort_dialog.body).find('.btn-info').click(function() {
  400. me.sort_dialog.hide();
  401. me.run();
  402. });
  403. },
  404. // setup export
  405. make_export: function() {
  406. var me = this;
  407. if(!wn.model.can_export(this.doctype)) {
  408. return;
  409. }
  410. var export_btn = this.page.appframe.add_button(wn._('Export'), function() {
  411. var args = me.get_args();
  412. args.cmd = 'webnotes.widgets.reportview.export_query'
  413. open_url_post(wn.request.url, args);
  414. }, 'icon-download-alt');
  415. },
  416. // save
  417. make_save: function() {
  418. var me = this;
  419. if(wn.user.is_report_manager()) {
  420. this.page.appframe.add_button(wn._('Save'), function() {
  421. // name
  422. if(me.docname) {
  423. var name = me.docname
  424. } else {
  425. var name = prompt(wn._('Select Report Name'));
  426. if(!name) {
  427. return;
  428. }
  429. }
  430. // callback
  431. return wn.call({
  432. method: 'webnotes.widgets.reportview.save_report',
  433. args: {
  434. name: name,
  435. doctype: me.doctype,
  436. json: JSON.stringify({
  437. filters: me.filter_list.get_filters(),
  438. columns: me.columns,
  439. sort_by: me.sort_by_select.val(),
  440. sort_order: me.sort_order_select.val(),
  441. sort_by_next: me.sort_by_next_select.val(),
  442. sort_order_next: me.sort_order_next_select.val()
  443. })
  444. },
  445. callback: function(r) {
  446. if(r.exc) {
  447. msgprint(wn._("Report was not saved (there were errors)"));
  448. return;
  449. }
  450. if(r.message != me.docname)
  451. wn.set_route('Report', me.doctype, r.message);
  452. }
  453. });
  454. }, 'icon-save');
  455. }
  456. },
  457. make_delete: function() {
  458. var me = this;
  459. if(this.can_delete) {
  460. $(this.page).on("click", "input[type='checkbox'][data-row]", function() {
  461. me.data[$(this).attr("data-row")]._checked
  462. = this.checked ? true : false;
  463. });
  464. this.page.appframe.add_button(wn._("Delete"), function() {
  465. var delete_list = []
  466. $.each(me.data, function(i, d) {
  467. if(d._checked) {
  468. if(d.name)
  469. delete_list.push(d.name);
  470. }
  471. });
  472. if(!delete_list.length)
  473. return;
  474. if(wn.confirm(wn._("This is PERMANENT action and you cannot undo. Continue?"),
  475. function() {
  476. return wn.call({
  477. method: 'webnotes.widgets.reportview.delete_items',
  478. args: {
  479. items: delete_list,
  480. doctype: me.doctype
  481. },
  482. callback: function() {
  483. me.refresh();
  484. }
  485. });
  486. }));
  487. }, 'icon-remove');
  488. }
  489. },
  490. make_user_restrictions: function() {
  491. var me = this;
  492. if(this.docname && wn.model.can_restrict("Report")) {
  493. this.page.appframe.add_button(wn._("User Permission Restrictions"), function() {
  494. wn.route_options = {
  495. property: "Report",
  496. restriction: me.docname
  497. };
  498. wn.set_route("user-properties");
  499. }, "icon-shield");
  500. }
  501. },
  502. });
  503. wn.ui.ColumnPicker = Class.extend({
  504. init: function(list) {
  505. this.list = list;
  506. this.doctype = list.doctype;
  507. },
  508. clear: function() {
  509. this.columns = [];
  510. $(this.dialog.body).html('<div class="help">'+wn._("Drag to sort columns")+'</div>\
  511. <div class="column-list"></div>\
  512. <div><button class="btn btn-default btn-add"><i class="icon-plus"></i>\
  513. '+wn._("Add Column")+'</button></div>\
  514. <hr>\
  515. <div><button class="btn btn-info">'+wn._("Update")+'</div>');
  516. },
  517. show: function(columns) {
  518. var me = this;
  519. if(!this.dialog) {
  520. this.dialog = new wn.ui.Dialog({
  521. title: wn._("Pick Columns"),
  522. width: '400'
  523. });
  524. }
  525. this.clear();
  526. // show existing
  527. $.each(columns, function(i, c) {
  528. me.add_column(c);
  529. });
  530. $(this.dialog.body).find('.column-list').sortable();
  531. // add column
  532. $(this.dialog.body).find('.btn-add').click(function() {
  533. me.add_column(['name']);
  534. });
  535. // update
  536. $(this.dialog.body).find('.btn-info').click(function() {
  537. me.dialog.hide();
  538. // selected columns as list of [column_name, table_name]
  539. var columns = $.map(me.columns, function(v) {
  540. return v ? [[v.selected_fieldname, v.selected_doctype]] : null;
  541. });
  542. wn.defaults.set_default("_list_settings:" + me.doctype, columns);
  543. me.list.columns = columns;
  544. me.list.run();
  545. });
  546. this.dialog.show();
  547. },
  548. add_column: function(c) {
  549. if(!c) return;
  550. var w = $('<div style="padding: 5px; background-color: #eee; \
  551. width: 90%; margin-bottom: 10px; border-radius: 3px; cursor: move;">\
  552. <img src="assets/webnotes/images/ui/drag-handle.png" style="margin-right: 10px;">\
  553. <a class="close" style="margin-top: 5px;">&times</a>\
  554. </div>')
  555. .appendTo($(this.dialog.body).find('.column-list'));
  556. var fieldselect = new wn.ui.FieldSelect({parent:w, doctype:this.doctype}),
  557. me = this;
  558. fieldselect.$select.css({"display": "inline"});
  559. fieldselect.$select.css({width: '70%', 'margin-top':'5px'})
  560. fieldselect.val((c[1] || this.doctype) + "." + c[0]);
  561. w.find('.close').data("fieldselect", fieldselect).click(function() {
  562. console.log(me.columns.indexOf($(this).data('fieldselect')));
  563. delete me.columns[me.columns.indexOf($(this).data('fieldselect'))];
  564. $(this).parent().remove();
  565. });
  566. this.columns.push(fieldselect);
  567. }
  568. });