You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

923 regels
26 KiB

  1. // Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
  2. // MIT License. See license.txt
  3. wn.provide("wn.report_dump");
  4. $.extend(wn.report_dump, {
  5. data: {},
  6. last_modified: {},
  7. with_data: function(doctypes, callback, progress_bar) {
  8. var pre_loaded = keys(wn.report_dump.last_modified);
  9. return wn.call({
  10. method: "webnotes.widgets.report_dump.get_data",
  11. type: "GET",
  12. args: {
  13. doctypes: doctypes,
  14. last_modified: wn.report_dump.last_modified
  15. },
  16. callback: function(r) {
  17. // creating map of data from a list
  18. $.each(r.message, function(doctype, doctype_data) {
  19. wn.report_dump.set_data(doctype, doctype_data);
  20. });
  21. // reverse map names
  22. $.each(r.message, function(doctype, doctype_data) {
  23. // only if not pre-loaded
  24. if(!in_list(pre_loaded, doctype)) {
  25. if(doctype_data.links) {
  26. $.each(wn.report_dump.data[doctype], function(row_idx, row) {
  27. $.each(doctype_data.links, function(link_key, link) {
  28. if(wn.report_dump.data[link[0]][row[link_key]]) {
  29. row[link_key] = wn.report_dump.data[link[0]][row[link_key]][link[1]];
  30. } else {
  31. row[link_key] = null;
  32. }
  33. })
  34. })
  35. }
  36. }
  37. });
  38. callback();
  39. },
  40. progress_bar: progress_bar
  41. })
  42. },
  43. set_data: function(doctype, doctype_data) {
  44. var data = [];
  45. var replace_dict = {};
  46. var make_row = function(d) {
  47. var row = {};
  48. $.each(doctype_data.columns, function(idx, col) {
  49. row[col] = d[idx];
  50. });
  51. row.id = row.name;
  52. row.doctype = doctype;
  53. return row;
  54. }
  55. if(wn.report_dump.last_modified[doctype]) {
  56. // partial loading, make a name dict
  57. $.each(doctype_data.data, function(i, d) {
  58. var row = make_row(d);
  59. replace_dict[row.name] = row;
  60. });
  61. // replace old data
  62. $.each(wn.report_dump.data[doctype], function(i, d) {
  63. if(replace_dict[d.name]) {
  64. data.push(replace_dict[d.name]);
  65. delete replace_dict[d.name];
  66. } else if(doctype_data.modified_names.indexOf(d.name)!==-1) {
  67. // if modified but not in replace_dict, then assume it as cancelled
  68. // don't push in data
  69. } else {
  70. data.push(d);
  71. }
  72. });
  73. // add new records
  74. $.each(replace_dict, function(name, d) {
  75. data.push(d);
  76. })
  77. } else {
  78. // first loading
  79. $.each(doctype_data.data, function(i, d) {
  80. data.push(make_row(d));
  81. });
  82. }
  83. wn.report_dump.last_modified[doctype] = doctype_data.last_modified;
  84. wn.report_dump.data[doctype] = data;
  85. }
  86. });
  87. wn.provide("wn.views");
  88. wn.views.GridReport = Class.extend({
  89. init: function(opts) {
  90. wn.require("assets/js/slickgrid.min.js");
  91. this.filter_inputs = {};
  92. this.preset_checks = [];
  93. this.tree_grid = {show: false};
  94. var me = this;
  95. $.extend(this, opts);
  96. this.wrapper = $('<div>').appendTo(this.parent);
  97. if(this.filters) {
  98. this.make_filters();
  99. }
  100. this.make_waiting();
  101. this.get_data_and_refresh();
  102. },
  103. bind_show: function() {
  104. // bind show event to reset cur_report_grid
  105. // and refresh filters from url
  106. // this must be called after init
  107. // because "wn.container.page" will only be set
  108. // once "load" event is over.
  109. var me = this;
  110. $(this.page).bind('show', function() {
  111. // reapply filters on show
  112. wn.cur_grid_report = me;
  113. me.get_data_and_refresh();
  114. });
  115. },
  116. get_data_and_refresh: function() {
  117. var me = this;
  118. this.get_data(function() {
  119. me.apply_filters_from_route();
  120. me.refresh();
  121. });
  122. },
  123. get_data: function(callback) {
  124. var me = this;
  125. var progress_bar = null;
  126. if(!this.setup_filters_done)
  127. progress_bar = this.wrapper.find(".progress .progress-bar");
  128. wn.report_dump.with_data(this.doctypes, function() {
  129. if(!me.setup_filters_done) {
  130. me.setup_filters();
  131. me.setup_filters_done = true;
  132. }
  133. callback();
  134. }, progress_bar);
  135. },
  136. setup_filters: function() {
  137. var me = this;
  138. $.each(me.filter_inputs, function(i, v) {
  139. var opts = v.get(0).opts;
  140. if(opts.fieldtype == "Select" && inList(me.doctypes, opts.link)) {
  141. $(v).add_options($.map(wn.report_dump.data[opts.link],
  142. function(d) { return d.name; }));
  143. } else if(opts.fieldtype == "Link" && inList(me.doctypes, opts.link)) {
  144. opts.list = $.map(wn.report_dump.data[opts.link],
  145. function(d) { return d.name; });
  146. me.set_autocomplete(v, opts.list);
  147. }
  148. });
  149. // refresh
  150. this.filter_inputs.refresh && this.filter_inputs.refresh.click(function() {
  151. me.get_data(function() {
  152. me.refresh();
  153. });
  154. });
  155. // reset filters
  156. this.filter_inputs.reset_filters && this.filter_inputs.reset_filters.click(function() {
  157. me.init_filter_values();
  158. me.refresh();
  159. });
  160. // range
  161. this.filter_inputs.range && this.filter_inputs.range.on("change", function() {
  162. me.refresh();
  163. });
  164. // plot check
  165. if(this.setup_plot_check)
  166. this.setup_plot_check();
  167. },
  168. set_filter: function(key, value) {
  169. var filters = this.filter_inputs[key];
  170. if(filters) {
  171. var opts = filters.get(0).opts;
  172. if(opts.fieldtype === "Check") {
  173. filters.prop("checked", cint(value) ? true : false);
  174. } if(opts.fieldtype=="Date") {
  175. filters.val(wn.datetime.str_to_user(value));
  176. } else {
  177. filters.val(value);
  178. }
  179. } else {
  180. msgprint("Invalid Filter: " + key)
  181. }
  182. },
  183. set_autocomplete: function($filter, list) {
  184. var me = this;
  185. $filter.autocomplete({
  186. source: list,
  187. select: function(event, ui) {
  188. $filter.val(ui.item.value);
  189. me.refresh();
  190. }
  191. });
  192. },
  193. init_filter_values: function() {
  194. var me = this;
  195. $.each(this.filter_inputs, function(key, filter) {
  196. var opts = filter.get(0).opts;
  197. if(sys_defaults[key]) {
  198. filter.val(sys_defaults[key]);
  199. } else if(opts.fieldtype=='Select') {
  200. filter.get(0).selectedIndex = 0;
  201. } else if(opts.fieldtype=='Data') {
  202. filter.val("");
  203. } else if(opts.fieldtype=="Link") {
  204. filter.val("");
  205. }
  206. });
  207. this.set_default_values();
  208. },
  209. set_default_values: function() {
  210. var values = {
  211. from_date: dateutil.str_to_user(sys_defaults.year_start_date),
  212. to_date: dateutil.str_to_user(sys_defaults.year_end_date)
  213. }
  214. var me = this;
  215. $.each(values, function(i, v) {
  216. if(me.filter_inputs[i] && !me.filter_inputs[i].val())
  217. me.filter_inputs[i].val(v);
  218. })
  219. },
  220. make_filters: function() {
  221. var me = this;
  222. $.each(this.filters, function(i, v) {
  223. v.fieldname = v.fieldname || v.label.replace(/ /g, '_').toLowerCase();
  224. var input = null;
  225. if(v.fieldtype=='Select') {
  226. input = me.appframe.add_select(v.label, v.options || [v.default_value]);
  227. } else if(v.fieldtype=="Link") {
  228. input = me.appframe.add_data(v.label);
  229. input.autocomplete({
  230. source: v.list || [],
  231. });
  232. } else if(v.fieldtype==='Button' && v.label==="Refresh") {
  233. input = me.appframe.set_title_right(v.label, null, v.icon);
  234. } else if(v.fieldtype==='Button') {
  235. input = me.appframe.add_primary_action(v.label, null, v.icon);
  236. } else if(v.fieldtype==='Date') {
  237. input = me.appframe.add_date(v.label);
  238. } else if(v.fieldtype==='Label') {
  239. input = me.appframe.add_label(v.label);
  240. } else if(v.fieldtype==='Data') {
  241. input = me.appframe.add_data(v.label);
  242. } else if(v.fieldtype==='Check') {
  243. input = me.appframe.add_check(v.label);
  244. }
  245. if(input) {
  246. input && (input.get(0).opts = v);
  247. if(v.cssClass) {
  248. input.addClass(v.cssClass);
  249. }
  250. input.keypress(function(e) {
  251. if(e.which==13) {
  252. me.refresh();
  253. }
  254. })
  255. }
  256. me.filter_inputs[v.fieldname] = input;
  257. });
  258. },
  259. make_waiting: function() {
  260. this.waiting = wn.messages.waiting(this.wrapper, wn._("Loading Report")+"...", '10');
  261. },
  262. load_filter_values: function() {
  263. var me = this;
  264. $.each(this.filter_inputs, function(i, f) {
  265. var opts = f.get(0).opts;
  266. if(opts.fieldtype=='Check') {
  267. me[opts.fieldname] = f.prop('checked') ? 1 : 0;
  268. } else if(opts.fieldtype!='Button') {
  269. me[opts.fieldname] = f.val();
  270. if(opts.fieldtype=="Date") {
  271. me[opts.fieldname] = dateutil.user_to_str(me[opts.fieldname]);
  272. } else if (opts.fieldtype == "Select") {
  273. me[opts.fieldname+'_default'] = opts.default_value;
  274. }
  275. }
  276. });
  277. if(this.filter_inputs.from_date && this.filter_inputs.to_date && (this.to_date < this.from_date)) {
  278. msgprint(wn._("From Date must be before To Date"));
  279. return;
  280. }
  281. },
  282. make_name_map: function(data, key) {
  283. var map = {};
  284. key = key || "name";
  285. $.each(data, function(i, v) {
  286. map[v[key]] = v;
  287. })
  288. return map;
  289. },
  290. reset_item_values: function(item) {
  291. var me = this;
  292. $.each(this.columns, function(i, col) {
  293. if (col.formatter==me.currency_formatter) {
  294. item[col.id] = 0.0;
  295. }
  296. });
  297. },
  298. round_item_values: function(item) {
  299. var me = this;
  300. $.each(this.columns, function(i, col) {
  301. if (col.formatter==me.currency_formatter) {
  302. item[col.id] = flt(item[col.id], wn.defaults.get_default("float_precision") || 3);
  303. }
  304. });
  305. },
  306. round_off_data: function() {
  307. var me = this;
  308. $.each(this.data, function(i, d) {
  309. me.round_item_values(d);
  310. });
  311. },
  312. refresh: function() {
  313. this.waiting.toggle(false);
  314. if(!this.grid_wrapper)
  315. this.make();
  316. this.show_zero = $('.show-zero input:checked').length;
  317. this.load_filter_values();
  318. this.setup_columns();
  319. this.setup_dataview_columns();
  320. this.apply_link_formatters();
  321. this.prepare_data();
  322. this.round_off_data();
  323. this.prepare_data_view();
  324. // plot might need prepared data
  325. this.wrapper.find(".processing").toggle(true);
  326. this.wrapper.find(".processing").delay(2000).fadeOut(300);
  327. this.render();
  328. this.render_plot && this.render_plot();
  329. },
  330. setup_dataview_columns: function() {
  331. this.dataview_columns = $.map(this.columns, function(col) {
  332. return !col.hidden ? col : null;
  333. });
  334. },
  335. make: function() {
  336. var me = this;
  337. // plot wrapper
  338. this.plot_area = $('<div class="plot" style="margin-bottom: 15px; display: none; \
  339. height: 300px; width: 100%;"></div>').appendTo(this.wrapper);
  340. // print / export
  341. $('<div style="text-align: right;"> \
  342. <div class="processing" style="background-color: #fec; display: none; \
  343. float: left; margin: 2px">Updated! </div> \
  344. <a href="#" class="grid-report-export"> \
  345. <i class="icon icon-download-alt"></i> Export</a> \
  346. </div>').appendTo(this.wrapper);
  347. this.wrapper.find(".grid-report-export").click(function() { return me.export(); });
  348. // grid wrapper
  349. this.grid_wrapper = $("<div style='height: 500px; border: 1px solid #aaa; \
  350. background-color: #eee; margin-top: 15px;'>")
  351. .appendTo(this.wrapper);
  352. this.id = wn.dom.set_unique_id(this.grid_wrapper.get(0));
  353. // zero-value check
  354. $('<div style="margin: 10px 0px; display: none" class="show-zero">\
  355. <input type="checkbox"> '+wn._('Show rows with zero values')
  356. +'</div>').appendTo(this.wrapper);
  357. this.bind_show();
  358. wn.cur_grid_report = this;
  359. $(this.wrapper).trigger('make');
  360. },
  361. apply_filters_from_route: function() {
  362. var me = this;
  363. if(wn.route_options) {
  364. $.each(wn.route_options, function(key, value) {
  365. me.set_filter(key, value);
  366. });
  367. wn.route_options = null;
  368. } else {
  369. this.init_filter_values();
  370. }
  371. this.set_default_values();
  372. $(this.wrapper).trigger('apply_filters_from_route');
  373. },
  374. options: {
  375. editable: false,
  376. enableColumnReorder: false
  377. },
  378. render: function() {
  379. // new slick grid
  380. this.grid = new Slick.Grid("#"+this.id, this.dataView, this.dataview_columns, this.options);
  381. var me = this;
  382. this.grid.setSelectionModel(new Slick.CellSelectionModel());
  383. this.grid.registerPlugin(new Slick.CellExternalCopyManager({
  384. dataItemColumnValueExtractor: function(item, columnDef, value) {
  385. return item[columnDef.field];
  386. }
  387. }));
  388. // bind events
  389. this.dataView.onRowsChanged.subscribe(function (e, args) {
  390. me.grid.invalidateRows(args.rows);
  391. me.grid.render();
  392. });
  393. this.dataView.onRowCountChanged.subscribe(function (e, args) {
  394. me.grid.updateRowCount();
  395. me.grid.render();
  396. });
  397. this.tree_grid.show && this.add_tree_grid_events();
  398. },
  399. prepare_data_view: function() {
  400. // initialize the model
  401. this.dataView = new Slick.Data.DataView({ inlineFilters: true });
  402. this.dataView.beginUpdate();
  403. this.dataView.setItems(this.data);
  404. if(this.dataview_filter) this.dataView.setFilter(this.dataview_filter);
  405. if(this.tree_grid.show) this.dataView.setFilter(this.tree_dataview_filter);
  406. this.dataView.endUpdate();
  407. },
  408. export: function() {
  409. wn.tools.downloadify(wn.slickgrid_tools.get_view_data(this.columns, this.dataView),
  410. ["Report Manager", "System Manager"], this);
  411. return false;
  412. },
  413. apply_filters: function(item) {
  414. // generic filter: apply filter functiions
  415. // from all filter_inputs
  416. var filters = this.filter_inputs;
  417. if(item._show) return true;
  418. for (i in filters) {
  419. if(!this.apply_filter(item, i)) {
  420. return false;
  421. }
  422. }
  423. return true;
  424. },
  425. apply_filter: function(item, fieldname) {
  426. var filter = this.filter_inputs[fieldname].get(0);
  427. if(filter.opts.filter) {
  428. if(!filter.opts.filter(this[filter.opts.fieldname], item, filter.opts, this)) {
  429. return false;
  430. }
  431. }
  432. return true;
  433. },
  434. apply_zero_filter: function(val, item, opts, me) {
  435. // show only non-zero values
  436. if(!me.show_zero) {
  437. for(var i=0, j=me.columns.length; i<j; i++) {
  438. var col = me.columns[i];
  439. if(col.formatter==me.currency_formatter && !col.hidden) {
  440. if(flt(item[col.field]) > 0.001 || flt(item[col.field]) < -0.001) {
  441. return true;
  442. }
  443. }
  444. }
  445. return false;
  446. }
  447. return true;
  448. },
  449. show_zero_check: function() {
  450. var me = this;
  451. this.wrapper.bind('make', function() {
  452. me.wrapper.find('.show-zero').toggle(true).find('input').click(function(){
  453. me.refresh();
  454. });
  455. });
  456. },
  457. is_default: function(fieldname) {
  458. return this[fieldname]==this[fieldname + "_default"];
  459. },
  460. date_formatter: function(row, cell, value, columnDef, dataContext) {
  461. return dateutil.str_to_user(value);
  462. },
  463. currency_formatter: function(row, cell, value, columnDef, dataContext) {
  464. return repl('<div style="text-align: right; %(_style)s">%(value)s</div>', {
  465. _style: dataContext._style || "",
  466. value: ((value==null || value==="") ? "" : format_number(value))
  467. });
  468. },
  469. text_formatter: function(row, cell, value, columnDef, dataContext) {
  470. return repl('<span style="%(_style)s" title="%(esc_value)s">%(value)s</span>', {
  471. _style: dataContext._style || "",
  472. esc_value: cstr(value).replace(/"/g, '\"'),
  473. value: cstr(value)
  474. });
  475. },
  476. check_formatter: function(row, cell, value, columnDef, dataContext) {
  477. return repl('<input type="checkbox" data-id="%(id)s" \
  478. class="plot-check" %(checked)s>', {
  479. "id": dataContext.id,
  480. "checked": dataContext.checked ? 'checked="checked"' : ""
  481. })
  482. },
  483. apply_link_formatters: function() {
  484. var me = this;
  485. $.each(this.dataview_columns, function(i, col) {
  486. if(col.link_formatter) {
  487. col.formatter = function(row, cell, value, columnDef, dataContext) {
  488. // added link and open button to links
  489. // link_formatter must have
  490. // filter_input, open_btn (true / false), doctype (will be eval'd)
  491. if(!value) return "";
  492. var me = wn.cur_grid_report;
  493. if(dataContext._show) {
  494. return repl('<span style="%(_style)s">%(value)s</span>', {
  495. _style: dataContext._style || "",
  496. value: value
  497. });
  498. }
  499. // make link to add a filter
  500. var link_formatter = me.dataview_columns[cell].link_formatter;
  501. if (link_formatter.filter_input) {
  502. var html = repl('<a href="#" \
  503. onclick="wn.cur_grid_report.set_filter(\'%(col_name)s\', \'%(value)s\'); \
  504. wn.cur_grid_report.refresh(); return false;">\
  505. %(value)s</a>', {
  506. value: value,
  507. col_name: link_formatter.filter_input,
  508. page_name: wn.container.page.page_name
  509. });
  510. } else {
  511. var html = value;
  512. }
  513. // make icon to open form
  514. if(link_formatter.open_btn) {
  515. var doctype = link_formatter.doctype
  516. ? eval(link_formatter.doctype)
  517. : dataContext.doctype;
  518. html += me.get_link_open_icon(doctype, value);
  519. }
  520. return html;
  521. }
  522. }
  523. })
  524. },
  525. get_link_open_icon: function(doctype, name) {
  526. return repl(' <a href="#Form/%(doctype)s/%(name)s">\
  527. <i class="icon icon-share" style="cursor: pointer;"></i></a>', {
  528. doctype: doctype,
  529. name: encodeURIComponent(name)
  530. });
  531. },
  532. make_date_range_columns: function() {
  533. this.columns = [];
  534. var me = this;
  535. var range = this.filter_inputs.range.val();
  536. this.from_date = dateutil.user_to_str(this.filter_inputs.from_date.val());
  537. this.to_date = dateutil.user_to_str(this.filter_inputs.to_date.val());
  538. var date_diff = dateutil.get_diff(this.to_date, this.from_date);
  539. me.column_map = {};
  540. var add_column = function(date) {
  541. me.columns.push({
  542. id: date,
  543. name: dateutil.str_to_user(date),
  544. field: date,
  545. formatter: me.currency_formatter,
  546. width: 100
  547. });
  548. }
  549. var build_columns = function(condition) {
  550. // add column for each date range
  551. for(var i=0; i <= date_diff; i++) {
  552. var date = dateutil.add_days(me.from_date, i);
  553. if(!condition) condition = function() { return true; }
  554. if(condition(date)) add_column(date);
  555. me.last_date = date;
  556. if(me.columns.length) {
  557. me.column_map[date] = me.columns[me.columns.length-1];
  558. }
  559. }
  560. }
  561. // make columns for all date ranges
  562. if(range=='Daily') {
  563. build_columns();
  564. } else if(range=='Weekly') {
  565. build_columns(function(date) {
  566. if(!me.last_date) return true;
  567. return !(dateutil.get_diff(date, me.from_date) % 7)
  568. });
  569. } else if(range=='Monthly') {
  570. build_columns(function(date) {
  571. if(!me.last_date) return true;
  572. return dateutil.str_to_obj(me.last_date).getMonth() != dateutil.str_to_obj(date).getMonth()
  573. });
  574. } else if(range=='Quarterly') {
  575. build_columns(function(date) {
  576. if(!me.last_date) return true;
  577. return dateutil.str_to_obj(date).getDate()==1 && in_list([0,3,6,9], dateutil.str_to_obj(date).getMonth())
  578. });
  579. } else if(range=='Yearly') {
  580. build_columns(function(date) {
  581. if(!me.last_date) return true;
  582. return $.map(wn.report_dump.data['Fiscal Year'], function(v) {
  583. return date==v.year_start_date ? true : null;
  584. }).length;
  585. });
  586. }
  587. // set label as last date of period
  588. $.each(this.columns, function(i, col) {
  589. col.name = me.columns[i+1]
  590. ? dateutil.str_to_user(dateutil.add_days(me.columns[i+1].id, -1))
  591. : dateutil.str_to_user(me.to_date);
  592. });
  593. },
  594. trigger_refresh_on_change: function(filters) {
  595. var me = this;
  596. $.each(filters, function(i, f) {
  597. me.filter_inputs[f] && me.filter_inputs[f].on("change", function() {
  598. me.refresh();
  599. });
  600. });
  601. }
  602. });
  603. wn.views.GridReportWithPlot = wn.views.GridReport.extend({
  604. render_plot: function() {
  605. var plot_data = this.get_plot_data ? this.get_plot_data() : null;
  606. if(!plot_data) {
  607. this.plot_area.toggle(false);
  608. return;
  609. }
  610. wn.require('assets/webnotes/js/lib/flot/jquery.flot.js');
  611. wn.require('assets/webnotes/js/lib/flot/jquery.flot.downsample.js');
  612. this.plot = $.plot(this.plot_area.toggle(true), plot_data,
  613. this.get_plot_options());
  614. this.setup_plot_hover();
  615. },
  616. setup_plot_check: function() {
  617. var me = this;
  618. me.wrapper.bind('make', function() {
  619. me.wrapper.on("click", ".plot-check", function() {
  620. var checked = $(this).prop("checked");
  621. var id = $(this).attr("data-id");
  622. if(me.item_by_name) {
  623. if(me.item_by_name[id]) {
  624. me.item_by_name[id].checked = checked ? true : false;
  625. }
  626. } else {
  627. $.each(me.data, function(i, d) {
  628. if(d.id==id) d.checked = checked;
  629. });
  630. }
  631. me.render_plot();
  632. });
  633. });
  634. },
  635. setup_plot_hover: function() {
  636. var me = this;
  637. this.tooltip_id = wn.dom.set_unique_id();
  638. function showTooltip(x, y, contents) {
  639. $('<div id="' + me.tooltip_id + '">' + contents + '</div>').css( {
  640. position: 'absolute',
  641. display: 'none',
  642. top: y + 5,
  643. left: x + 5,
  644. border: '1px solid #fdd',
  645. padding: '2px',
  646. 'background-color': '#fee',
  647. opacity: 0.80
  648. }).appendTo("body").fadeIn(200);
  649. }
  650. this.previousPoint = null;
  651. this.wrapper.find('.plot').bind("plothover", function (event, pos, item) {
  652. if (item) {
  653. if (me.previousPoint != item.dataIndex) {
  654. me.previousPoint = item.dataIndex;
  655. $("#" + me.tooltip_id).remove();
  656. showTooltip(item.pageX, item.pageY,
  657. me.get_tooltip_text(item.series.label, item.datapoint[0], item.datapoint[1]));
  658. }
  659. }
  660. else {
  661. $("#" + me.tooltip_id).remove();
  662. me.previousPoint = null;
  663. }
  664. });
  665. },
  666. get_tooltip_text: function(label, x, y) {
  667. var date = dateutil.obj_to_user(new Date(x));
  668. var value = format_number(y);
  669. return value + " on " + date;
  670. },
  671. get_plot_data: function() {
  672. var data = [];
  673. var me = this;
  674. $.each(this.data, function(i, item) {
  675. if (item.checked) {
  676. data.push({
  677. label: item.name,
  678. data: $.map(me.columns, function(col, idx) {
  679. if(col.formatter==me.currency_formatter && !col.hidden && col.plot!==false) {
  680. return me.get_plot_points(item, col, idx)
  681. }
  682. }),
  683. points: {show: true},
  684. lines: {show: true, fill: true},
  685. });
  686. // prepend opening
  687. data[data.length-1].data = [[dateutil.str_to_obj(me.from_date).getTime(),
  688. item.opening]].concat(data[data.length-1].data);
  689. }
  690. });
  691. return data.length ? data : false;
  692. },
  693. get_plot_options: function() {
  694. return {
  695. grid: { hoverable: true, clickable: true },
  696. xaxis: { mode: "time",
  697. min: dateutil.str_to_obj(this.from_date).getTime(),
  698. max: dateutil.str_to_obj(this.to_date).getTime() },
  699. series: { downsample: { threshold: 1000 } }
  700. }
  701. }
  702. });
  703. wn.views.TreeGridReport = wn.views.GridReportWithPlot.extend({
  704. make_transaction_list: function(parent_doctype, doctype) {
  705. var me = this;
  706. var tmap = {};
  707. $.each(wn.report_dump.data[doctype], function(i, v) {
  708. if(!tmap[v.parent]) tmap[v.parent] = [];
  709. tmap[v.parent].push(v);
  710. });
  711. if (!this.tl) this.tl = {};
  712. if (!this.tl[parent_doctype]) this.tl[parent_doctype] = [];
  713. $.each(wn.report_dump.data[parent_doctype], function(i, parent) {
  714. if(tmap[parent.name]) {
  715. $.each(tmap[parent.name], function(i, d) {
  716. me.tl[parent_doctype].push($.extend(copy_dict(parent), d));
  717. });
  718. }
  719. });
  720. },
  721. add_tree_grid_events: function() {
  722. var me = this;
  723. this.grid.onClick.subscribe(function (e, args) {
  724. if ($(e.target).hasClass("toggle")) {
  725. var item = me.dataView.getItem(args.row);
  726. if (item) {
  727. if (!item._collapsed) {
  728. item._collapsed = true;
  729. } else {
  730. item._collapsed = false;
  731. }
  732. me.dataView.updateItem(item.id, item);
  733. }
  734. e.stopImmediatePropagation();
  735. }
  736. });
  737. },
  738. tree_formatter: function (row, cell, value, columnDef, dataContext) {
  739. var me = wn.cur_grid_report;
  740. value = value.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;");
  741. var data = me.data;
  742. var spacer = "<span style='display:inline-block;height:1px;width:" +
  743. (15 * dataContext["indent"]) + "px'></span>";
  744. var idx = me.dataView.getIdxById(dataContext.id);
  745. var link = me.tree_grid.formatter(dataContext);
  746. if(dataContext.doctype) {
  747. link += me.get_link_open_icon(dataContext.doctype, dataContext.name);
  748. }
  749. if (data[idx + 1] && data[idx + 1].indent > data[idx].indent) {
  750. if (dataContext._collapsed) {
  751. return spacer + " <span class='toggle expand'></span>&nbsp;" + link;
  752. } else {
  753. return spacer + " <span class='toggle collapse'></span>&nbsp;" + link;
  754. }
  755. } else {
  756. return spacer + " <span class='toggle'></span>&nbsp;" + link;
  757. }
  758. },
  759. tree_dataview_filter: function(item) {
  760. var me = wn.cur_grid_report;
  761. if(!me.apply_filters(item)) return false;
  762. var parent = item[me.tree_grid.parent_field];
  763. while (parent) {
  764. if (me.item_by_name[parent]._collapsed) {
  765. return false;
  766. }
  767. parent = me.parent_map[parent];
  768. }
  769. return true;
  770. },
  771. prepare_tree: function(item_dt, group_dt) {
  772. var group_data = wn.report_dump.data[group_dt];
  773. var item_data = wn.report_dump.data[item_dt];
  774. // prepare map with child in respective group
  775. var me = this;
  776. var item_group_map = {};
  777. var group_ids = $.map(group_data, function(v) { return v.id; });
  778. $.each(item_data, function(i, item) {
  779. var parent = item[me.tree_grid.parent_field];
  780. if(!item_group_map[parent]) item_group_map[parent] = [];
  781. if(group_ids.indexOf(item.name)==-1) {
  782. item_group_map[parent].push(item);
  783. } else {
  784. msgprint("Ignoring Item "+ item.name.bold() +
  785. ", because a group exists with the same name!");
  786. }
  787. });
  788. // arrange items besides their parent item groups
  789. var items = [];
  790. $.each(group_data, function(i, group){
  791. group.is_group = true;
  792. items.push(group);
  793. items = items.concat(item_group_map[group.name] || []);
  794. });
  795. return items;
  796. },
  797. set_indent: function() {
  798. var me = this;
  799. $.each(this.data, function(i, d) {
  800. var indent = 0;
  801. var parent = me.parent_map[d.name];
  802. if(parent) {
  803. while(parent) {
  804. indent++;
  805. parent = me.parent_map[parent];
  806. }
  807. }
  808. d.indent = indent;
  809. });
  810. },
  811. export: function() {
  812. var msgbox = msgprint('<p>Select To Download:</p>\
  813. <p><input type="checkbox" name="with_groups" checked="checked"> With Groups</p>\
  814. <p><input type="checkbox" name="with_ledgers" checked="checked"> With Ledgers</p>\
  815. <p><button class="btn btn-info">Download</button>');
  816. var me = this;
  817. $(msgbox.body).find("button").click(function() {
  818. var with_groups = $(msgbox.body).find("[name='with_groups']").prop("checked");
  819. var with_ledgers = $(msgbox.body).find("[name='with_ledgers']").prop("checked");
  820. var data = wn.slickgrid_tools.get_view_data(me.columns, me.dataView,
  821. function(row, item) {
  822. if(with_groups) {
  823. // add row
  824. for(var i=0; i<item.indent; i++) row[0] = " " + row[0];
  825. }
  826. if(with_groups && (item.group_or_ledger == "Group" || item.is_group)) {
  827. return true;
  828. }
  829. if(with_ledgers && (item.group_or_ledger != "Group" && !item.is_group)) {
  830. return true;
  831. }
  832. return false;
  833. });
  834. wn.tools.downloadify(data, ["Report Manager", "System Manager"], me);
  835. return false;
  836. })
  837. return false;
  838. },
  839. });