Nie możesz wybrać więcej, niż 25 tematów Tematy muszą się zaczynać od litery lub cyfry, mogą zawierać myślniki ('-') i mogą mieć do 35 znaków.
 
 
 
 
 
 

447 wiersze
13 KiB

  1. // Copyright (c) 2012 Web Notes Technologies Pvt Ltd (http://erpnext.com)
  2. //
  3. // MIT License (MIT)
  4. //
  5. // Permission is hereby granted, free of charge, to any person obtaining a
  6. // copy of this software and associated documentation files (the "Software"),
  7. // to deal in the Software without restriction, including without limitation
  8. // the rights to use, copy, modify, merge, publish, distribute, sublicense,
  9. // and/or sell copies of the Software, and to permit persons to whom the
  10. // Software is furnished to do so, subject to the following conditions:
  11. //
  12. // The above copyright notice and this permission notice shall be included in
  13. // all copies or substantial portions of the Software.
  14. //
  15. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
  16. // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
  17. // PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
  18. // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
  19. // CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
  20. // OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  21. //
  22. wn.views.reportview = {
  23. show: function(dt, rep_name) {
  24. wn.require('js/report-legacy.js');
  25. dt = get_label_doctype(dt);
  26. if(!_r.rb_con) {
  27. // first load
  28. _r.rb_con = new _r.ReportContainer();
  29. }
  30. _r.rb_con.set_dt(dt, function(rb) {
  31. if(rep_name) {
  32. var t = rb.current_loaded;
  33. rb.load_criteria(rep_name);
  34. // if loaded, then run
  35. if((rb.dt) && (!rb.dt.has_data() || rb.current_loaded!=t)) {
  36. rb.dt.run();
  37. }
  38. }
  39. // show
  40. if(!rb.forbidden) {
  41. wn.container.change_to('Report Builder');
  42. }
  43. } );
  44. }
  45. }
  46. // Routing Rules
  47. // --------------
  48. // `Report` shows list of all pages from which you can start a report + all saved reports
  49. // (module wise)
  50. // `Report/[doctype]` shows report for that doctype
  51. // `Report/[doctype]/[report_name]` loads report with that name
  52. wn.views.reportview2 = {
  53. show: function(dt) {
  54. var page_name = wn.get_route_str();
  55. if(wn.pages[page_name]) {
  56. wn.container.change_to(wn.pages[page_name]);
  57. } else {
  58. var route = wn.get_route();
  59. if(route[1]) {
  60. new wn.views.ReportViewPage(route[1], route[2]);
  61. } else {
  62. wn.set_route('404');
  63. }
  64. }
  65. }
  66. }
  67. wn.views.ReportViewPage = Class.extend({
  68. init: function(doctype, docname) {
  69. this.doctype = doctype;
  70. this.docname = docname;
  71. this.page_name = wn.get_route_str();
  72. this.make_page();
  73. var me = this;
  74. wn.model.with_doctype(doctype, function() {
  75. me.make_report_view();
  76. if(docname) {
  77. wn.model.with_doc('Report', docname, function(r) {
  78. me.reportview.set_columns_and_filters(JSON.parse(locals['Report'][docname].json));
  79. me.reportview.run();
  80. });
  81. } else {
  82. me.reportview.run();
  83. }
  84. });
  85. },
  86. make_page: function() {
  87. this.page = wn.container.add_page(this.page_name);
  88. wn.ui.make_app_page({parent:this.page,
  89. single_column:true});
  90. wn.container.change_to(this.page_name);
  91. },
  92. make_report_view: function() {
  93. // add breadcrumbs
  94. wn.views.breadcrumbs($('<span>').appendTo(this.page.appframe.$titlebar),
  95. locals.DocType[this.doctype].module);
  96. this.reportview = new wn.views.ReportView(this.doctype, this.docname, this.page)
  97. }
  98. })
  99. wn.views.ReportView = wn.ui.Listing.extend({
  100. init: function(doctype, docname, page) {
  101. var me = this;
  102. $(page).find('.layout-main').html('Loading Report...');
  103. this.import_slickgrid();
  104. $(page).find('.layout-main').empty();
  105. this.doctype = doctype;
  106. this.docname = docname;
  107. this.page = page;
  108. this.tab_name = '`tab'+doctype+'`';
  109. this.setup();
  110. },
  111. import_slickgrid: function() {
  112. wn.require('js/lib/slickgrid/slick.grid.css');
  113. wn.require('js/lib/slickgrid/slick-default-theme.css');
  114. wn.require('js/lib/slickgrid/jquery.event.drag.min.js');
  115. wn.require('js/lib/slickgrid/slick.core.js');
  116. wn.require('js/lib/slickgrid/slick.grid.js');
  117. wn.dom.set_style('.slick-cell { font-size: 12px; }');
  118. },
  119. set_init_columns: function() {
  120. // pre-select mandatory columns
  121. var columns = [['name'], ['owner']];
  122. $.each(wn.meta.docfield_list[this.doctype], function(i, df) {
  123. if(df.in_filter && df.fieldname!='naming_series' && df.fieldtype!='Table') {
  124. columns.push([df.fieldname]);
  125. }
  126. });
  127. this.columns = columns;
  128. },
  129. setup: function() {
  130. var me = this;
  131. this.make({
  132. title: 'Report: ' + (this.docname ? (this.doctype + ' - ' + this.docname) : this.doctype),
  133. appframe: this.page.appframe,
  134. method: 'webnotes.widgets.doclistview.get',
  135. get_args: this.get_args,
  136. parent: $(this.page).find('.layout-main'),
  137. start: 0,
  138. page_length: 20,
  139. show_filters: true,
  140. new_doctype: this.doctype,
  141. allow_delete: true,
  142. });
  143. this.make_column_picker();
  144. this.make_sorter();
  145. this.make_export();
  146. this.set_init_columns();
  147. this.make_save();
  148. },
  149. // preset columns and filters from saved info
  150. set_columns_and_filters: function(opts) {
  151. var me = this;
  152. if(opts.columns) this.columns = opts.columns;
  153. if(opts.filters) $.each(opts.filters, function(i, f) {
  154. // fieldname, condition, value
  155. me.filter_list.add_filter(f[1], f[2], f[3]);
  156. });
  157. // first sort
  158. if(opts.sort_by) this.sort_by_select.val(opts.sort_by);
  159. if(opts.sort_order) this.sort_order_select.val(opts.sort_order);
  160. // second sort
  161. if(opts.sort_by_next) this.sort_by_next_select.val(opts.sort_by_next);
  162. if(opts.sort_order_next) this.sort_order_next_select.val(opts.sort_order_next);
  163. },
  164. // build args for query
  165. get_args: function() {
  166. var me = this;
  167. return {
  168. doctype: this.doctype,
  169. fields: $.map(this.columns, function(v) { return me.get_full_column_name(v) }),
  170. order_by: this.get_order_by(),
  171. filters: this.filter_list.get_filters(),
  172. docstatus: ['0','1','2']
  173. }
  174. },
  175. get_order_by: function() {
  176. // first
  177. var order_by = this.get_selected_table_and_column(this.sort_by_select)
  178. + ' ' + this.sort_order_select.val()
  179. // second
  180. if(this.sort_by_next_select.val()) {
  181. order_by += ', ' + this.get_selected_table_and_column(this.sort_by_next_select)
  182. + ' ' + this.sort_order_next_select.val()
  183. }
  184. return order_by;
  185. },
  186. get_selected_table_and_column: function($select) {
  187. return this.get_full_column_name([$select.val(),
  188. $select.find('option:selected').attr('table')])
  189. },
  190. // get table_name.column_name
  191. get_full_column_name: function(v) {
  192. return (v[1] ? ('`tab' + v[1] + '`') : this.tab_name) + '.' + v[0];
  193. },
  194. // build columns for slickgrid
  195. build_columns: function() {
  196. var me = this;
  197. return $.map(this.columns, function(c) {
  198. var docfield = wn.meta.docfield_map[c[1] || me.doctype][c[0]];
  199. coldef = {
  200. id: c[0],
  201. field: c[0],
  202. docfield: docfield,
  203. name: (docfield ? docfield.label : toTitle(c[0])),
  204. width: (docfield ? cint(docfield.width) : 120) || 120
  205. }
  206. if(c[0]=='name') {
  207. coldef.formatter = function(row, cell, value, columnDef, dataContext) {
  208. return repl("<a href='#!Form/%(doctype)s/%(name)s'>%(name)s</a>", {
  209. doctype: me.doctype,
  210. name: value
  211. });
  212. }
  213. } else if(docfield && docfield.fieldtype=='Link') {
  214. coldef.formatter = function(row, cell, value, columnDef, dataContext) {
  215. if(value) {
  216. return repl("<a href='#!Form/%(doctype)s/%(name)s'>%(name)s</a>", {
  217. doctype: columnDef.docfield.options,
  218. name: value
  219. });
  220. } else {
  221. return '';
  222. }
  223. }
  224. }
  225. return coldef;
  226. });
  227. },
  228. // render data
  229. render_list: function() {
  230. var me = this;
  231. //this.gridid = wn.dom.set_unique_id()
  232. var columns = [{id:'_idx', field:'_idx', name: 'Sr.', width: 40}].concat(this.build_columns());
  233. // add sr in data
  234. $.each(this.data, function(i, v) {
  235. // add index
  236. v._idx = i+1;
  237. });
  238. var options = {
  239. enableCellNavigation: true,
  240. enableColumnReorder: false
  241. };
  242. var grid = new Slick.Grid(this.$w.find('.result-list')
  243. .css('border', '1px solid grey')
  244. .css('height', '500px')
  245. .get(0), this.data,
  246. columns, options);
  247. },
  248. // setup column picker
  249. make_column_picker: function() {
  250. var me = this;
  251. this.column_picker = new wn.ui.ColumnPicker(this);
  252. this.page.appframe.add_button('Pick Columns', function() {
  253. me.column_picker.show(me.columns);
  254. }, 'icon-th-list');
  255. },
  256. // setup sorter
  257. make_sorter: function() {
  258. var me = this;
  259. this.sort_dialog = new wn.ui.Dialog({title:'Sorting Preferences'});
  260. $(this.sort_dialog.body).html('<p class="help">Sort By</p>\
  261. <div class="sort-column"></div>\
  262. <div><select class="sort-order" style="margin-top: 10px; width: 60%;">\
  263. <option value="asc">Ascending</option>\
  264. <option value="desc">Descending</option>\
  265. </select></div>\
  266. <hr><p class="help">Then By (optional)</p>\
  267. <div class="sort-column-1"></div>\
  268. <div><select class="sort-order-1" style="margin-top: 10px; width: 60%;">\
  269. <option value="asc">Ascending</option>\
  270. <option value="desc">Descending</option>\
  271. </select></div><hr>\
  272. <div><button class="btn btn-small btn-info">Update</div>');
  273. // first
  274. this.sort_by_select = new wn.ui.FieldSelect($(this.sort_dialog.body).find('.sort-column'),
  275. this.doctype).$select;
  276. this.sort_by_select.css('width', '60%');
  277. this.sort_order_select = $(this.sort_dialog.body).find('.sort-order');
  278. // second
  279. this.sort_by_next_select = new wn.ui.FieldSelect($(this.sort_dialog.body).find('.sort-column-1'),
  280. this.doctype, null, true).$select;
  281. this.sort_by_next_select.css('width', '60%');
  282. this.sort_order_next_select = $(this.sort_dialog.body).find('.sort-order-1');
  283. // initial values
  284. this.sort_by_select.val('modified');
  285. this.sort_order_select.val('desc');
  286. this.sort_by_next_select.val('');
  287. this.sort_order_next_select.val('desc');
  288. // button actions
  289. this.page.appframe.add_button('Sort By', function() {
  290. me.sort_dialog.show();
  291. }, 'icon-arrow-down');
  292. $(this.sort_dialog.body).find('.btn-info').click(function() {
  293. me.sort_dialog.hide();
  294. me.run();
  295. });
  296. },
  297. // setup export
  298. make_export: function() {
  299. var me = this;
  300. if(wn.user.is_report_manager()) {
  301. this.page.appframe.add_button('Export', function() {
  302. var args = me.get_args();
  303. args.cmd = 'webnotes.widgets.doclistview.export_query'
  304. open_url_post(wn.request.url, args);
  305. }, 'icon-download-alt');
  306. }
  307. },
  308. // save
  309. make_save: function() {
  310. var me = this;
  311. if(wn.user.is_report_manager()) {
  312. this.page.appframe.add_button('Save', function() {
  313. // name
  314. if(me.docname) {
  315. var name = me.docname
  316. } else {
  317. var name = prompt('Select Report Name');
  318. if(!name) {
  319. return;
  320. }
  321. }
  322. // callback
  323. wn.call({
  324. method: 'webnotes.widgets.doclistview.save_report',
  325. args: {
  326. name: name,
  327. doctype: me.doctype,
  328. json: JSON.stringify({
  329. filters: me.filter_list.get_filters(),
  330. columns: me.columns,
  331. sort_by: me.sort_by_select.val(),
  332. sort_order: me.sort_order_select.val(),
  333. sort_by_next: me.sort_by_next_select.val(),
  334. sort_order_next: me.sort_order_next_select.val()
  335. })
  336. },
  337. callback: function(r) {
  338. if(r.exc) return;
  339. if(r.message != me.docname)
  340. wn.set_route('Report2', me.doctype, r.message);
  341. }
  342. });
  343. }, 'icon-upload');
  344. }
  345. }
  346. });
  347. wn.ui.ColumnPicker = Class.extend({
  348. init: function(list) {
  349. this.list = list;
  350. this.doctype = list.doctype;
  351. this.selects = {};
  352. },
  353. show: function(columns) {
  354. wn.require('js/lib/jquery/jquery.ui.sortable.js');
  355. var me = this;
  356. if(!this.dialog) {
  357. this.dialog = new wn.ui.Dialog({
  358. title: 'Pick Columns',
  359. width: '400'
  360. });
  361. }
  362. $(this.dialog.body).html('<div class="help">Drag to sort columns</div>\
  363. <div class="column-list"></div>\
  364. <div><button class="btn btn-small btn-add"><i class="icon-plus"></i>\
  365. Add Column</button></div>\
  366. <hr>\
  367. <div><button class="btn btn-small btn-info">Update</div>');
  368. // show existing
  369. $.each(columns, function(i, c) {
  370. me.add_column(c);
  371. });
  372. $(this.dialog.body).find('.column-list').sortable();
  373. // add column
  374. $(this.dialog.body).find('.btn-add').click(function() {
  375. me.add_column('name');
  376. });
  377. // update
  378. $(this.dialog.body).find('.btn-info').click(function() {
  379. me.dialog.hide();
  380. // selected columns as list of [column_name, table_name]
  381. me.list.columns = [];
  382. $(me.dialog.body).find('select').each(function() {
  383. me.list.columns.push([$(this).val(),
  384. $(this).find('option:selected').attr('table')]);
  385. })
  386. me.list.run();
  387. });
  388. this.dialog.show();
  389. },
  390. add_column: function(c) {
  391. var w = $('<div style="padding: 5px 5px 5px 35px; background-color: #eee; width: 70%; \
  392. margin-bottom: 10px; border-radius: 3px; cursor: move;">\
  393. <a class="close" style="margin-top: 5px;">&times</a>\
  394. </div>')
  395. .appendTo($(this.dialog.body).find('.column-list'));
  396. var fieldselect = new wn.ui.FieldSelect(w, this.doctype);
  397. fieldselect.$select.css('width', '90%').val(c);
  398. w.find('.close').click(function() {
  399. $(this).parent().remove();
  400. });
  401. }
  402. });