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

1289 行
37 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. // ReportContainer Contains ReportBuilder objects for all DocTypes
  23. // - Only one ReportContainer exists
  24. // - Page header is als a part
  25. // - New ReportBuilder is made here
  26. _r.ReportContainer = function() {
  27. if(user=='Guest') {
  28. msgprint("Not Allowed");
  29. return;
  30. }
  31. var page = wn.container.add_page("Report Builder");
  32. this.wrapper = $a(page, 'div', 'layout-wrapper', {padding: '0px'});
  33. this.appframe = new wn.views.AppFrame(this.wrapper);
  34. this.appframe.$titlebar.append('<span class="report-title">');
  35. this.rb_area = $a(this.wrapper, 'div', '', {padding: '15px'});
  36. var me = this;
  37. this.rb_dict = {};
  38. var run_fn = function() {
  39. if(me.cur_rb){
  40. me.cur_rb.dt.start_rec = 1;
  41. me.cur_rb.dt.run();
  42. }
  43. }
  44. var runbtn = this.appframe.add_button('Run', run_fn, 'icon-refresh');
  45. // refresh btn
  46. this.appframe.add_button('Export', function() { me.cur_rb && me.cur_rb.dt.do_export(); },
  47. 'icon-download-alt');
  48. this.appframe.add_button('Print', function() { me.cur_rb && me.cur_rb.dt.do_print(); },
  49. 'icon-print');
  50. this.appframe.add_button('Calc', function() { me.cur_rb && me.cur_rb.dt.do_calc(); },
  51. 'icon-plus');
  52. // new
  53. if(has_common(['Administrator', 'System Manager'], user_roles)) {
  54. // save
  55. var savebtn = this.appframe.add_button('Save',
  56. function() {if(me.cur_rb) me.cur_rb.save_criteria(); });
  57. // advanced
  58. var fn = function() {
  59. if(me.cur_rb) {
  60. if(!me.cur_rb.current_loaded) {
  61. msgprint("error:You must save the report before you can set Advanced features");
  62. return;
  63. }
  64. loaddoc('Search Criteria', me.cur_rb.sc_dict[me.cur_rb.current_loaded]);
  65. }
  66. };
  67. var advancedbtn = this.appframe.add_button('Advanced Settings', fn, 'icon-cog');
  68. }
  69. // set a type
  70. this.set_dt = function(dt, onload) {
  71. my_onload = function(f) {
  72. if(!f.forbidden) {
  73. me.cur_rb = f;
  74. me.cur_rb.mytabs.items['Result'].expand();
  75. if(onload)onload(f);
  76. }
  77. }
  78. if(me.cur_rb)
  79. me.cur_rb.hide();
  80. if(me.rb_dict[dt]){
  81. me.rb_dict[dt].show(my_onload);
  82. } else {
  83. me.rb_dict[dt] = new _r.ReportBuilder(me.rb_area, dt, my_onload);
  84. }
  85. }
  86. }
  87. // ===================================================================================
  88. // + ReportBuilder
  89. // + Datatable (grid)
  90. // + ColumnPicker
  91. // + ReportFilter
  92. //
  93. // - Contains all methods relating to saving, loading and executing Search Criteria
  94. // - Contains ui objects of the report including tabs
  95. _r.ReportBuilder = function(parent, doctype, onload) {
  96. this.menuitems = {};
  97. this.has_primary_filters = false;
  98. this.doctype = doctype;
  99. this.forbidden = 0;
  100. this.filter_fields = [];
  101. this.filter_fields_dict = {};
  102. var me = this;
  103. this.fn_list = ['beforetableprint','beforerowprint','afterrowprint','aftertableprint','customize_filters','get_query'];
  104. this.wrapper = $a(parent, 'div', 'finder_wrapper');
  105. this.make_tabs();
  106. this.current_loaded = null;
  107. this.setup_doctype(onload);
  108. this.hide = function() {
  109. $dh(me.wrapper);
  110. }
  111. this.show = function(my_onload) {
  112. $ds(me.wrapper);
  113. // reset main title
  114. this.set_main_title('Report: ' + get_doctype_label(me.doctype));
  115. if(my_onload)my_onload(me);
  116. }
  117. }
  118. // -------------------------------------------------------------------------------------
  119. _r.ReportBuilder.prototype.make_tabs = function() {
  120. this.tab_wrapper = $a(this.wrapper, 'div', 'finder_tab_area');
  121. this.mytabs = new TabbedPage(this.tab_wrapper);
  122. this.mytabs.add_item('Result', null, null, 1);
  123. this.mytabs.add_item('More Filters', null, null, 1);
  124. this.mytabs.add_item('Select Columns', null, null, 1);
  125. this.mytabs.tabs = this.mytabs.items;
  126. }
  127. // -------------------------------------------------------------------------------------
  128. _r.ReportBuilder.prototype.make_body = function() {
  129. this.set_main_title('Report: ' + get_doctype_label(this.doctype));
  130. var me = this;
  131. this.make_save_criteria();
  132. this.column_picker = new _r.ReportColumnPicker(this);
  133. this.report_filters = new _r.ReportFilters(this);
  134. }
  135. //
  136. // Make list of all Criterias relating to this DocType
  137. // -------------------------------------------------------------------------------------
  138. // Search Criterias are loaded with the DocType - put them in a list and dict
  139. _r.ReportBuilder.prototype.make_save_criteria = function() {
  140. var me = this;
  141. // make_list
  142. // ---------
  143. this.sc_list = []; this.sc_dict = {};
  144. for(var n in locals['Search Criteria']) {
  145. var d = locals['Search Criteria'][n];
  146. if(d.doc_type==this.doctype) {
  147. this.sc_list[this.sc_list.length] = d.criteria_name;
  148. this.sc_dict[d.criteria_name] = n;
  149. }
  150. }
  151. }
  152. // Save Criteria
  153. // -------------------------------------------------------------------------------------
  154. _r.ReportBuilder.prototype.save_criteria = function(save_as) {
  155. var overwrite = 0;
  156. // is loaded?
  157. if(this.current_loaded && (!save_as)) {
  158. var overwrite = confirm('Do you want to overwrite the saved criteria "'+this.current_loaded+'"');
  159. if(overwrite) {
  160. var doc = locals['Search Criteria'][this.sc_dict[this.current_loaded]];
  161. var criteria_name = this.current_loaded;
  162. }
  163. }
  164. // new criteria
  165. if(!overwrite) {
  166. var criteria_name = prompt('Select a name for the criteria:', '');
  167. if(!criteria_name)
  168. return;
  169. var dn = createLocal('Search Criteria');
  170. var doc = locals['Search Criteria'][dn];
  171. doc.criteria_name = criteria_name;
  172. doc.doc_type = this.doctype;
  173. }
  174. var cl = []; var fl = {};
  175. // save columns
  176. var t = this.column_picker.get_selected();
  177. for(var i=0;i<t.length;i++)
  178. cl.push(t[i].parent + '\1' + t[i].label);
  179. // save filters
  180. for(var i=0;i<this.filter_fields.length;i++) {
  181. var t = this.filter_fields[i];
  182. var v = t.get_value?t.get_value():'';
  183. if(v) fl[t.df.parent + '\1' + t.df.label + (t.bound?('\1'+t.bound):'')] = v;
  184. }
  185. doc.columns = cl.join(',');
  186. doc.filters = JSON.stringify(fl);
  187. // sort by and sort order
  188. doc.sort_by = sel_val(this.dt.sort_sel);
  189. doc.sort_order = this.dt.sort_order;
  190. doc.page_len = this.dt.page_len;
  191. // save advanced
  192. if(this.parent_dt)
  193. doc.parent_doc_type = this.parent_dt
  194. // rename
  195. var me = this;
  196. var fn = function(r) {
  197. me.sc_dict[criteria_name] = r.main_doc_name;
  198. me.set_criteria_sel(criteria_name);
  199. }
  200. //if(this.current_loaded && overwrite) {
  201. // msgprint('Filters and Columns Synchronized. You must also "Save" the Search Criteria to update');
  202. // loaddoc('Search Criteria', this.sc_dict[this.current_loaded]);
  203. //} else {
  204. save_doclist(doc.doctype, doc.name, 'Save', fn); // server-side save
  205. //}
  206. }
  207. // -------------------------------------------------------------------------------------
  208. _r.ReportBuilder.prototype.hide_all_filters = function() {
  209. for(var i=0; i<this.filter_fields.length; i++) {
  210. this.filter_fields[i].df.filter_hide = 1;
  211. }
  212. }
  213. // -------------------------------------------------------------------------------------
  214. _r.ReportBuilder.prototype.run = function() {
  215. // Note: all client code is executed in datatable
  216. this.dt.run();
  217. }
  218. // Load Criteria
  219. // -------------------------------------------------------------------------------------
  220. _r.ReportBuilder.prototype.clear_criteria = function() {
  221. this.column_picker.clear();
  222. this.column_picker.set_defaults();
  223. // clear filters
  224. // -------------
  225. for(var i=0; i<this.filter_fields.length; i++) {
  226. // reset filters
  227. this.filter_fields[i].df.filter_hide = 0;
  228. this.filter_fields[i].df.ignore = 0;
  229. if(this.filter_fields[i].is_custom) {
  230. // hide+ignore customized filters
  231. this.filter_fields[i].df.filter_hide = 1;
  232. this.filter_fields[i].df.ignore = 1;
  233. }
  234. this.filter_fields[i].set_input(null);
  235. }
  236. this.set_sort_options();
  237. this.set_main_title('Report: ' + get_doctype_label(this.doctype));
  238. this.current_loaded = null;
  239. this.customized_filters = null;
  240. this.sc = null;
  241. this.has_index = 1; this.has_headings = 1;
  242. for(var i in this.fn_list) this[this.fn_list[i]] = null; // clear custom functions
  243. }
  244. // -------------------------------------------------------------------------------------
  245. _r.ReportBuilder.prototype.set_main_title = function(t, t1) {
  246. var title = t + (t1 ? t1 : '');
  247. _r.rb_con.appframe.$titlebar.find('.report-title').html(title);
  248. set_title(title);
  249. }
  250. _r.ReportBuilder.prototype.select_column = function(dt, label, value) {
  251. if(value==null)value = 1;
  252. this.column_picker.set(dt, label, value);
  253. }
  254. // -------------------------------------------------------------------------------------
  255. _r.ReportBuilder.prototype.set_filter = function(dt, label, value) {
  256. if(this.filter_fields_dict[dt+'\1'+ label])
  257. this.filter_fields_dict[dt+'\1'+ label].set_input(value);
  258. }
  259. // -------------------------------------------------------------------------------------
  260. _r.ReportBuilder.prototype.load_criteria = function(criteria_name) {
  261. this.clear_criteria();
  262. if(!this.sc_dict[criteria_name]) {
  263. alert(criteria_name + ' could not be loaded. Please Refresh and try again');
  264. }
  265. this.sc = locals['Search Criteria'][this.sc_dict[criteria_name]];
  266. // eval the custom script
  267. var report = this;
  268. if(this.sc && this.sc.report_script) eval(this.sc.report_script);
  269. this.large_report = 0;
  270. // execute the customize_filters method from Search Criteria
  271. if(report.customize_filters) {
  272. try {
  273. report.customize_filters(this);
  274. } catch(err) {
  275. errprint('Error in "customize_filters":\n' + err);
  276. }
  277. }
  278. // refresh fiters
  279. this.report_filters.refresh();
  280. // set fields
  281. // ----------
  282. this.column_picker.clear();
  283. var cl = this.sc.columns ? this.sc.columns.split(',') : [];
  284. for(var c=0;c<cl.length;c++) {
  285. var key = cl[c].split('\1');
  286. this.select_column(key[0], key[1], 1);
  287. }
  288. // set filters
  289. // -----------
  290. try {
  291. var fl = JSON.parse(this.sc.filters);
  292. } catch(e) {
  293. eval('var fl = ' + this.sc.filters);
  294. }
  295. for(var n in fl) {
  296. if(fl[n]) {
  297. var key = n.split('\1');
  298. if(key[1]=='docstatus') { /* ? */ }
  299. this.set_filter(key[0], key[1], fl[n]);
  300. }
  301. }
  302. // refresh column picker
  303. this.set_criteria_sel(criteria_name);
  304. }
  305. // -------------------------------------------------------------------------------------
  306. // this method must be called after resetting the Search Criteria (or clearing)
  307. // to set Data table properties
  308. _r.ReportBuilder.prototype.set_criteria_sel = function(criteria_name) {
  309. // add additional columns
  310. var sc = locals['Search Criteria'][this.sc_dict[criteria_name]];
  311. if(sc && sc.add_col)
  312. var acl = sc.add_col.split('\n');
  313. else
  314. var acl = [];
  315. var new_sl = [];
  316. // update the label in datatable where the column name is specified in the query using AS
  317. for(var i=0; i<acl.length; i++) {
  318. var tmp = acl[i].split(' AS ');
  319. if(tmp[1]) {
  320. var t = eval(tmp[1]);
  321. new_sl[new_sl.length] = [t, "`"+t+"`"];
  322. }
  323. }
  324. // set sort
  325. this.set_sort_options(new_sl);
  326. if(sc && sc.sort_by) {
  327. this.dt.sort_sel.value = sc.sort_by;
  328. }
  329. if(sc && sc.sort_order) {
  330. sc.sort_order=='ASC' ? this.dt.set_asc() : this.dt.set_desc();
  331. }
  332. if(sc && sc.page_len) {
  333. this.dt.page_len_sel.inp.value = sc.page_len;
  334. }
  335. this.current_loaded = criteria_name;
  336. // load additional fields sort option
  337. this.set_main_title(criteria_name, sc.description);
  338. }
  339. //
  340. // Create the filter UI and column selection UI
  341. // -------------------------------------------------------------------------------------
  342. _r.ReportBuilder.prototype.setup_filters_and_cols = function() {
  343. // function checks where there is submit permission on the DocType or if the DocType
  344. // can be trashed
  345. function can_dt_be_submitted(dt) {
  346. if(locals.DocType && locals.DocType[dt] && locals.DocType[dt].allow_trash) return 1;
  347. var plist = getchildren('DocPerm', dt, 'permissions', 'DocType');
  348. for(var pidx in plist) {
  349. if(plist[pidx].submit) return 1;
  350. }
  351. return 0;
  352. }
  353. var me = this;
  354. var dt = me.parent_dt?me.parent_dt:me.doctype;
  355. // default filters
  356. var fl = [
  357. {'fieldtype':'Data', 'label':'ID', 'fieldname':'name', 'in_filter':1, 'parent':dt},
  358. {'fieldtype':'Data', 'label':'Owner', 'fieldname':'owner', 'in_filter':1, 'parent':dt},
  359. {'fieldtype':'Date', 'label':'Created on', 'fieldname':'creation', 'in_filter':0, 'parent':dt},
  360. {'fieldtype':'Date', 'label':'Last modified on', 'fieldname':'modified', 'in_filter':0, 'parent':dt},
  361. ];
  362. // can this be submitted?
  363. if(can_dt_be_submitted(dt)) {
  364. fl[fl.length] = {'fieldtype':'Check', 'label':'Saved', 'fieldname':'docstatus', 'search_index':1, 'in_filter':1, 'def_filter':1, 'parent':dt};
  365. fl[fl.length] = {'fieldtype':'Check', 'label':'Submitted', 'fieldname':'docstatus', 'search_index':1, 'in_filter':1, 'def_filter':1, 'parent':dt};
  366. fl[fl.length] = {'fieldtype':'Check', 'label':'Cancelled', 'fieldname':'docstatus', 'search_index':1, 'in_filter':1, 'parent':dt};
  367. }
  368. // make the datatable
  369. me.make_datatable();
  370. // Add columns and filters of parent doctype
  371. me.orig_sort_list = [];
  372. if(me.parent_dt) {
  373. me.setup_dt_filters_and_cols(fl, me.parent_dt);
  374. var fl = [];
  375. }
  376. // Add columns and filters of selected doctype
  377. me.setup_dt_filters_and_cols(fl, me.doctype);
  378. // hide primary filters blue box if there are no primary filters
  379. if(!this.has_primary_filters)
  380. $dh(this.report_filters.first_page_filter);
  381. this.column_picker.refresh();
  382. // show body
  383. $ds(me.body);
  384. }
  385. // -------------------------------------------------------------------------------------
  386. _r.ReportBuilder.prototype.add_filter = function(f) {
  387. if(this.filter_fields_dict[f.parent + '\1' + f.label]) {
  388. // exists
  389. this.filter_fields_dict[f.parent + '\1' + f.label].df = f; // reset properties
  390. } else {
  391. this.report_filters.add_field(f, f.parent, null, 1);
  392. }
  393. }
  394. // -------------------------------------------------------------------------------------
  395. // this is where the filters and columns are created for a particular doctype
  396. _r.ReportBuilder.prototype.setup_dt_filters_and_cols = function(fl, dt) {
  397. var me = this;
  398. // set section headings
  399. var lab = $a(me.filter_area,'div','filter_dt_head');
  400. lab.innerHTML = 'Filters for ' + get_doctype_label(dt);
  401. var lab = $a(me.picker_area,'div','builder_dt_head');
  402. lab.innerHTML = 'Select columns for ' + get_doctype_label(dt);
  403. // get fields
  404. var dt_fields = fields_list[dt];
  405. for(var i=0;i<dt_fields.length;i++) {
  406. fl[fl.length] = dt_fields[i];
  407. }
  408. // get "high priority" search fields
  409. // if the field is in search_field then it should be primary filter (i.e. on first page)
  410. var sf_list = locals.DocType[dt].search_fields ? locals.DocType[dt].search_fields.split(',') : [];
  411. for(var i in sf_list) sf_list[i] = strip(sf_list[i]);
  412. // make fields
  413. for(var i=0;i<fl.length;i++) {
  414. var f=fl[i];
  415. // add to filter
  416. if(f && cint(f.in_filter)) {
  417. me.report_filters.add_field(f, dt, in_list(sf_list, f.fieldname));
  418. }
  419. // add to column selector (builder)
  420. if(f && !in_list(no_value_fields, f.fieldtype) && f.fieldname != 'docstatus' && (!f.report_hide)) {
  421. me.column_picker.add_field(f);
  422. }
  423. }
  424. me.set_sort_options();
  425. }
  426. // -------------------------------------------------------------------------------------
  427. _r.ReportBuilder.prototype.set_sort_options = function(l) {
  428. var sl = this.orig_sort_list;
  429. empty_select(this.dt.sort_sel);
  430. if(l) sl = add_lists(l, this.orig_sort_list);
  431. // make new list if reqd
  432. if(!l) l = [];
  433. // no sorts, add one
  434. if(!l.length) {
  435. l.push(['ID', 'name'])
  436. }
  437. for(var i=0; i<sl.length; i++) {
  438. this.dt.add_sort_option(sl[i][0], sl[i][1]);
  439. }
  440. }
  441. // -------------------------------------------------------------------------------------
  442. _r.ReportBuilder.prototype.validate_permissions = function(onload) {
  443. this.perm = get_perm(this.parent_dt ? this.parent_dt : this.doctype);
  444. if(!this.perm[0][READ]) {
  445. this.forbidden = 1;
  446. if(user=='Guest') {
  447. msgprint('You must log in to view this page');
  448. } else {
  449. msgprint('No Read Permission');
  450. }
  451. window.back();
  452. return 0;
  453. }
  454. return 1;
  455. }
  456. // -------------------------------------------------------------------------------------
  457. _r.ReportBuilder.prototype.setup_doctype = function(onload) {
  458. // load doctype
  459. var me = this;
  460. if(!locals['DocType'][this.doctype]) {
  461. this.load_doctype_from_server(onload);
  462. } else {
  463. // find parent dt if required
  464. for(var key in locals.DocField) {
  465. var f = locals.DocField[key];
  466. if(f.fieldtype=='Table' && f.options==this.doctype)
  467. this.parent_dt = f.parent;
  468. }
  469. if(!me.validate_permissions())
  470. return;
  471. me.validate_permissions();
  472. me.make_body();
  473. me.setup_filters_and_cols();
  474. if(onload)onload(me);
  475. }
  476. }
  477. _r.ReportBuilder.prototype.load_doctype_from_server = function(onload) {
  478. var me = this;
  479. $c('webnotes.widgets.form.load.getdoctype', args = {'doctype': this.doctype, 'with_parent':1 },
  480. function(r,rt) {
  481. if(r.parent_dt)me.parent_dt = r.parent_dt;
  482. if(!me.validate_permissions())
  483. return;
  484. me.make_body();
  485. me.setup_filters_and_cols();
  486. if(onload)onload(me);
  487. }
  488. );
  489. }
  490. // -------------------------------------------------------------------------------------
  491. _r.ReportBuilder.prototype.reset_report = function() {
  492. this.clear_criteria();
  493. // show column picker if find
  494. this.mytabs.items['Select Columns'].show();
  495. this.mytabs.items['More Filters'].show();
  496. this.report_filters.refresh();
  497. this.column_picker.refresh();
  498. var dt = this.parent_dt?this.parent_dt: this.doctype;
  499. this.set_filter(dt, 'Saved', 1);
  500. this.set_filter(dt, 'Submitted', 1);
  501. this.set_filter(dt, 'Cancelled', 0);
  502. this.column_picker.set_defaults();
  503. this.dt.clear_all();
  504. this.dt.sort_sel.value = 'ID';
  505. this.dt.page_len_sel.inp.value = '50';
  506. this.dt.set_no_limit(0);
  507. this.dt.set_desc();
  508. }
  509. //
  510. // Make the SQL query
  511. // -------------------------------------------------------------------------------------
  512. _r.ReportBuilder.prototype.make_datatable = function() {
  513. var me = this;
  514. this.dt_area = $a(this.mytabs.items['Result'].body, 'div');
  515. var clear_area = $a(this.mytabs.items['Result'].body, 'div');
  516. clear_area.style.marginTop = '8px';
  517. clear_area.style.textAlign = 'right';
  518. this.clear_btn = $a($a(clear_area, 'span'), 'button');
  519. this.clear_btn.innerHTML = 'Clear Settings';
  520. this.clear_btn.onclick = function() {
  521. me.reset_report();
  522. }
  523. var d = $a(clear_area, 'span', '', {marginLeft:'16px'});
  524. d.innerHTML = '<span>Show Query: </span>';
  525. this.show_query = $a_input(d, 'checkbox');
  526. this.show_query.checked = false;
  527. this.dt = new _r.DataTable(this.dt_area, '');
  528. this.dt.finder = this;
  529. this.dt.make_query = function() {
  530. // attach report script functions
  531. var report = me;
  532. // get search criteria
  533. if(me.current_loaded && me.sc_dict[me.current_loaded]) {
  534. var sc = get_local('Search Criteria', me.sc_dict[me.current_loaded]);
  535. }
  536. if(sc) me.dt.search_criteria = sc;
  537. else me.dt.search_criteria = null;
  538. // reset no_limit
  539. //me.dt.set_no_limit(0);
  540. //load server script
  541. if(sc && sc.server_script) me.dt.server_script = sc.server_script;
  542. else me.dt.server_script = null;
  543. // load client scripts - attach all functions from ReportBuilder to Datatable
  544. // this is a bad way of doing things but since DataTable is a stable object
  545. // currently this is okay.... to change in future
  546. for(var i=0;i<me.fn_list.length;i++) {
  547. if(me[me.fn_list[i]]) me.dt[me.fn_list[i]] = me[me.fn_list[i]];
  548. else me.dt[me.fn_list[i]] = null;
  549. }
  550. var fl = []; // field list
  551. var docstatus_cl = [];
  552. var cl = []; // cond list
  553. // format table name
  554. var table_name = function(t) { return '`tab' + t + '`'; }
  555. // advanced - make list of diabled filters
  556. var dis_filters_list = [];
  557. if(sc && sc.dis_filters)
  558. var dis_filters_list = sc.dis_filters.split('\n');
  559. // make a list of selected columns from ColumnPicker in tableName.fieldName format
  560. var t = me.column_picker.get_selected();
  561. for(var i=0;i<t.length;i++) {
  562. fl.push(table_name(t[i].parent) + '.`'+t[i].fieldname+'` AS `'+t[i].parent +'.'+ t[i].fieldname+'`');
  563. }
  564. me.selected_fields = fl;
  565. // advanced - additional fields
  566. if(sc && sc.add_col) {
  567. var adv_fl = sc.add_col.split('\n');
  568. for(var i=0;i<adv_fl.length;i++) {
  569. fl[fl.length] = adv_fl[i];
  570. }
  571. }
  572. // build dictionary for filter values for server side
  573. me.dt.filter_vals = {}
  574. add_to_filter = function(k,v,is_select) {
  575. if(v==null)v='';
  576. if(!in_list(keys(me.dt.filter_vals), k)) {
  577. me.dt.filter_vals[k] = v;
  578. } else {
  579. if(is_select)
  580. me.dt.filter_vals[k] += '\n' + v;
  581. else
  582. me.dt.filter_vals[k+'1'] = v; // for date, numeric (from-to)
  583. }
  584. }
  585. // loop over the fields and construct the SQL query
  586. // ------------------------------------------------
  587. for(var i=0;i<me.filter_fields.length;i++) {
  588. var t = me.filter_fields[i];
  589. // add to "filter_values"
  590. var v = t.get_value?t.get_value():'';
  591. if(t.df.fieldtype=='Select') {
  592. if(t.input.multiple) {
  593. for(var sel_i=0;sel_i < v.length; sel_i++) {
  594. add_to_filter(t.df.fieldname, v[sel_i], 1);
  595. }
  596. // no values? atleast add key
  597. if(!v.length) add_to_filter(t.df.fieldname, "", 1);
  598. } else {
  599. add_to_filter(t.df.fieldname, v);
  600. }
  601. } else add_to_filter(t.df.fieldname, v);
  602. // if filter is not disabled
  603. if(!in_list(dis_filters_list, t.df.fieldname) && !t.df.ignore) {
  604. if(t.df.fieldname=='docstatus') {
  605. // work around for docstatus
  606. // -------------------------
  607. if(t.df.label=='Saved'){
  608. if(t.get_value()) docstatus_cl[docstatus_cl.length] = table_name(t.df.parent)+'.docstatus=0';
  609. else cl[cl.length] = table_name(t.df.parent)+'.docstatus!=0';
  610. }
  611. else if(t.df.label=='Submitted'){
  612. if(t.get_value()) docstatus_cl[docstatus_cl.length] = table_name(t.df.parent)+'.docstatus=1';
  613. else cl[cl.length] = table_name(t.df.parent)+'.docstatus!=1';
  614. }
  615. else if(t.df.label=='Cancelled'){
  616. if(t.get_value()) docstatus_cl[docstatus_cl.length] = table_name(t.df.parent)+'.docstatus=2';
  617. else cl[cl.length] = table_name(t.df.parent)+'.docstatus!=2';
  618. }
  619. } else {
  620. // normal
  621. // -------
  622. var fn = '`' + t.df.fieldname + '`';
  623. var v = t.get_value?t.get_value():'';
  624. if(v) {
  625. if(in_list(['Data','Link','Small Text','Text'],t.df.fieldtype)) {
  626. cl[cl.length] = table_name(t.df.parent) + '.' + fn + ' LIKE "' + v + '%"';
  627. } else if(t.df.fieldtype=='Select') {
  628. if(t.input.multiple) {
  629. // loop for multiple select
  630. var tmp_cl = [];
  631. for(var sel_i=0;sel_i < v.length; sel_i++) {
  632. if(v[sel_i]) {
  633. tmp_cl[tmp_cl.length] = table_name(t.df.parent) + '.' + fn + ' = "' + v[sel_i] + '"';
  634. }
  635. }
  636. // join multiple select conditions by OR
  637. if(tmp_cl.length)cl[cl.length] = '(' + tmp_cl.join(' OR ') + ')';
  638. } else {
  639. cl[cl.length] = table_name(t.df.parent) + '.' + fn + ' = "' + v + '"';
  640. }
  641. } else {
  642. var condition = '=';
  643. if(t.sql_condition) condition = t.sql_condition;
  644. cl[cl.length] = table_name(t.df.parent) + '.' + fn + condition + '"' + v + '"';
  645. }
  646. }
  647. }
  648. }
  649. }
  650. // standard filters
  651. me.dt.filter_vals.user = user;
  652. me.dt.filter_vals.user_email = user_email;
  653. me.filter_vals = me.dt.filter_vals; // in both dt and report
  654. // overloaded query - finish it here
  655. this.is_simple = 0;
  656. if(sc && sc.custom_query) {
  657. this.query = repl(sc.custom_query, me.dt.filter_vals);
  658. this.is_simple = 1;
  659. return
  660. }
  661. if(me.get_query) {
  662. // custom query method
  663. this.query = me.get_query();
  664. this.is_simple = 1;
  665. } else {
  666. // add docstatus conditions
  667. if(docstatus_cl.length)
  668. cl[cl.length] = '('+docstatus_cl.join(' OR ')+')';
  669. // advanced - additional conditions
  670. if(sc && sc.add_cond) {
  671. var adv_cl = sc.add_cond.split('\n');
  672. for(var i=0;i< adv_cl.length;i++) {
  673. cl[cl.length] = adv_cl[i];
  674. }
  675. }
  676. // atleast one field
  677. if(!fl.length) {
  678. alert('You must select atleast one column to view');
  679. this.query = '';
  680. return;
  681. }
  682. // join with parent in case of child
  683. var tn = table_name(me.doctype);
  684. if(me.parent_dt) {
  685. tn = tn + ',' + table_name(me.parent_dt);
  686. cl[cl.length] = table_name(me.doctype) + '.`parent` = ' + table_name(me.parent_dt) + '.`name`';
  687. }
  688. // advanced - additional tables
  689. if(sc && sc.add_tab) {
  690. var adv_tl = sc.add_tab.split('\n');
  691. tn = tn + ',' + adv_tl.join(',');
  692. }
  693. // make the query
  694. if(!cl.length)
  695. this.query = 'SELECT ' + fl.join(',\n') + ' FROM ' + tn
  696. else
  697. this.query = 'SELECT ' + fl.join(',') + ' FROM ' + tn + ' WHERE ' + cl.join('\n AND ');
  698. // advanced - group by
  699. if(sc && sc.group_by) {
  700. this.query += ' GROUP BY ' + sc.group_by;
  701. }
  702. // replace - in custom query if %(key)s is specified, then replace it by filter values
  703. this.query = repl(this.query, me.dt.filter_vals)
  704. }
  705. if(me.show_query.checked) {
  706. this.show_query = 1;
  707. }
  708. // report name - used as filename in export
  709. if(me.current_loaded) this.rep_name = me.current_loaded;
  710. else this.rep_name = me.doctype;
  711. }
  712. }
  713. // -------------------------------------------------------------------------------------
  714. _r.ReportBuilder.prototype.get_filter = function(dt, label) {
  715. return this.filter_fields_dict[dt + FILTER_SEP + label];
  716. }
  717. _r.ReportBuilder.prototype.set_filter_properties = function(dt, label, properties) {
  718. var f = this.filter_fields_dict[dt + FILTER_SEP + label];
  719. for(key in properties) {
  720. f.df[key]=properties[key];
  721. }
  722. }
  723. // Report Filter
  724. // ===================================================================================
  725. _r.ReportFilters = function(rb) {
  726. this.rb = rb;
  727. // filters broken into - primary - in searchfields and others
  728. this.first_page_filter = $a(rb.mytabs.items['Result'].body, 'div', 'finder_filter_area');
  729. this.filter_area = $a(rb.mytabs.items['More Filters'].body, 'div', 'finder_filter_area');
  730. // filter fields area
  731. this.filter_fields_area = $a(this.filter_area,'div');
  732. }
  733. // -------------------------------------------------------------------------------------
  734. _r.ReportFilters.prototype.refresh = function() {
  735. // called after customization
  736. var fl = this.rb.filter_fields
  737. for(var i=0; i<fl.length; i++) {
  738. var f = fl[i];
  739. // is hidden ?
  740. if(f.df.filter_hide) {
  741. $dh(f.wrapper);
  742. } else {
  743. $ds(f.wrapper);
  744. }
  745. // is bold?
  746. if(f.df.bold) {
  747. if(f.label_cell)
  748. $y(f.label_cell, {fontWeight:'bold'})
  749. } else {
  750. if(f.label_cell) $y(f.label_cell, {fontWeight:'normal'})
  751. }
  752. // set default value
  753. if(f.df['report_default'])
  754. f.set_input(f.df['report_default']);
  755. // show in first page?
  756. if(f.df.in_first_page && f.df.filter_cell) {
  757. f.df.filter_cell.parentNode.removeChild(f.df.filter_cell);
  758. this.first_page_filter.appendChild(f.df.filter_cell);
  759. this.rb.has_primary_filters = 1;
  760. $ds(this.first_page_filter);
  761. }
  762. // clear / hide all custom added filters
  763. }
  764. }
  765. // -------------------------------------------------------------------------------------
  766. _r.ReportFilters.prototype.add_date_field = function(cell, f, dt, is_custom) {
  767. var my_div = $a(cell,'div','',{});
  768. // from date
  769. var f1 = copy_dict(f);
  770. f1.label = 'From ' + f1.label;
  771. var tmp1 = this.make_field_obj(f1, dt, my_div, is_custom);
  772. tmp1.sql_condition = '>=';
  773. tmp1.bound = 'lower';
  774. // to date
  775. var f2 = copy_dict(f);
  776. f2.label = 'To ' + f2.label;
  777. var tmp2 = this.make_field_obj(f2, dt, my_div, is_custom);
  778. tmp2.sql_condition = '<=';
  779. tmp2.bound = 'upper';
  780. }
  781. // -------------------------------------------------------------------------------------
  782. _r.ReportFilters.prototype.add_numeric_field = function(cell, f, dt, is_custom) {
  783. var my_div = $a(cell,'div','',{});
  784. // from value
  785. var f1 = copy_dict(f);
  786. f1.label = f1.label + ' >=';
  787. var tmp1 = this.make_field_obj(f1, dt, my_div, is_custom);
  788. tmp1.sql_condition = '>=';
  789. tmp1.bound = 'lower';
  790. // to value
  791. var f2 = copy_dict(f);
  792. f2.label = f2.label + ' <=';
  793. var tmp2 = this.make_field_obj(f2, dt, my_div, is_custom);
  794. tmp2.sql_condition = '<=';
  795. tmp2.bound = 'upper';
  796. }
  797. // make a field object
  798. _r.ReportFilters.prototype.make_field_obj = function(f, dt, parent, is_custom) {
  799. var tmp = make_field(f, dt, parent, this.rb, false);
  800. tmp.not_in_form = 1;
  801. tmp.in_filter = 1;
  802. tmp.refresh();
  803. this.rb.filter_fields[this.rb.filter_fields.length] = tmp;
  804. this.rb.filter_fields_dict[f.parent + '\1' + f.label] = tmp;
  805. if(is_custom) tmp.is_custom = 1;
  806. return tmp;
  807. }
  808. // -------------------------------------------------------------------------------------
  809. _r.ReportFilters.prototype.add_field = function(f, dt, in_primary, is_custom) {
  810. var me = this;
  811. // insert in (parent element)
  812. if(f.in_first_page) in_primary = true;
  813. var fparent = this.filter_fields_area;
  814. if(in_primary) {
  815. fparent = this.first_page_filter;
  816. this.rb.has_primary_filters = 1;
  817. }
  818. // label
  819. // --- ability to insert
  820. if(f.on_top) {
  821. var cell = document.createElement('div');
  822. fparent.insertBefore(cell, fparent.firstChild);
  823. $y(cell,{width:'70%'});
  824. } else if(f.insert_before) {
  825. var cell = document.createElement('div');
  826. fparent.insertBefore(cell, fparent[f.df.insert_before].filter_cell);
  827. $y(cell,{width:'70%'});
  828. }
  829. else
  830. var cell = $a(fparent, 'div', '', {width:'70%'});
  831. f.filter_cell = cell;
  832. // make field
  833. if(f.fieldtype=='Date') {
  834. // date field
  835. this.add_date_field(cell, f, dt);
  836. } else if(in_list(['Currency', 'Int', 'Float'], f.fieldtype)) {
  837. // numeric
  838. this.add_numeric_field(cell, f, dt);
  839. } else if (!in_list(['Section Break', 'Column Break', 'Read Only', 'HTML', 'Table', 'Image', 'Button'], f.fieldtype)) {
  840. var tmp = this.make_field_obj(f, dt, cell, is_custom);
  841. }
  842. // add to datatable sort
  843. if(f.fieldname != 'docstatus')
  844. me.rb.orig_sort_list.push([f.label, '`tab' + f.parent + '`.`' + f.fieldname + '`']);
  845. // check saved
  846. if(f.def_filter)
  847. tmp.input.checked = true;
  848. }
  849. // Column Picker
  850. // ===================================================================================
  851. _r.ReportColumnPicker = function(rb) {
  852. this.rb = rb;
  853. this.picker_area = $a(this.rb.mytabs.items['Select Columns'].body, 'div', 'finder_picker_area');
  854. this.all_fields = [];
  855. this.sel_idx = 0;
  856. this.make_body();
  857. }
  858. // -------------------------------------------------------------------------------------
  859. _r.ReportColumnPicker.prototype.make_body = function() {
  860. var t = make_table(this.picker_area, 1, 3, '100%', ['35%','30%','35%'], {verticalAlign:'middle', textAlign:'center'});
  861. // all fields
  862. $a($td(t,0,0), 'h3', '', {marginBottom:'8px'}).innerHTML = 'Columns';
  863. this.unsel_fields = $a($td(t,0,0), 'select', '', {height:'200px', width:'100%', border:'1px solid #AAA'});
  864. this.unsel_fields.multiple = true;
  865. this.unsel_fields.onchange = function() { for(var i=0; i<this.options.length; i++) this.options[i].field.is_selected = this.options[i].selected; }
  866. // buttons
  867. var me = this;
  868. this.up_btn = $a($a($td(t,0,1), 'div'), 'button', '', {width:'70px'});
  869. this.up_btn.innerHTML = 'Up &uarr;';
  870. this.up_btn.onclick = function() { me.move_up(); }
  871. this.add_all = $a($a($td(t,0,1), 'div'), 'button', '', {width:'40px'});
  872. this.add_all.innerHTML = '&gt;&gt;';
  873. this.add_all.onclick = function() { me.move(me.unsel_fields, 'add', 1); }
  874. this.add_btn = $a($a($td(t,0,1), 'div'), 'button', '', {width:'110px'});
  875. this.add_btn.innerHTML = '<b>Add &gt;</b>';
  876. this.add_btn.onclick = function() { me.move(me.unsel_fields, 'add'); }
  877. this.remove_btn = $a($a($td(t,0,1), 'div'), 'button', '', {width:'110px'});
  878. this.remove_btn.innerHTML = '<b>&lt; Remove</b>';
  879. this.remove_btn.onclick = function() { me.move(me.sel_fields, 'remove'); }
  880. this.remove_all = $a($a($td(t,0,1), 'div'), 'button', '', {width:'40px'});
  881. this.remove_all.innerHTML = '&lt;&lt;';
  882. this.remove_all.onclick = function() { me.move(me.sel_fields, 'remove', 1); }
  883. this.dn_btn = $a($a($td(t,0,1), 'div'), 'button', '', {width:'70px'});
  884. this.dn_btn.innerHTML = 'Down &darr;';
  885. this.dn_btn.onclick = function() { me.move_down(); }
  886. // multiple fields
  887. $a($td(t,0,2), 'h3', '', {marginBottom:'8px'}).innerHTML = 'Selected Columns';
  888. this.sel_fields = $a($td(t,0,2), 'select', '', {height:'200px', width:'100%', border:'1px solid #AAA'});
  889. this.sel_fields.multiple = true;
  890. this.sel_fields.onchange = function() { for(var i=0; i<this.options.length; i++) this.options[i].field.is_selected = this.options[i].selected; }
  891. }
  892. // -------------------------------------------------------------------------------------
  893. _r.ReportColumnPicker.prototype.get_by_sel_idx = function(s, idx) {
  894. for(var j=0;j<s.options.length; j++) {
  895. if(s.options[j].field.sel_idx == idx)
  896. return s.options[j].field;
  897. }
  898. return {} // nothing
  899. }
  900. _r.ReportColumnPicker.prototype.move_up = function() {
  901. var s = this.sel_fields;
  902. for(var i=1;i<s.options.length; i++ ) {
  903. if(s.options[i].selected) {
  904. s.options[i].field.sel_idx--;
  905. this.get_by_sel_idx(s, i-1).sel_idx++;
  906. }
  907. }
  908. this.refresh();
  909. }
  910. _r.ReportColumnPicker.prototype.move_down = function() {
  911. var s = this.sel_fields;
  912. if(s.options.length<=1) return;
  913. for(var i=s.options.length-2;i>=0; i-- ) {
  914. if(s.options[i].selected) {
  915. this.get_by_sel_idx(s, i+1).sel_idx--;
  916. s.options[i].field.sel_idx++;
  917. }
  918. }
  919. this.refresh();
  920. }
  921. // -------------------------------------------------------------------------------------
  922. _r.ReportColumnPicker.prototype.move = function(s, type, all) {
  923. for(var i=0;i<s.options.length; i++ ) {
  924. if(s.options[i].selected || all) {
  925. if(type=='add') {
  926. s.options[i].field.selected = 1;
  927. s.options[i].field.sel_idx = this.sel_idx;
  928. this.sel_idx++;
  929. } else {
  930. s.options[i].field.selected = 0;
  931. s.options[i].field.sel_idx = 0;
  932. this.sel_idx--;
  933. }
  934. }
  935. }
  936. this.refresh();
  937. }
  938. // -------------------------------------------------------------------------------------
  939. _r.ReportColumnPicker.prototype.refresh = function() {
  940. // separate
  941. var ul = []; var sl=[];
  942. for(var i=0; i<this.all_fields.length; i++) {
  943. var o = this.all_fields[i];
  944. if(o.selected) {
  945. sl.push(o);
  946. // enable sort option
  947. if(this.rb.dt) this.rb.dt.set_sort_option_disabled(o.df.label, 0);
  948. } else {
  949. ul.push(o);
  950. // disable sort option
  951. if(this.rb.dt) this.rb.dt.set_sort_option_disabled(o.df.label, 1);
  952. }
  953. }
  954. // sort by field idx
  955. ul.sort(function(a,b){return (cint(a.df.idx)-cint(b.df.idx))});
  956. // sort by order in which they were selected
  957. sl.sort(function(a,b){return (cint(a.sel_idx)-cint(b.sel_idx))})
  958. // re-number selected
  959. for(var i=0; i<sl.length; i++) { sl[i].sel_idx = i; }
  960. // add options
  961. this.set_options(this.unsel_fields, ul);
  962. this.set_options(this.sel_fields, sl);
  963. }
  964. // -------------------------------------------------------------------------------------
  965. _r.ReportColumnPicker.prototype.set_options = function(s, l) {
  966. empty_select(s);
  967. for(var i=0; i<l.length; i++) {
  968. var v = l[i].df.parent + '.' + l[i].df.label;
  969. var v_label = get_doctype_label(l[i].df.parent) + '.' + l[i].df.label;
  970. var o = new Option (v_label, v, false, false);
  971. o.field = l[i];
  972. if(o.field.is_selected) o.selected = 1;
  973. s.options[s.options.length] = o;
  974. }
  975. }
  976. // -------------------------------------------------------------------------------------
  977. _r.ReportColumnPicker.prototype.clear = function() {
  978. this.sel_idx = 0;
  979. for(var i=0; i<this.all_fields.length; i++) {
  980. this.all_fields[i].selected = 0;
  981. }
  982. this.refresh();
  983. }
  984. // -------------------------------------------------------------------------------------
  985. _r.ReportColumnPicker.prototype.get_selected = function() {
  986. var sl = [];
  987. for(var i=0; i<this.all_fields.length; i++) {
  988. var o = this.all_fields[i];
  989. if(o.selected) {
  990. sl[sl.length] = o.df;
  991. o.df.sel_idx = o.sel_idx;
  992. }
  993. }
  994. return sl.sort(function(a,b){return (cint(a.sel_idx)-cint(b.sel_idx))});
  995. }
  996. // -------------------------------------------------------------------------------------
  997. _r.ReportColumnPicker.prototype.set_defaults = function() {
  998. for(var i=0; i<this.all_fields.length; i++) {
  999. if(this.all_fields[i].selected_by_default)
  1000. this.all_fields[i].selected = 1;
  1001. }
  1002. }
  1003. // -------------------------------------------------------------------------------------
  1004. _r.ReportColumnPicker.prototype.add_field = function(f) {
  1005. // column picker
  1006. if(!f.label) return;
  1007. var by_default = (f.in_filter) ? 1 : 0;
  1008. this.all_fields.push({
  1009. selected:by_default
  1010. ,df:f
  1011. ,sel_idx: (by_default ? this.sel_idx : 0)
  1012. ,selected_by_default : by_default
  1013. });
  1014. this.sel_idx += by_default;
  1015. }
  1016. // -------------------------------------------------------------------------------------
  1017. _r.ReportColumnPicker.prototype.set = function(dt, label, selected) {
  1018. for(var i=0; i<this.all_fields.length; i++) {
  1019. if(this.all_fields[i].df.parent == dt && this.all_fields[i].df.label==label) {
  1020. this.all_fields[i].selected = selected;
  1021. this.all_fields[i].sel_idx = this.sel_idx;
  1022. this.sel_idx += cint(selected);
  1023. this.refresh();
  1024. return;
  1025. }
  1026. }
  1027. }