您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符
 
 
 
 
 
 

601 行
17 KiB

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