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

2436 lines
68KB

  1. (function ($) {
  2. $(function () {
  3. // http://davidwalsh.name/javascript-debounce-function
  4. function debounce(func, wait, immediate) {
  5. var timeout;
  6. return function () {
  7. var context = this, args = arguments;
  8. var later = function () {
  9. timeout = null;
  10. if (!immediate)
  11. func.apply(context, args);
  12. };
  13. var callNow = immediate && !timeout;
  14. clearTimeout(timeout);
  15. timeout = setTimeout(later, wait);
  16. if (callNow)
  17. func.apply(context, args);
  18. };
  19. };
  20. /*____________________________________________________________________________________*/
  21. class People{
  22. constructor(selector, template, data){
  23. this.selector = selector;
  24. this.data = data;
  25. this.template = template;
  26. // this.sample_people = {
  27. // login: '01515b52-6936-46b2-a000-9ad4cd7a5b50',
  28. // firstname: "first",
  29. // lastname: "last",
  30. // phone: '041122221',
  31. // email: 'abc@gmail.com',
  32. // pay: 0,
  33. // hour: 12,
  34. // OT: 3,
  35. // petrol: 50,
  36. // rating: 1,
  37. // };
  38. this.load_data(this.data);
  39. }
  40. load_data(data){
  41. var template = $(this.template).html();
  42. var html = Mustache.render(template, data);
  43. $(this.selector).html(html);
  44. //save it
  45. $(this.selector).data({obj:this, data:data});
  46. //draw rating star
  47. //this.set_ratings(this.data.rating);
  48. this.set_unconfirmed_job(this.data.unconfirmedjob);
  49. }
  50. set_ratings(num){
  51. for (var i=1; i<= 5; i++){
  52. if (i <=num){
  53. $(this.selector + " div[name='rating'] span:nth-child(" +i+ ")").addClass('checked');
  54. }else{
  55. $(this.selector + " div[name='rating'] span:nth-child(" +i+ ")").removeClass('checked');
  56. }
  57. }
  58. this.data.rating = num;
  59. }
  60. set_km(km)
  61. {
  62. var str = "petrol:" + km.toFixed(2) + " km";
  63. $(this.selector + ' div[name="petrol"]').html(str);
  64. }
  65. set_unconfirmed_job(num){
  66. if( num == 0 )
  67. $(this.selector + " span[name='badge']").hide();
  68. else
  69. $(this.selector + " span[name='badge']").show();
  70. this.data.unconfirmedjob = num;
  71. }
  72. reset_summary() {
  73. this.summary = {
  74. wages : 0,
  75. normal_hour : 0,
  76. ot_hour : 0,
  77. petrol : 0,
  78. };
  79. this.update_summary_in_gui();
  80. }
  81. add_payment_summary(ps)
  82. {
  83. //{ot: false, hour: "2.67", money: "76.90"}
  84. this.summary.wages += ps.money;
  85. if (! ps.ot )
  86. this.summary.normal_hour += ps.hour;
  87. else
  88. this.summary.ot_hour += ps.hour;
  89. this.update_summary_in_gui();
  90. }
  91. update_summary_in_gui()
  92. {
  93. var msg = '$' + this.summary.wages.toFixed(2);
  94. $(this.selector).find('div[name="wages"]').html(msg);
  95. msg = this.summary.normal_hour.toFixed(2) + '+' +this.summary.ot_hour.toFixed(2) + 'hr';
  96. $(this.selector).find('div[name="hours"]').html(msg);
  97. msg = 'petrol:' + this.summary.petrol.toFixed(2) + 'km';
  98. $(this.selector).find('div[name="petrol"]').html(msg);
  99. }
  100. }//end of class People
  101. function bts_staff_html(data){
  102. var template = $('#staff_item').html();
  103. var head = '<div class="peopleitem" id="p'+ data.login +'">';
  104. r = head + '</div>' ;
  105. return r;
  106. }
  107. function bts_client_html(data){
  108. var template = $('#client_item').html();
  109. var head = '<div class="peopleitem" id="p'+ data.login +'">';
  110. r = head + '</div>' ;
  111. return r;
  112. }
  113. function sample_staff(){
  114. for (var i=1; i<100; i++){
  115. var sample_people = {
  116. login: '01515b52-6936-46b2-a000-9ad4cd7a5b50' +i,
  117. firstname: "first"+i,
  118. lastname: "last",
  119. mobile: '041122221' +i,
  120. email: 'abc@gmail.com' + i,
  121. wages: 0,
  122. hour: i,
  123. OT: 3,
  124. petrol: 50 +i,
  125. rating: Math.floor(Math.random() * Math.floor(5)),
  126. unconfirmedjob: Math.floor(Math.random() * Math.floor(30)),
  127. };
  128. var html = bts_staff_html(sample_people);
  129. jQuery('div.stafflist').append(html);
  130. new People("#p" + sample_people.login, sample_people);
  131. }
  132. }
  133. function list_staff() {
  134. show_loading_staff();
  135. $('div.stafflist div.peopleitem').remove();
  136. $.post(bts().ajax_url, { // POST request
  137. _ajax_nonce: bts().nonce, // nonce
  138. action: "list_staff", // action
  139. }).done(function(response, status, xhr){
  140. if (response.status =='success'){
  141. bts().staff = response.users;
  142. bts().staff_map = {};
  143. bts().staff_people= {};
  144. response.users.forEach(function(u){
  145. bts().staff_map[u.login] = u;
  146. var html = bts_staff_html(u);
  147. jQuery('div.stafflist').append(html);
  148. var staff_obj = new People("#p" + u.login,'#staff_item', u);
  149. bts().staff_people[u.login] = staff_obj;
  150. });
  151. hide_loading_staff();
  152. }else{
  153. alert('error getting staff list');
  154. }
  155. });
  156. }
  157. function list_clients() {
  158. show_loading_client();
  159. $('div.clientlist div.peopleitem').remove(); //clear it
  160. $.post(bts().ajax_url, { // POST request
  161. _ajax_nonce: bts().nonce, // nonce
  162. action: "list_client", // action
  163. }, function(response, status, xhr){
  164. if (response.status =='success'){
  165. bts().client = response.users;
  166. bts().client_map = {};
  167. response.users.forEach(function(u){
  168. bts().client_map[u.login] = u;
  169. hide_loading_client();
  170. var html = bts_client_html(u);
  171. jQuery('div.clientlist').append(html);
  172. new People("#p" + u.login, '#client_item' ,u);
  173. });
  174. }else{
  175. alert('error getting Client list');
  176. }
  177. });
  178. }
  179. function list_tos() {
  180. wifi(true);
  181. $.post(bts().ajax_url, { // POST request
  182. _ajax_nonce: bts().nonce, // nonce
  183. action: "list_tos", // action
  184. }, function(response, status, xhr){
  185. if (response.status =='success'){
  186. bts().tos = response.tos;
  187. wifi(false);
  188. }else{
  189. alert('error getting Type of Service list');
  190. }
  191. });
  192. }
  193. function show_loading_staff(){
  194. jQuery('div.stafflist img').attr('src', bts().load_user_img).show();
  195. }
  196. function show_loading_client(){
  197. jQuery('div.clientlist img').attr('src', bts().load_user_img).show();
  198. }
  199. function hide_loading_staff(){
  200. jQuery('div.stafflist img').hide();
  201. }
  202. function hide_loading_client(){
  203. jQuery('div.clientlist img').hide();
  204. }
  205. function show_loading_jobs(){
  206. jQuery('div.workspace img').attr('src', bts().load_job_img).show();
  207. }
  208. function hide_loading_jobs(){
  209. jQuery('div.workspace img').hide();
  210. }
  211. function xero(t){
  212. if (t)
  213. $('div.xero i').show();
  214. else
  215. $('div.xero i').hide();
  216. }
  217. function wifi(t){
  218. if (t)
  219. $('div.wifi i').show();
  220. else
  221. $('div.wifi i').hide();
  222. }
  223. function csv(t){
  224. if (t)
  225. $('div.csv i').show();
  226. else
  227. $('div.csv i').hide();
  228. }
  229. function init_user_search(){
  230. $('div.b_search input').keyup(debounce(function(e){
  231. filter_user(e.target);
  232. }, 500));
  233. }
  234. function filter_user(input){
  235. var value = $(input).val();
  236. value = value.toLowerCase();
  237. var selector = get_selector_for_filter_people(input);
  238. $.each( $(selector).find('div.peopleitem'), function(index, e){
  239. //uncheck everyone
  240. $(e).find('input[type="checkbox"]').prop('checked', false);
  241. var html = $(e).find('div[name="title"] a').html();
  242. html = html.toLowerCase();
  243. if (-1 != html.indexOf(value)){//we find it;
  244. $(e).show();
  245. }else{
  246. $(e).hide();
  247. }
  248. });
  249. }
  250. function get_selector_for_filter_people(input){
  251. var selector='';
  252. var role = $(input).attr('placeholder');
  253. if (role == 'staff') //we filter staff
  254. selector = 'div.stafflist';
  255. else if (role = 'client')
  256. selector = 'div.clientlist';
  257. return selector;
  258. }
  259. $(document).on('click', 'div.jobTable div.brate.error', function(){
  260. var msg = $(this).attr('title');
  261. if (msg != "")
  262. alert(msg);
  263. });
  264. $(document).on('click', 'div.workspace div.bedit span.ticon-edit', function(){
  265. var el = $(this).closest('div.jobTable');
  266. var newjob_id = el.attr('data-newjob_id');
  267. if (typeof newjob_id != 'undefined' && newjob_id != ''){
  268. do_edit_new_job(newjob_id);
  269. }else{
  270. var id = el.attr('data-id');
  271. do_edit_job(id);
  272. }
  273. });
  274. function get_workspace_start_date(){
  275. return new Date($('span[name="w1d1"]').data().date) ;
  276. }
  277. function get_payroll_start()
  278. {
  279. return new Date(bts().pay_calendar.start + " 00:00:00");
  280. }
  281. function allow_editing(date){
  282. var begin = new Date(date);
  283. var pay_begin = get_payroll_start();
  284. var seconds = (begin.getTime() - pay_begin.getTime())/1000;
  285. return seconds > -10; // 10 seconds difference make sure job right on the edge is working.
  286. }
  287. // $(document).on('click', 'div.bts_editor div.ult-overlay-close', function(){
  288. // $('.Editing').addClass('blink_me');
  289. // setTimeout(function(){
  290. // $(".Editing").removeClass('Editing blink_me');
  291. // }, 1000);
  292. //
  293. // });
  294. $(document).on('click', 'div.bts_editor div.ult-overlay-close', function(){
  295. var el = $('.Editing');
  296. el.addClass("blink_me").removeClass('Editing');
  297. setTimeout(function(){
  298. el.removeClass('blink_me');
  299. }, 600);
  300. });
  301. function close_editor(){
  302. $('div.bts_editor div.ult-overlay-close').trigger('click');
  303. }
  304. $(document).on('jobEditor:close', 'div.divTable.Editor', function(e,data){
  305. var editor = data.editor;
  306. var job = data.job;
  307. //console.log('close_editor event %o, %o', editor, job);
  308. editor.off_event_handler();//remove all events handler;
  309. var templ = $("#jobv1_item").html();
  310. var html = Mustache.render(templ, {jobs:job}); //job id should be available;
  311. if ( $('div.jobTable.Editing').length == 0){
  312. $('div.workspace').append(html);
  313. $('#job_'+job.id).get(0).scrollIntoView();
  314. }else{
  315. //create new job underneath it
  316. var templ = $("#jobv1_item").html();
  317. var html = Mustache.render(templ, {jobs:job}); //job id should be available;
  318. $('div.jobTable.Editing').after(html).remove();
  319. }
  320. var el = $('#job_' + job.id).addClass("blink_me");
  321. setTimeout(function(){
  322. el.removeClass('blink_me');
  323. }, 1500);
  324. debounced_calculate();
  325. });
  326. $(document).on('click', 'div.divTableHead.bdelete span.ticon-trash', function(){
  327. check_duplicate();
  328. setTimeout(do_delete_duplicate, 200);//200ms make GUI refresh
  329. });
  330. function fade_and_delete(el)
  331. {
  332. $(el).fadeOut(300);
  333. setTimeout(function(){
  334. $(el).remove();
  335. },300);
  336. }
  337. function add_new_empty_job(job){
  338. var title = "";
  339. if (typeof job == 'undefined' || ! (job instanceof Job) ){
  340. job = new Job({empty:true});
  341. job.is_new = true;
  342. title = "Create New Job ";
  343. }else if (job instanceof Job){
  344. title = "Create new Job by Copy Existing one";
  345. }
  346. job.editorid = Math.floor(Math.random() * Math.floor(99999)); // a random number;
  347. //show editor
  348. var html = $('#jobv1_editor').html();
  349. html = Mustache.render(html, job);
  350. set_modal_content('editor', html);
  351. //update GUI
  352. open_modal('editor');
  353. set_modal_title('editor', title);
  354. dtp_init();
  355. //init editor
  356. var e = new JobEditor('#editor_' + job.editorid, job);
  357. console.log("%o is instance of JobEditor %o", e, e instanceof JobEditor);
  358. }
  359. $(document).on('click', 'div[name="copyschedule"]', function(e){
  360. e.stopPropagation();
  361. add_new_empty_job();
  362. });
  363. function remove_job_from_gui(el)
  364. {
  365. el = $(el);
  366. var newjob_id = el.data().newjob_id;
  367. var id = el.data().id;
  368. if (typeof newjob_id != 'undefined' && newjob_id !=''){
  369. delete bts().job_map_new[newjob_id];
  370. }else if (typeof id != 'undefined' && id != ''){
  371. delete bts().job_map[id];
  372. }
  373. el.addClass('blink_me');
  374. el.fadeOut(500);
  375. setTimeout(function(){
  376. el.remove();
  377. debounced_calculate();
  378. }, 500);
  379. }
  380. $(document).on('click', 'div.divTableCell.bdelete', function(){
  381. var el = $(this).closest('div.jobTable');
  382. var data = el.data();
  383. if (typeof data.id =='undefined' || data.id == ''){//not saved
  384. remove_job_from_gui(el);//remove direclty
  385. }else{
  386. var id = data.id;
  387. if (confirm('delete this job?')){
  388. $.post(bts().ajax_url, { // POST request
  389. _ajax_nonce: bts().nonce, // nonce
  390. action: "delete_job", // action
  391. jobid: id,
  392. }, function(response, status, xhr){
  393. if (response.status=='success'){
  394. remove_job_from_gui(el);
  395. }else{
  396. alert( 'error deleting job, please check your network');
  397. }
  398. });
  399. }
  400. }
  401. });
  402. $(document).on('mouseenter', 'div.divTableCell', function(){
  403. $(this).closest('div.divTable').addClass('highlight');
  404. });
  405. $(document).on('mouseleave', 'div.divTableCell', function(){
  406. $(this).closest('div.divTable').removeClass('highlight');
  407. });
  408. $(document).on('click', 'div.workspace span.ticon.ticon-save', function(){
  409. var table = $(this).closest('div.jobTable');
  410. var data = table.data();
  411. if (data.id == "" && data.newjob_id != "" && data.newjob_id.substring(0,4) == "new_" ){
  412. job = bts().job_map_new[data.newjob_id];
  413. console.assert(typeof job != 'undefined');
  414. do_save_new_job(job.get_record(), table);
  415. return;
  416. }
  417. alert("Error occured");
  418. $(this).fadeOut();
  419. });
  420. $(document).on('click', '.divTableHeading span.ticon.ticon-save', function(){
  421. //save all
  422. $('div.workspace span.ticon.ticon-save').each(function (i,e){
  423. if ($(this).is(":visible"))
  424. $(this).trigger('click');
  425. });
  426. });
  427. $(document).on('click', 'span.ticon.ticon-copy', function(){
  428. var table = $(this).closest('div.jobTable');
  429. var record = bts().job_map[table.data().id].get_record();
  430. //create new job;
  431. var newj = clone_data_create_new_job(record);
  432. add_new_empty_job(newj);
  433. });
  434. function do_save_new_job(job_tobe_saved, table){
  435. $.post(bts().ajax_url, { // POST request
  436. _ajax_nonce: bts().nonce, // nonce
  437. action: "save_job", // action
  438. record: job_tobe_saved,
  439. }, function(response, status, xhr){
  440. if (response.status=='success'){
  441. var job = new Job(response.newdata);
  442. job.saved = true;
  443. job.is_new = response.isNew;
  444. bts().job_map[job.id] = job;
  445. //delete that old job and display the new one
  446. var jobs=[job];
  447. var html = Mustache.render($('#jobv1_item').html(), {jobs:jobs});
  448. delete bts().job_map_new[table.data().newjob_id];
  449. table.after(html);
  450. table.remove();
  451. }else{
  452. console.error("error saving job %o, response=%o", job_tobe_saved, response);
  453. alert( 'error saving data, please check your network');
  454. }
  455. });
  456. }
  457. function mark_highlight_me(el, ms){
  458. el.addClass('blink_me');
  459. el.addClass('highlight');
  460. el.addClass('newcopy');
  461. setTimeout(function(){
  462. el.removeClass('blink_me');
  463. el.removeClass('highlight');
  464. el.removeClass('newcopy');
  465. },ms);
  466. }
  467. class Job{
  468. constructor(record)
  469. {
  470. this.original_attr = Object.keys(record);
  471. $.extend(this, record);
  472. this.derive_attr();
  473. }
  474. get_record()
  475. {//without extended attributes;
  476. var self = this;
  477. var r = {};
  478. this.original_attr.forEach(function(attr){
  479. r[attr] = self[attr];
  480. });
  481. return r;
  482. }
  483. derive_attr()
  484. {
  485. this.tos_name(this);
  486. this.staff_name(this);
  487. this.client_name(this);
  488. this.rate_name(this);
  489. this.rating_range(this);
  490. this.identify_job_week(this);
  491. this.job_acked(this);
  492. this.job_disabled(this);
  493. }
  494. tos_name(e){
  495. if (typeof bts().tos[e.tos] != 'undefined'){
  496. e.tos_name = bts().tos[e.tos].tos_full_str;
  497. }else{
  498. e.tos_name = "(deleted) ";
  499. e.tos_err="Missing: " + e.tos;
  500. }
  501. }
  502. staff_name(e){
  503. if (typeof bts().staff_map[e.staff] != 'undefined'){
  504. e.staff_name = bts().staff_map[e.staff].display_name;
  505. }else{
  506. e.staff_name = "(deleted) ";
  507. e.staff_err="Missing: " + e.staff;
  508. }
  509. }
  510. client_name(e){
  511. if (typeof bts().client_map[e.client] != 'undefined'){
  512. e.client_name = bts().client_map[e.client].display_name;
  513. }else{
  514. e.client_name ="(deleted)";
  515. e.client_err ="Missing: " + e.client;
  516. }
  517. }
  518. rate_name(e){
  519. if (typeof bts().earnings_rate[e.rate] != 'undefined') {
  520. e.rate_name = bts().earnings_rate[e.rate].RatePerUnit + "-" + bts().earnings_rate[e.rate].Name;
  521. if (! has_txt_hour( bts().earnings_rate[e.rate].TypeOfUnits )){
  522. e.rate_err = `Rate unit must be ⟦ Hours ⟧
  523. Possible solution:
  524. 1. Change it in Xero
  525. 2. Delete this job`;
  526. }
  527. }else{
  528. e.rate_name = "(deleted)";
  529. e.rate_err = "Missing: " + e.rate;
  530. }
  531. }
  532. rating_range(e){
  533. if (e.rating <0 || e.rating >5){
  534. e.rating_err = "Rating 0-5 Only";
  535. }
  536. }
  537. identify_job_week(e){
  538. if (job_is_week1(e.start)){
  539. e.is_week1=true;
  540. }else if (job_is_week2(e.start)){
  541. e.is_week2=true;
  542. }
  543. }
  544. job_acked(e){
  545. if (e.ack != 0){
  546. e.is_confirmed = true;
  547. }
  548. }
  549. job_disabled(e){
  550. var allow = allow_editing(e.start);
  551. if (!allow)
  552. e.disabled =true;
  553. else
  554. delete e.disabled;
  555. }
  556. is_job_valid(){
  557. var error_found = false;
  558. var self = this;
  559. this.original_attr.forEach(function(attr){
  560. var col = attr + "_err";
  561. if (typeof self[col] == 'undefined')
  562. return;
  563. if (self[attr+"_err"] != "")
  564. error_found = true;
  565. });
  566. return !error_found;
  567. }
  568. is_high_pay()
  569. {
  570. var job = this;
  571. var rate_info = bts().earnings_rate[job.rate];
  572. var keywords =bts().high_pay_keywords;
  573. var found = false;
  574. keywords.forEach(function(e){
  575. if (-1 != rate_info.Name.toLowerCase().indexOf(e.toLowerCase()) )
  576. found = true;
  577. });
  578. return found;
  579. }
  580. get_payment_summary()
  581. {
  582. var job = this;
  583. var result ={};
  584. result.ot = job.is_high_pay();
  585. result.hour = job.get_working_duration();
  586. result.money = job.get_wages();
  587. return result;
  588. }
  589. get_working_duration()
  590. {
  591. var job = this;
  592. //finish - start
  593. var f = new Date(job.finish);
  594. var s = new Date(job.start);
  595. var diff = f.getTime() - s.getTime();
  596. var hours = Math.floor(diff / 1000 / 60 / 60);
  597. diff -= hours * 1000 * 60 * 60;
  598. var minutes = Math.floor(diff / 1000 / 60);
  599. var minute_to_hour = minutes/60;
  600. return (hours + minute_to_hour);
  601. }
  602. get_wages()
  603. {
  604. var job = this;
  605. var hour = job.get_working_duration(job);
  606. var rate_info = bts().earnings_rate[job.rate];
  607. if ( has_txt_hour( bts().earnings_rate[job.rate].TypeOfUnits ) )
  608. {
  609. return hour * rate_info.RatePerUnit;
  610. }else{
  611. return 1 * rate_info.RatePerUnit;
  612. }
  613. }
  614. allow_edit(){
  615. return allow_editing(this.start);
  616. }
  617. };
  618. class JobEditor{ //save data for the record, and display it as GUI
  619. constructor(selector, job){
  620. this.el = $(selector);
  621. this.load_data(job);
  622. console.assert(job instanceof Job);
  623. this.init_event_handler();
  624. }
  625. load_data(data)
  626. {
  627. //save to html element
  628. this.data = data;
  629. this.el.data({job:this, data:data});
  630. //draw GUI
  631. this.clear_err_msg();
  632. this.set_job_id(data.id);
  633. this.set_tos(data.tos);
  634. this.set_start(data.start);
  635. this.set_finish(data.finish);
  636. this.set_rate(data.rate);
  637. this.set_staff(data.staff);
  638. this.set_client(data.client);
  639. this.set_ack(data.ack);
  640. this.set_rating(data.rating);
  641. //draw GUI by other
  642. this.mark_dirty_on_new_record(data);
  643. this.mark_week_color();
  644. //this.validate(); //also triggers mark errors
  645. }
  646. init_event_handler(){
  647. var self = this;
  648. this.el.find("div.btos select").change(function(){
  649. if ( self.validate_tos() ) {
  650. self.data.tos = self.get_tos();
  651. self.set_err_msg_tos('');
  652. }
  653. });
  654. this.el.find("div.bstart input").change(function(){
  655. if (self.validate_start()){
  656. self.data.start = self.get_start();
  657. self.set_err_msg_start('');
  658. self.validate_start_and_finish();
  659. }
  660. });
  661. this.el.find("div.bfinish input").change(function(){
  662. if (self.validate_finish()){
  663. console.log(self);
  664. self.data.finish = self.get_finish();
  665. self.set_err_msg_finish('');
  666. self.validate_start_and_finish();
  667. }
  668. });
  669. this.el.find("div.bstaff select").change(function(){
  670. if (self.validate_staff()){
  671. self.data.staff = self.get_staff();
  672. self.set_err_msg_staff('');
  673. }
  674. });
  675. this.el.find("div.bclient select").change(function(){
  676. if (self.validate_client()){
  677. self.data.client = self.get_client();
  678. self.set_err_msg_client('');
  679. }
  680. });
  681. this.el.find("div.brate select").change(function(){
  682. if (self.validate_rate()){
  683. self.data.rate = self.get_rate();
  684. self.set_err_msg_rate('');
  685. }
  686. });
  687. this.el.find("div.bconfirmed input").change(function(){
  688. if(self.validate_ack()){
  689. self.data.ack = self.get_ack();
  690. }
  691. });
  692. this.el.find("div.brating select").change(function(){
  693. if( self.validate_rating()){
  694. self.data.rating =self.get_rating();
  695. }
  696. });
  697. this.el.find("div.bsave span.ticon-save").click(function(e){
  698. if ( self.validate() ){
  699. self.do_save_record();
  700. }else{
  701. self.set_err_msg_save('Data Error');
  702. }
  703. });
  704. }
  705. off_event_handler(){
  706. this.el.off('change',"div.btos select");
  707. this.el.off('change',"div.bstart input");
  708. this.el.off('change',"div.bfinish input");
  709. this.el.off('change',"div.bstaff select");
  710. this.el.off('change',"div.bclient select");
  711. this.el.off('change',"div.brate select");
  712. this.el.off('change',"div.bconfirmed input");
  713. this.el.off('change',"div.brating select");
  714. this.el.off('change',"div.bsave span.ticon-save");
  715. }
  716. get_job_id(){
  717. return this.el.attr('data-id');
  718. }
  719. set_job_id(val){
  720. if (typeof val == 'undefined' || val == '')
  721. {//remove data-id and id
  722. this.el.removeAttr('data-id');
  723. this.el.removeAttr('id');
  724. }else{
  725. this.el.attr('data-id', val);
  726. this.el.attr('id', 'job_' + val);
  727. }
  728. }
  729. get_tos()
  730. {
  731. return this.el.find('div.btos select').children("option:selected").val();
  732. }
  733. set_tos(val)
  734. {
  735. if (typeof(val) =="undefined")
  736. return;
  737. this.el.find('div.btos select option[value="'+val+'"]').prop('selected',true);
  738. if ( this.get_tos() != val ){
  739. this.set_err_msg_tos("Missing:" + this.data.tos)
  740. var o = new Option("(deleted)", this.data.tos);
  741. this.el.find('div.btos select').prepend(o);
  742. this.el.find('div.btos select option[value="'+this.data.tos+'"]').prop('selected',true);
  743. }
  744. }
  745. get_start(){
  746. return this.el.find('div.bstart input').val();
  747. }
  748. set_start(val)
  749. {
  750. if (typeof(val) =="undefined"){
  751. this.set_err_msg_start("need start");
  752. return;
  753. }
  754. this.el.find('div.bstart input').val(val);
  755. }
  756. get_finish()
  757. {
  758. return this.el.find('div.bfinish input').val();
  759. }
  760. set_finish(val)
  761. {
  762. if (typeof(val) == "undefined"){
  763. this.set_err_msg_finish("need finish");
  764. return;
  765. }
  766. this.el.find('div.bfinish input').val(val);
  767. }
  768. get_rate()
  769. {
  770. return this.el.find('div.brate select').children("option:selected").val();
  771. }
  772. set_rate(val)
  773. {
  774. if (typeof(val) =="undefined")
  775. return;
  776. this.el.find('div.brate select option[value="'+val+'"]').prop('selected',true);
  777. if ( this.get_rate() != val ){
  778. var o = new Option("(deleted)", this.data.rate);
  779. this.el.find('div.brate select').prepend(o);
  780. this.set_err_msg_rate('Missing:' + this.data.rate);
  781. this.el.find('div.brate select option[value="'+this.data.rate+'"]').prop('selected',true);
  782. }
  783. }
  784. get_staff()
  785. {
  786. return this.el.find('div.bstaff select').children("option:selected").val();
  787. }
  788. set_staff(val)
  789. {
  790. if (typeof(val) =="undefined")
  791. return;
  792. this.el.find('div.bstaff select option[value="'+val+'"]').prop('selected',true);
  793. if ( this.get_staff() != val ){
  794. var o = new Option("(deleted)", this.data.staff);
  795. this.el.find('div.bstaff select').prepend(o);
  796. this.set_err_msg_staff('Missing:' + this.data.staff);
  797. this.el.find('div.bstaff select option[value="'+this.data.staff+'"]').prop('selected',true);
  798. }
  799. }
  800. get_client()
  801. {
  802. return this.el.find('div.bclient select').children("option:selected").val();
  803. }
  804. set_client(val)
  805. {
  806. if (typeof(val) =="undefined")
  807. return;
  808. this.el.find('div.bclient select option[value="'+val+'"]').prop('selected',true);
  809. if ( this.get_client() != val ){
  810. var o = new Option("(deleted)", this.data.client);
  811. this.el.find('div.bclient select').prepend(o);
  812. this.set_err_msg_client('Missing:' + this.data.client);
  813. this.el.find('div.bclient select option[value="'+this.data.client+'"]').prop('selected',true);
  814. }
  815. }
  816. get_ack()
  817. {
  818. return this.el.find('div.bconfirmed input:checked').length > 0? 'true':0;
  819. }
  820. set_ack(val)
  821. {
  822. if (typeof(val) =="undefined")
  823. return;
  824. return this.el.find('div.bconfirmed input').prop('checked', val!=0);
  825. }
  826. get_rating(){
  827. return this.el.find('div.brating select').children("option:selected").val();
  828. }
  829. set_rating(num){
  830. if (!(0 <= num && num <=5))
  831. return;
  832. this.el.find('div.brating select option[value="'+num+'"]').prop('selected',true);
  833. }
  834. get_record_from_ui(){
  835. var record = {};
  836. record.id = this.get_job_id();
  837. record.tos = this.get_tos();
  838. record.start = this.get_start();
  839. record.finish = this.get_finish();
  840. record.rate = this.get_rate();
  841. record.staff = this.get_staff();
  842. record.client = this.get_client();
  843. record.ack = this.get_ack();
  844. record.rating = this.get_rating();
  845. return record;
  846. }
  847. do_save_record(){
  848. var self = this;
  849. $.post(bts().ajax_url, { // POST request
  850. _ajax_nonce: bts().nonce, // nonce
  851. action: "save_job", // action
  852. record: self.get_record_from_ui(),
  853. }, function(response, status, xhr){
  854. if (response.status=='success'){
  855. self.load_data(response.newdata);
  856. var job = new Job(response.newdata);
  857. job.saved = true;
  858. job.is_new = response.isNew;
  859. bts().job_map[job.id] = job;
  860. var data = {editor:self, job:job};
  861. self.el.trigger('jobEditor:close', data);
  862. close_editor();
  863. }else{
  864. self.self.set_err_msg_save('Not saved');
  865. alert( 'error saving data, please check your network');
  866. }
  867. });
  868. }
  869. mark_dirty_on_new_record(data){
  870. if (typeof(data.id) === 'undefined' || data.id == ''){
  871. this.mark_dirty();
  872. this.mark_new();
  873. }
  874. else{
  875. this.mark_saved();
  876. }
  877. }
  878. mark_dirty() //need save
  879. {
  880. var d = this.el.find('.bsave');
  881. d.removeClass('saved');
  882. d.addClass('blink_me');
  883. setTimeout(function(){
  884. d.removeClass('blink_me');
  885. d.removeClass('saved');
  886. },1000);
  887. }
  888. mark_saved()
  889. {
  890. var d = this.el.find('.bsave');
  891. d.addClass('blink_me');
  892. setTimeout(function(){
  893. d.removeClass('blink_me');
  894. d.addClass('saved');
  895. },1000);
  896. }
  897. //newly created empty record
  898. mark_new()
  899. {
  900. this.el.addClass('emptyrecord');
  901. }
  902. mark_old()
  903. {
  904. this.el.removeClass('emptyrecord');
  905. }
  906. is_start_valid(){
  907. var s = this.get_start();
  908. return is_valid_date_str(s);
  909. }
  910. is_finish_valid(){
  911. var f = this.get_finish();
  912. if (!is_valid_date_str(f))
  913. return false;
  914. }
  915. is_finish_resonable(){
  916. var f = this.get_finish();
  917. if (!is_valid_date_str(f))
  918. return false;
  919. var s = this.get_start();
  920. s = new Date(s);
  921. f = new Date(f);
  922. return (s < f);
  923. }
  924. validate()
  925. {
  926. var ok_tos = this.validate_tos();
  927. var ok_time = this.validate_start() && this.validate_finish() && this.validate_start_and_finish();
  928. var ok_staff = this.validate_staff();
  929. var ok_client = this.validate_client();
  930. var ok_rate = this.validate_rate() ; //make sure this validate is executed
  931. var ok = ok_tos && ok_time && ok_staff && ok_client && ok_rate;
  932. if (ok){
  933. this.el.removeClass('invalidjob');
  934. }else{
  935. this.el.addClass('invalidjob');
  936. }
  937. return ok;
  938. }
  939. validate_start(){
  940. var str = this.get_start();
  941. if (str == ""){
  942. this.set_err_msg_start('need start');
  943. return false;
  944. }
  945. if (!is_valid_date_str(str) ){
  946. this.mark_start_invalid();
  947. this.set_err_msg_start('wrong date');
  948. return false;
  949. }
  950. return true;
  951. }
  952. validate_finish()
  953. {
  954. var str = this.get_finish();
  955. if (str == ""){
  956. this.set_err_msg_finish('need finish');
  957. return false;
  958. }
  959. if (! is_valid_date_str(str)){
  960. this.set_err_msg_finish('wrong date');
  961. this.mark_finish_invalid();
  962. return false;
  963. }
  964. return true;
  965. }
  966. validate_start_and_finish()
  967. {
  968. if (! this.validate_start() || ! this.validate_finish())
  969. return;
  970. if (!this.is_finish_resonable()){
  971. this.set_err_msg_finish("older than start");
  972. this.set_err_msg_start("after finish");
  973. this.mark_start_invalid();
  974. this.mark_finish_invalid();
  975. return false;
  976. }else{
  977. this.mark_start_valid();
  978. this.mark_finish_valid();
  979. this.set_err_msg_finish("");
  980. this.set_err_msg_start("");
  981. return true;
  982. }
  983. }
  984. validate_rate()
  985. {
  986. var rate_info = this.get_rate_info_by_id(this.get_rate());
  987. if ( rate_info.RatePerUnit <= 0){
  988. this.set_err_msg_rate('bad rate');
  989. this.mark_rate_invalid();
  990. return false;
  991. }
  992. this.set_err_msg_rate('');
  993. this.mark_rate_valid();
  994. return true;
  995. }
  996. validate_tos(){
  997. if ( typeof bts().tos[this.get_tos()] == 'undefined') {
  998. this.set_err_msg_tos('missing ' + this.get_tos());
  999. return false;
  1000. }else{
  1001. this.set_err_msg_tos('');
  1002. return true;
  1003. }
  1004. }
  1005. validate_staff(){
  1006. if ( typeof bts().staff_map[this.get_staff()] == 'undefined') {
  1007. this.set_err_msg_staff('missing ' + this.get_staff());
  1008. return false;
  1009. }else{
  1010. this.set_err_msg_staff('');
  1011. return true;
  1012. }
  1013. }
  1014. validate_client(){
  1015. if ( typeof bts().client_map[this.get_client()] == 'undefined') {
  1016. this.set_err_msg_client('missing ' + this.get_client());
  1017. return false;
  1018. }else{
  1019. this.set_err_msg_client('');
  1020. return true;
  1021. }
  1022. }
  1023. validate_rating(){
  1024. return;
  1025. }
  1026. clear_err_msg(){
  1027. this.el.find('.divTableRow.errmsg > div').html('');
  1028. }
  1029. set_err_msg_start(str)
  1030. {
  1031. this.el.find('div.bstart_err').html(str);
  1032. }
  1033. set_err_msg_finish(str)
  1034. {
  1035. this.el.find('div.bfinish_err').html(str);
  1036. }
  1037. set_err_msg_staff(str)
  1038. {
  1039. this.el.find('div.bstaff_err').html(str);
  1040. }
  1041. set_err_msg_client(str)
  1042. {
  1043. this.el.find('div.bclient_err').html(str);
  1044. }
  1045. set_err_msg_rate(str)
  1046. {
  1047. this.el.find('div.brate_err').html(str);
  1048. }
  1049. set_err_msg_save(str)
  1050. {
  1051. this.el.find('div.bsave_err').html(str);
  1052. }
  1053. set_err_msg_tos(str)
  1054. {
  1055. this.el.find('div.btos_err').html(str);
  1056. }
  1057. mark_tos_valid(){
  1058. this.el.find('div.btos select').removeClass('invalid');
  1059. }
  1060. mark_tos_invalid(){
  1061. this.el.find('div.btos select').addClass('invalid');
  1062. }
  1063. mark_start_valid(){
  1064. this.el.find('div.bstart input').removeClass('invalid');
  1065. }
  1066. mark_start_invalid(){
  1067. this.el.find('div.bstart input').addClass('invalid');
  1068. }
  1069. mark_finish_valid(){
  1070. this.el.find('div.bfinish input').removeClass('invalid');
  1071. }
  1072. mark_finish_invalid(){
  1073. this.el.find('div.bfinish input').addClass('invalid');
  1074. }
  1075. mark_rate_valid(){
  1076. this.el.find('div.brate select').removeClass('invalid');
  1077. }
  1078. mark_rate_invalid(){
  1079. this.el.find('div.brate select').addClass('invalid');
  1080. }
  1081. mark_week_color(){
  1082. this.el.find('div.brating').removeClass('week1color');
  1083. this.el.find('div.brating').removeClass('week2color');
  1084. if (this.is_week1()){
  1085. this.el.find('div.brating').addClass('week1color');
  1086. }else if (this.is_week2()){
  1087. this.el.find('div.brating').addClass('week2color');
  1088. }
  1089. }
  1090. is_week1()
  1091. {
  1092. var w1_begin = new Date($('span[name="w1d1"]').data().date) ;
  1093. var w1_end = new Date($('span[name="w1d7"]').data().date);
  1094. w1_begin.setHours(0,0,0,0);
  1095. w1_end.setHours(23,59,59);
  1096. //console.log("week1 begin %o, end %o", w1_begin, w1_end);
  1097. //w1_end = new Date (w1_end.setDate(w1_end.getDate()+1)); //from 00:00 to 23:59;
  1098. var me = new Date(this.data.start);
  1099. return (w1_begin <= me && me <= w1_end );
  1100. }
  1101. is_week2()
  1102. {
  1103. var w2_begin = new Date($('span[name="w2d1"]').data().date);
  1104. var w2_end = new Date($('span[name="w2d7"]').data().date);
  1105. w2_begin.setHours(0,0,0,0);
  1106. w2_end.setHours(23,59,59);
  1107. var me = new Date(this.data.start);
  1108. return (w2_begin <= me && me <= w2_end );
  1109. }
  1110. get_payment_summary(){
  1111. var result ={};
  1112. result.ot = this.get_is_high_pay();
  1113. result.hour = this.get_working_duration();
  1114. result.money = this.get_wages();
  1115. return result;
  1116. }
  1117. get_is_high_pay()
  1118. {
  1119. var rate_info = this.get_rate_info_by_id(this.get_rate());
  1120. return this.is_high_pay_hour(rate_info);
  1121. }
  1122. get_working_duration()
  1123. {
  1124. //finish - start
  1125. var f = new Date(this.get_finish());
  1126. var s = new Date(this.get_start());
  1127. var diff = f.getTime() - s.getTime();
  1128. var hours = Math.floor(diff / 1000 / 60 / 60);
  1129. diff -= hours * 1000 * 60 * 60;
  1130. var minutes = Math.floor(diff / 1000 / 60);
  1131. var minute_to_hour = minutes/60;
  1132. return (hours + minute_to_hour);
  1133. }
  1134. get_wages(){
  1135. var hour = this.get_working_duration();
  1136. var rate_info = this.get_rate_info_by_id(this.get_rate());
  1137. return hour * rate_info.RatePerUnit;
  1138. }
  1139. get_rate_info_by_id(id){
  1140. var rate_info = {};
  1141. var rates = bts().earnings_rate;
  1142. for(var i =0; i< rates.length; i++){
  1143. var r = rates[i];
  1144. if(r.EarningsRateID == id){
  1145. rate_info = $.extend(true,{}, r);//make a copy
  1146. break;
  1147. }
  1148. }
  1149. return rate_info;
  1150. }
  1151. is_high_pay_hour(rate_info){
  1152. var keywords =bts().high_pay_keywords;
  1153. var found = false;
  1154. return false;
  1155. keywords.forEach(function(e){
  1156. if (-1 != rate_info.Name.toLowerCase().indexOf(e.toLowerCase()) )
  1157. found = true;
  1158. });
  1159. return found;
  1160. }
  1161. }//end of class Job
  1162. //global GUI summary
  1163. function get_wages()
  1164. {
  1165. var txt = $('div.wages div').html();
  1166. return parseInt(txt);
  1167. }
  1168. function set_wages(num){
  1169. $('div.wages div').html(num);
  1170. }
  1171. function set_working_hours(num){
  1172. $('input#woh').val(num);
  1173. }
  1174. function get_working_hours(){
  1175. var txt = $('input#woh').val();
  1176. return parseFloat(txt);
  1177. }
  1178. //modal box
  1179. function set_modal_title(selector, title){
  1180. var s = 'div.bts_'+ selector +' .ult_modal-title';
  1181. $(s).html(title);
  1182. }
  1183. function set_modal_content(selector, content){
  1184. var s = 'div.bts_'+ selector +' div.ult_modal-body.ult-html';
  1185. $(s).html(content);
  1186. }
  1187. function open_modal (selector){
  1188. var s='div.bts_'+selector+'_button';
  1189. $(s).trigger('click');
  1190. }
  1191. function set_modal_data(selector, data){
  1192. var s = 'div.bts_'+ selector;
  1193. $(s).data(data);
  1194. }
  1195. function get_modal_data(selector, data){
  1196. var s = 'div.bts_'+ selector;
  1197. $(s).data();
  1198. }
  1199. var blink_by_date_timer;
  1200. $(document).on('mouseenter', 'div.week1 > div, div.week2 > div', function(){
  1201. var self = this;
  1202. blink_by_date_timer = setTimeout (function(){
  1203. $(self).addClass('blink_me');
  1204. get_week2_partner(self).addClass('blink_me');
  1205. blink_same_date_by_div(self);
  1206. }, 1500);
  1207. });
  1208. $(document).on('mouseleave', 'div.week1 div, div.week2 > div', function(){
  1209. clearTimeout(blink_by_date_timer);
  1210. $(this).removeClass('blink_me');
  1211. get_week2_partner(this).removeClass('blink_me');
  1212. unblink_all_date();
  1213. });
  1214. function get_week2_partner(div){
  1215. var index = $(div).index()+1;
  1216. return $('div.week2 div:nth-child('+index+')');
  1217. }
  1218. function init_weekdays(){
  1219. var curr = new Date; // get current date
  1220. init_weekdays_by_anchor(curr, true);
  1221. return;
  1222. }
  1223. function init_weekdays_by_anchor(anchor, is_week1){
  1224. var curr = new Date(anchor); // get the date;
  1225. if (!is_week1){ //it is week2, shift for 7 days;
  1226. curr.setDate(curr.getDate() -7); //curr will be changed;
  1227. }
  1228. var first = curr.getDate() - curr.getDay() + 1; //+1 we want Mon as first
  1229. var last = first + 6; // last day is the first day + 6
  1230. if (curr.getDay() == 0 ){// it's Sunday;
  1231. last = curr.getDate();
  1232. first = last - 6;
  1233. }
  1234. var pos = 1; //first lot
  1235. for (var i=first; i<=last; i++)
  1236. {
  1237. var now = new Date(curr);
  1238. var d1 = new Date(now.setDate(i));
  1239. now = new Date(curr);
  1240. var d2 = new Date(now.setDate(i+7));
  1241. set_day_number(1,pos, d1); //week 1
  1242. set_day_number(2,pos, d2); //week 2
  1243. pos +=1;
  1244. }
  1245. }
  1246. function set_day_number(week, index, date){
  1247. var selector = 'span[name="w'+week+'d'+index+'"]';
  1248. $(selector).html(date.getDate());
  1249. $(selector).data({date:date});
  1250. //console.log('set w%d-d%d %o', week,index,date);
  1251. }
  1252. function set_today(){
  1253. var selector = 'div.sheettitle span[name="today"]';
  1254. var curr = new Date;
  1255. $(selector).html(format_date(curr));
  1256. }
  1257. Date.prototype.get_week_number = function(){
  1258. var d = new Date(Date.UTC(this.getFullYear(), this.getMonth(), this.getDate()));
  1259. var dayNum = d.getUTCDay() || 7;
  1260. d.setUTCDate(d.getUTCDate() + 4 - dayNum);
  1261. var yearStart = new Date(Date.UTC(d.getUTCFullYear(),0,1));
  1262. return Math.ceil((((d - yearStart) / 86400000) + 1)/7)
  1263. };
  1264. function set_week_number(){
  1265. var date = $('span[name="w1d1"]').data().date;
  1266. //console.log("date %o", date);
  1267. var num = date.get_week_number();
  1268. $('div.weekly span[name="week1"]').html(num);
  1269. $('div.weekly span[name="week2"]').html(num+1);
  1270. set_week_boundry();
  1271. }
  1272. function set_week_boundry()
  1273. {
  1274. var date = $('span[name="w1d1"]').data().date;
  1275. $('#week1b').val(format_date(date));
  1276. var date = $('span[name="w2d7"]').data().date;
  1277. $('#week2b').val(format_date(date));
  1278. }
  1279. function number_of_unsaved_job(){
  1280. var count =0;
  1281. var total_job = $('div.jobTable').length;
  1282. var total_saved = $('div.jobTable.saved').length;
  1283. var empty = $('div.emptyrecord').length;
  1284. count = total_job - total_saved - empty;
  1285. return count;
  1286. }
  1287. $('div.prevweek.left').click(function(){
  1288. if (number_of_unsaved_job() > 0){
  1289. if(!confirm("you have unsaved jobs, proceed will lost them"))
  1290. return;
  1291. }
  1292. $('div.weekdays span.weekday').each(function(i, e){
  1293. var date = $(e).data().date;
  1294. var newdate = new Date(date.setDate(date.getDate() -7 ));
  1295. $(e).html(newdate.getDate());
  1296. $(e).data({data:newdate});
  1297. });
  1298. set_week_number();
  1299. debounced_load_timesheet();
  1300. });
  1301. $('div.nextweek.right').click(function(){
  1302. if (number_of_unsaved_job() > 0){
  1303. if(!confirm("you have unsaved jobs,proceed will lost them"))
  1304. return;
  1305. }
  1306. $('div.weekdays span.weekday').each(function(i, e){
  1307. var date = $(e).data().date;
  1308. var newdate = new Date(date.setDate(date.getDate() +7 ));
  1309. $(e).html(newdate.getDate());
  1310. $(e).data({data:newdate});
  1311. });
  1312. set_week_number();
  1313. debounced_load_timesheet();
  1314. });
  1315. $('div.weekly div.weekname.prev >input ').click(function(e){
  1316. e.stopPropagation();
  1317. });
  1318. $('div.weekly div.weekname.prev >input ').change(function(e){
  1319. var date = $('#week1b').val();
  1320. init_weekdays_by_anchor(date, true);
  1321. set_week_number();
  1322. debounced_load_timesheet();
  1323. });
  1324. $('div.weekly div.weekname.prev').click(function(){
  1325. if (!confirm ('copy entire week to next week?'))
  1326. return;
  1327. var jobs = [];
  1328. $('div.week1 >div').each(function(i,e){
  1329. var date = new Date($(e).find('span.weekday').data().date);
  1330. var strDate = format_date(date); //yyyy-mm-dd
  1331. $('div.bstart:visible').each(function(i,e){
  1332. var value = $(e).html();
  1333. if( -1 != value.indexOf(strDate) ) //found
  1334. {
  1335. var el = $(e).closest('div.jobTable');
  1336. if (el.is(":visible") && el.hasClass('saved') && ! el.hasClass('disabled')){
  1337. var id = el.data().id;
  1338. var j = bts().job_map[id];
  1339. var newj = clone_data_create_new_job(j.get_record(),7);//add 7 days
  1340. add_new_job_to_map(newj);
  1341. jobs.push(newj);
  1342. }
  1343. }
  1344. });
  1345. });
  1346. show_jobs(jobs);
  1347. debounced_calculate();
  1348. alert("Copied " + jobs.length + " jobs");
  1349. });
  1350. $('div.weekly div.weekname.next > input').click(function(e){
  1351. e.stopPropagation();
  1352. });
  1353. $('div.weekly div.weekname.next >input ').change(function(e){
  1354. e.stopPropagation();
  1355. var date = $('#week2b').val();
  1356. init_weekdays_by_anchor(date, false);
  1357. set_week_number();
  1358. debounced_load_timesheet();
  1359. });
  1360. $('div.weekly div.weekname.next').click(function(e){
  1361. $(e).find('input').trigger('click');
  1362. });
  1363. $('div.week1 > div').click(copy_to_next_week);
  1364. function copy_to_next_week (e){
  1365. e.stopPropagation();
  1366. debounced_copy_to_next_week(e, this);
  1367. }
  1368. var debounced_copy_to_next_week = debounce(function (e, self) {
  1369. blink_same_date_by_div(self);
  1370. if ($('div.bstart.blink_me').length == 0){
  1371. alert("nothing to copy");
  1372. return;
  1373. }
  1374. if (!confirm ('copy to next week'))
  1375. return;
  1376. var new_jobs = [];
  1377. $('div.bstart.blink_me').each(function(i,e){
  1378. var r = copy_single_day_to_next_week(e);
  1379. if (r != false){
  1380. add_new_job_to_map(r);
  1381. new_jobs.push(r);
  1382. }
  1383. });
  1384. show_jobs(new_jobs);
  1385. unblink_all_date();
  1386. //alert("Copied " + new_jobs.length + " jobs");
  1387. }, 500);
  1388. $('div.week1,div.week2').click(function(e){
  1389. e.stopPropagation();
  1390. $(this).toggleClass('filtered');
  1391. do_filter_workspace();
  1392. });
  1393. function copy_single_day_to_next_week(el){
  1394. var tb = $(el).closest('div.jobTable');
  1395. if (tb.is(':visible') && tb.hasClass('saved')){
  1396. var id = tb.data().id;
  1397. var j = bts().job_map[id];
  1398. if (! j.allow_edit())
  1399. return false;
  1400. var newj = clone_data_create_new_job(j.get_record() , 7); // +7 days
  1401. return newj;
  1402. }
  1403. return false;
  1404. }
  1405. function clone_data_create_new_job(val, num_of_shifted_days){
  1406. var data = $.extend(true, {}, val);//make a copy
  1407. num_of_shifted_days = typeof num_of_shifted_days !=='undefined'? num_of_shifted_days: 0;// 0 days
  1408. //reset
  1409. data.id='';
  1410. data.ack = 0;
  1411. data.rating = 0;
  1412. if (is_valid_date_str(data.start)){
  1413. var s = new Date(data.start);
  1414. var s1 = s.getDate() + num_of_shifted_days;
  1415. s = new Date(s.setDate(s1));
  1416. data.start = format_date_time(s);
  1417. }
  1418. if (is_valid_date_str(data.finish)){
  1419. var f = new Date(data.finish);
  1420. var f1 = f.getDate() + num_of_shifted_days;
  1421. f = new Date(f.setDate(f1));
  1422. data.finish = format_date_time(f);
  1423. }
  1424. var newj = new Job(data);
  1425. //return;
  1426. return newj;
  1427. }
  1428. function add_new_job_to_map(newj)
  1429. {
  1430. //add to job map
  1431. newj.newjob_id = "new_" + bts_unique_ID();
  1432. if (typeof bts().job_map_new == 'undefined'){
  1433. bts().job_map_new = [];
  1434. }
  1435. bts().job_map_new[newj.newjob_id] = newj;
  1436. }
  1437. function is_valid_date_str(val){
  1438. var d = new Date(val);
  1439. if (d.toString()== 'Invalid Date')
  1440. return false;
  1441. return true;
  1442. }
  1443. function blink_same_date_by_div(div){
  1444. var date = new Date($(div).find('span.weekday').data().date);
  1445. blink_same_date(date);
  1446. }
  1447. function blink_same_date(date){
  1448. var strDate = format_date(date); //yyyy-mm-dd
  1449. var els=[];
  1450. unblink_all_date();
  1451. var first_into_view = false; //make sure first row in match is visible
  1452. $('div.workspace div.bstart:visible').each(function(i,e){
  1453. var value = $(e).html();
  1454. if( -1 != value.indexOf(strDate) ) //found
  1455. {
  1456. if ( !first_into_view ){ //scroll to top
  1457. first_into_view = true;
  1458. ensure_visible(e);
  1459. }
  1460. els.push($(e));
  1461. $(e).addClass('blink_me');
  1462. }
  1463. });
  1464. }
  1465. function ensure_visible(el){
  1466. $(el).get(0).scrollIntoView();
  1467. }
  1468. function unblink_all_date(){
  1469. $('div.bstart').removeClass('blink_me');
  1470. }
  1471. $('div.sheettitle h1 span').click(function(){
  1472. reset_title_to_today();
  1473. load_timesheet();
  1474. });
  1475. function reset_title_to_today(){
  1476. set_today();
  1477. init_weekdays();
  1478. set_week_number();
  1479. }
  1480. var debounced_load_timesheet = debounce(load_timesheet,1000);
  1481. function load_timesheet()
  1482. {
  1483. clear_workspace();
  1484. var first = $('span[name="w1d1"]').data().date;
  1485. var last = $('span[name="w2d7"]').data().date;
  1486. $.post(bts().ajax_url, { // POST request
  1487. _ajax_nonce: bts().nonce, // nonce
  1488. action: "list_jobv1", // action
  1489. start: format_date(first),
  1490. finish: format_date(last),
  1491. }, function(response, status, xhr){
  1492. if (response.status =='success'){
  1493. display_jobs_after_staff_client_tos_info_ready(response);
  1494. }else{
  1495. alert('error loading job');
  1496. hide_loading_jobs();
  1497. }
  1498. });
  1499. }
  1500. function display_jobs_after_staff_client_tos_info_ready(response)
  1501. {
  1502. var b = bts();
  1503. if ((typeof b.staff_map != "undefined" && Object.keys(b.staff_map).length > 0) &&
  1504. (typeof b.client_map != "undefined" && Object.keys(b.client_map).length > 0) &&
  1505. (typeof b.tos != "undefined" && Object.keys(b.tos).length > 0 ) &&
  1506. (typeof b.earnings_rate != "undefined" && Object.keys(b.earnings_rate).length > 0 ))
  1507. {
  1508. var job_map={};
  1509. var jobs = [];
  1510. //map data for each jobTable
  1511. response.jobs.forEach(function(e){
  1512. //job_derive_attr(e);
  1513. var j = new Job(e)
  1514. j.saved=true; //this is a record from database;
  1515. job_map[e.id] = j;
  1516. jobs.push(j);
  1517. });
  1518. bts().job_map = job_map;
  1519. //we do works, load timesheets
  1520. var template = $("#jobv1_item").html();
  1521. var html = Mustache.render(template, {jobs:jobs});
  1522. $('div.workspace').append(html);
  1523. hide_loading_jobs();
  1524. //filter it if reqired
  1525. debounced_filter_workspace();
  1526. return;
  1527. }
  1528. //console.log('wating staff/client/tos info to be ready');
  1529. setTimeout(function(){
  1530. display_jobs_after_staff_client_tos_info_ready(response);
  1531. }, 500); //try it half seconds later
  1532. }
  1533. function has_txt_hour(str){
  1534. if (str == null){
  1535. console.warn('null');
  1536. return;
  1537. }
  1538. var s = str.toLowerCase();
  1539. return s.indexOf('hour') != -1;
  1540. }
  1541. function show_jobs(jobs){
  1542. if (jobs.length >0){
  1543. var templ = $("#jobv1_item").html();
  1544. var html = Mustache.render(templ, {jobs:jobs}); //job id should be available;
  1545. var el = $(html);
  1546. $('div.workspace').append(el);
  1547. el.get(0).scrollIntoView();
  1548. }
  1549. debounced_calculate();
  1550. }
  1551. function format_date(date){
  1552. var dd = date.getDate();
  1553. var mm = date.getMonth() + 1; //January is 0!
  1554. var yyyy = date.getFullYear();
  1555. if (dd < 10) {
  1556. dd = '0' + dd;
  1557. }
  1558. if (mm < 10) {
  1559. mm = '0' + mm;
  1560. }
  1561. return yyyy + '-' + mm + '-' +dd ;
  1562. }
  1563. function format_date_time(date){
  1564. var strdate = format_date(date);
  1565. var hh = date.getHours();
  1566. if (hh<10){
  1567. hh= '0' + hh;
  1568. }
  1569. var mm = date.getMinutes();
  1570. if (mm<10){
  1571. mm='0' + mm;
  1572. }
  1573. return strdate + ' ' + hh + ":" + mm;
  1574. }
  1575. function clear_workspace()//clear all timesheet jobs
  1576. {
  1577. $('div.workspace > div.divTable').remove();
  1578. //clear datetime picker
  1579. $('div.xdsoft_datetimepicker').remove();
  1580. //
  1581. bts().job_map = {};
  1582. bts().job_map_new = {};
  1583. show_loading_jobs();
  1584. }
  1585. $('div.workinghours').click(function(){
  1586. $('div.bts_message_button').trigger('click');
  1587. });
  1588. function check_workspace_error(){
  1589. var els = $('div.workspace').find('.error');
  1590. if(els.length >0){
  1591. els.get(0).scrollIntoView();
  1592. return true;
  1593. }
  1594. return false;
  1595. }
  1596. $('button[name="confirmschedule"]').click(function(){
  1597. if( check_workspace_error() ){
  1598. return;
  1599. }
  1600. if (!confirm('sending email to each staff for their job arrangement?'))
  1601. return;
  1602. $('div.bts_message .ult-overlay-close-inside').hide();
  1603. $('div.bts_message_button').trigger('click');
  1604. setTimeout(do_email_jobs, 2000);//2 seconds for dialog to popup
  1605. });
  1606. $('button[name="confirmschedule"]').mouseenter(function(){
  1607. $('div.week2').addClass('blink_me');
  1608. })
  1609. $('button[name="confirmschedule"]').mouseleave(function(){
  1610. $('div.week2').removeClass('blink_me');
  1611. })
  1612. var debounced_filter_workspace = debounce(do_filter_workspace, 1000);
  1613. $(document).on('click','div.userlist', debounced_filter_workspace);
  1614. function do_filter_workspace(){
  1615. var staffs =[];
  1616. $('div.stafflist div.peopleitem :checked').each(function(i, e){
  1617. if ($(e).parent().is(':visible')){
  1618. var id = $(e).parent().attr('data-id');
  1619. //console.log("%o, id=%s", e, id);
  1620. staffs.push(id.substring(1));
  1621. }
  1622. });
  1623. var clients =[];
  1624. $('div.clientlist div.peopleitem :checked').each(function(i, e){
  1625. if ($(e).parent().is(':visible')){
  1626. var id = $(e).parent().attr('data-id');
  1627. //console.log("%o, id=%s", e, id);
  1628. clients.push(id.substring(1));
  1629. }
  1630. });
  1631. $('div.jobTable').show();//show non-week1 and none-week2
  1632. filter_workspace(staffs, clients);
  1633. filter_workspace_by_weeks();
  1634. debounced_calculate();
  1635. }
  1636. function filter_workspace(staffs, clients){
  1637. //if both array is empty
  1638. if( (staffs === undefined || staffs.length ==0) &&
  1639. (clients===undefined || clients.length ==0)){
  1640. //show all
  1641. $('div.workspace div.divTable').show();
  1642. return;
  1643. }
  1644. //if staffs is empty, we only filter by client
  1645. if (staffs === undefined || staffs.length ==0){
  1646. filter_workspace_by_client(clients);
  1647. return;
  1648. }
  1649. //if clients is empty, we only filter by staff
  1650. if (clients===undefined || clients.length ==0){
  1651. filter_workspace_by_staff(staffs);
  1652. return;
  1653. }
  1654. //filter by both
  1655. filter_workspace_by_both(staffs, clients);
  1656. }
  1657. function filter_workspace_by_staff(staffs)
  1658. {
  1659. var class_name='to_be_shown';
  1660. //filter some of them;
  1661. staffs.forEach(function(e){
  1662. $('div.workspace div.jobTable[data-staff="' + e + '"]').addClass(class_name);
  1663. $('div.workspace div.jobTable[data-driver="' + e + '"]').addClass(class_name);
  1664. });
  1665. $('div.workspace div.jobTable.' + class_name).fadeIn();
  1666. $('div.workspace div.jobTable:not(.'+ class_name +')').hide();
  1667. $('.' + class_name).removeClass(class_name);
  1668. }
  1669. function filter_workspace_by_client(clients)
  1670. {
  1671. var class_name='to_be_shown';
  1672. //filter some of them;
  1673. clients.forEach(function(e){
  1674. $('div.workspace div.jobTable[data-client="' + e + '"]').addClass(class_name);
  1675. });
  1676. $('div.workspace div.jobTable.' + class_name).fadeIn();
  1677. $('div.workspace div.jobTable:not(.'+ class_name +')').hide();
  1678. $('.' + class_name).removeClass(class_name);
  1679. }
  1680. function filter_workspace_by_both(staffs, clients)
  1681. {
  1682. var class_name='hide';
  1683. //filter some of them;
  1684. clients.forEach(function(e){
  1685. $('div.workspace div.jobTable:not([data-client="' + e + '"])').addClass(class_name);
  1686. });
  1687. staffs.forEach(function(e){
  1688. $('div.workspace div.jobTable:not([data-staff="' + e + '"])').addClass(class_name);
  1689. });
  1690. $('div.workspace div.jobTable.' + class_name).hide();
  1691. $('div.workspace div.jobTable:not(.'+ class_name +')').show();
  1692. $('.' + class_name).removeClass(class_name);
  1693. }
  1694. function filter_workspace_by_weeks(){
  1695. var hide_week1 = $('div.week1').hasClass('filtered');
  1696. var hide_week2 = $('div.week2').hasClass('filtered');
  1697. if (hide_week1 && hide_week2 ){
  1698. alert("You are hiding both weeks");
  1699. //$('div.jobTable').show();//show non-week1 and none-week2
  1700. $('div.jobTable.week1job,div.jobTable.week2job').hide(); //hide week1 or week2;
  1701. }else if (hide_week1){
  1702. //$('div.jobTable:not(.week1job)').show();//show non-week1
  1703. $('div.jobTable.week1job').hide(); //hide week1;
  1704. }else if (hide_week2){
  1705. $('div.jobTable.week2job').hide(); //show non-week2
  1706. //$('div.jobTable:not(.week2job)').show(); //hide week2
  1707. }
  1708. }
  1709. var debounced_calculate = debounce(calculate_total_hour_and_money, 2000);
  1710. function calculate_total_hour_and_money()
  1711. {
  1712. //init pays for all staff;
  1713. var pays={
  1714. total: 0,
  1715. hours: 0,
  1716. };
  1717. $('.stafflist > div.peopleitem').each(function(i,e){
  1718. var people = $(this).data().obj;
  1719. people.reset_summary();
  1720. });
  1721. $('div.workspace > .divTable.jobTable:visible').each(function(i,e){
  1722. job = get_job_by_jobTable(e);
  1723. if (typeof job === 'undefined' || !job.is_job_valid() )
  1724. return;
  1725. if (is_dummy_driving(job.staff))
  1726. return;
  1727. var ps = job.get_payment_summary();
  1728. pays.total += ps.money;
  1729. pays.hours += ps.hour;
  1730. var staff = job.staff;
  1731. var people = bts().staff_people[staff]; //class People
  1732. if (people !=false)
  1733. people.add_payment_summary(ps);
  1734. });
  1735. set_wages(pays.total.toFixed(2));
  1736. set_working_hours(pays.hours.toFixed(2));
  1737. calculate_driving();
  1738. }
  1739. function is_dummy_driving(staff){
  1740. return staff == bts().driving;
  1741. }
  1742. function get_job_by_jobTable(div)
  1743. {
  1744. var e = div;
  1745. var id = $(e).attr('data-id');
  1746. var job = bts().job_map[id];
  1747. if (id == ''){
  1748. //is this a new Job without id?
  1749. var newjob_id = $(e).attr('data-newjob_id');
  1750. if ( typeof newjob_id != "undefined"){
  1751. id = newjob_id;
  1752. job = bts().job_map_new[newjob_id];
  1753. }
  1754. }
  1755. return job;
  1756. }
  1757. function calculate_driving(){
  1758. var id = bts().driving;
  1759. var kms={};
  1760. $('div.jobTable[data-staff="' + id + '"]:visible').each(function(){
  1761. var el = this;
  1762. var match = find_driving_partner_job(el);
  1763. if (match == false)
  1764. return;
  1765. var staff = $('#' + match).data().staff;
  1766. if (typeof kms[staff] =='undefined'){
  1767. kms[staff] = 0;
  1768. }
  1769. kms[staff] += convert_driving_to_km(el);
  1770. //console.log($(this).attr('id'), matches, kms);
  1771. });
  1772. //console.log(kms);
  1773. for (var staff in kms ){
  1774. bts().staff_people[staff].set_km(kms[staff]);
  1775. //console.log(bts().staff_map[staff]);
  1776. ensure_visible(bts().staff_people[staff].selector);
  1777. }
  1778. }
  1779. function find_driving_partner_job(selector){
  1780. return false;
  1781. if (typeof $(selector).attr('data-parent') != "undefined" && $(selector).attr('data-parent') !="")
  1782. return $(selector).attr('data-parent');
  1783. var job = $(selector).data();
  1784. var start = new Date(job.start);
  1785. var client = job.client;
  1786. var matches = [];
  1787. $('div.workspace > .divTable.jobTable:visible').each(function(i,e){
  1788. var match = $(this).data();
  1789. staff = match.staff;
  1790. s = new Date(match.start);
  1791. c = match.client;
  1792. if ((start - s == 0) && (client == c) && staff != bts().driving){
  1793. matches.push({parent:$(this).attr('id'), driver: staff});
  1794. }
  1795. });
  1796. if (matches.length != 1){
  1797. //$(selector).find('.bstart').addClass("error");
  1798. //$(selector).find(".bstart_err").html("No matched driving Job");
  1799. //ensure_visible(selector);
  1800. console.warn("1 driving job has more than 1 matching", $(selector).attr('id'), matches);
  1801. return false;
  1802. }
  1803. $(selector).attr('data-driver', matches[0].driver);
  1804. $(selector).attr('data-parent', matches[0].parent);
  1805. $(selector).find('.bstaff').html("Driving/" + bts().staff_map[matches[0].driver].display_name);
  1806. return matches[0].parent;
  1807. }
  1808. function convert_driving_to_km(selector){
  1809. var data = $(selector).data();
  1810. var tos = data.tos;
  1811. var start = new Date(data.start);
  1812. var finish = new Date(data.finish);
  1813. var hours = Math.abs(finish - start) / 36e5; //in hours
  1814. var rate = parseFloat(bts().tos[tos].price);
  1815. var pay = hours * rate;
  1816. var km = pay / 1.2; //$1.2 per km
  1817. return km;
  1818. }
  1819. function find_staff(login)
  1820. {
  1821. var d = $('#p'+login).data();
  1822. if (typeof d === 'undefined')
  1823. return false;
  1824. return $('#p'+login).data().obj;
  1825. }
  1826. $(document).on('change', '.divTableRow input[name="ack"]', function(e) {
  1827. var el = $(this).closest('.jobTable');
  1828. var data = el.data();
  1829. data.ack = e.checked? 1: 0;
  1830. job_mark_dirty(el);
  1831. });
  1832. function job_mark_dirty(el)
  1833. {
  1834. el.removeClass('saved');
  1835. el.addClass('dirty');
  1836. }
  1837. function job_mark_clean(el)
  1838. {
  1839. el.addClass('saved');
  1840. el.removeClass('dirty');
  1841. }
  1842. function job_is_week1(t)
  1843. {
  1844. var w1_begin = new Date($('span[name="w1d1"]').data().date) ;
  1845. var w1_end = new Date($('span[name="w1d7"]').data().date);
  1846. w1_begin.setHours(0,0,0,0);
  1847. w1_end.setHours(23,59,59);
  1848. //console.log("week1 begin %o, end %o", w1_begin, w1_end);
  1849. //w1_end = new Date (w1_end.setDate(w1_end.getDate()+1)); //from 00:00 to 23:59;
  1850. var me = new Date(t);
  1851. return (w1_begin <= me && me <= w1_end );
  1852. }
  1853. function job_is_week2(t)
  1854. {
  1855. var w2_begin = new Date($('span[name="w2d1"]').data().date);
  1856. var w2_end = new Date($('span[name="w2d7"]').data().date);
  1857. w2_begin.setHours(0,0,0,0);
  1858. w2_end.setHours(23,59,59);
  1859. var me = new Date(t);
  1860. return (w2_begin <= me && me <= w2_end );
  1861. }
  1862. function init_ts(){
  1863. xero(false);
  1864. wifi(false);
  1865. csv(false);
  1866. show_loading_jobs();
  1867. list_staff();
  1868. list_clients();
  1869. list_tos();
  1870. ajax_earning_rate();
  1871. //setTimeout(list_staff, 5000); // for testing delayed loading of jobs only
  1872. //setTimeout(list_clients, 8000); // for testing delayed loading of jobs only
  1873. //setTimeout(list_tos, 10000); // for testing delayed loading of jobs only
  1874. init_user_search();
  1875. reset_title_to_today();
  1876. load_timesheet();
  1877. }
  1878. function do_email_jobs()
  1879. {
  1880. var selector = 'div.bts_message div.ult_modal-body';
  1881. $(selector).html('Analysis staff jobs ... ok');
  1882. var staff = bts().staff.slice(0);//copy this array;
  1883. var s = staff.pop();
  1884. //week2 start
  1885. var w2_begin = new Date($('span[name="w2d1"]').data().date);
  1886. var w2_end = new Date($('span[name="w2d7"]').data().date);
  1887. var start = format_date(w2_begin);
  1888. var finish = format_date(w2_end);
  1889. function do_staff(){
  1890. var el = $('<p> Checking ' + s.firstname + "....</p>");
  1891. $(selector).append(el);
  1892. el[0].scrollIntoView();
  1893. $.post(bts().ajax_url, { // POST request
  1894. _ajax_nonce: bts().nonce, // nonce
  1895. action: "email_job", // action
  1896. staff : s.login,
  1897. start : start,
  1898. finish: finish,
  1899. }, function(response, status, xhr){
  1900. if (response.status == 'success'){
  1901. if (response.sent){
  1902. el.append('<span class="sent">' + response.emailstatus + '</span>');
  1903. }else{
  1904. el.append('<span class="nojob">' + response.emailstatus + '</span>');
  1905. }
  1906. }else{
  1907. el.append('<span class="error"> Error[' + response.error + ' ...]</span>');
  1908. }
  1909. }).fail(function(){
  1910. el.append('<span class="error">' + 'Network Error occured' + '</span>');
  1911. //clear staff pending list, stop further processing
  1912. s = [];
  1913. }).always(function(){//next staff
  1914. if (staff.length >0){
  1915. s = staff.pop();
  1916. setTimeout(do_staff, 100); //a short delay makes it looks nice
  1917. }else{
  1918. $('div.bts_message .ult-overlay-close-inside').show();
  1919. $('div.bts_message .ult-overlay-close-inside').addClass('blink_me');
  1920. $('div.week2').removeClass('blink_me');
  1921. $(selector).append('<span class="sent">All staff confirmed! </span>');
  1922. }
  1923. });
  1924. }
  1925. //execute
  1926. do_staff();
  1927. }
  1928. function ajax_earning_rate(){
  1929. $.post(bts().ajax_url, { // POST request
  1930. _ajax_nonce: bts().nonce, // nonce
  1931. action: "earnings_rate", // action
  1932. }, function(response, status, xhr){
  1933. bts().earnings_rate = {};
  1934. response.options.forEach(function(e){
  1935. bts().earnings_rate[e.EarningsRateID]=e;
  1936. });
  1937. //console.log("%o", bts().earnings_rate);
  1938. });
  1939. }
  1940. function do_delete_duplicate()
  1941. {
  1942. var len = $('div.jobTable.to_be_deleted_duplicate').length;
  1943. if (len <= 0){
  1944. return;
  1945. }
  1946. if (!confirm("Delete " + len + " duplicates? ")){
  1947. return;
  1948. }
  1949. $('div.jobTable.to_be_deleted_duplicate:not(.saved)').each(function(){
  1950. remove_job_from_gui(this);
  1951. });
  1952. var ids = [];
  1953. $('div.jobTable.to_be_deleted_duplicate.saved').each(function(){
  1954. var id = $(this).attr('data-id');
  1955. if (id != '')
  1956. ids.push(id);
  1957. });
  1958. if ( ids.length >0 ){
  1959. $.post(bts().ajax_url, { // POST request
  1960. _ajax_nonce: bts().nonce, // nonce
  1961. action: "delete_jobv1", // action
  1962. jobs: ids, //delete multiple ids
  1963. }, function(response, status, xhr){
  1964. if (response.status=='success'){
  1965. $('div.jobTable.to_be_deleted_duplicate.saved').each(function(){
  1966. remove_job_from_gui(this);
  1967. });
  1968. debounced_calculate();
  1969. }else{
  1970. alert( 'error deleting duplicates:\n\nError:\n\n' + response.error + "\n\n");
  1971. }
  1972. });
  1973. }
  1974. }
  1975. function check_duplicate()
  1976. {
  1977. var to_be_deleted=[];
  1978. //make a copy of all jobs
  1979. var alljobs = {};
  1980. $('div.jobTable:visible').each(function(e){
  1981. alljobs[$(this).attr('id')] = $.extend({}, $(this).data());
  1982. });
  1983. //loop through jobs
  1984. for(var id1 in alljobs){
  1985. var job1 = alljobs[id1];
  1986. if (typeof job1.parent != 'undefined')
  1987. continue; //bypass it it has already found parents
  1988. job1.compared_as_master = true;//mark it
  1989. job1.duplicates={};
  1990. //console.log('investigating %s' , job1.id);
  1991. //match job2
  1992. for(var id2 in alljobs){
  1993. if (id1 == id2)
  1994. continue;
  1995. var job2 = alljobs[id2];
  1996. if (typeof job2.compared_as_master != 'undefined')
  1997. continue;
  1998. if (typeof job2.parent != "undefined")
  1999. continue; //it has already parent;
  2000. //console.log('comareing %s vs %s', job1.id, job2.id);
  2001. if (is_same_job(job1,job2)){
  2002. job2.parent = job1.id;
  2003. job1.duplicates[id2] = job2;
  2004. //console.warn("found: %s = %s", job1.id, job2.id);
  2005. to_be_deleted.push(id2);
  2006. }
  2007. }
  2008. }
  2009. if (to_be_deleted.length == 0){
  2010. alert("No duplicate found!");
  2011. return;
  2012. }
  2013. //console.log('all-done, found %d duplicates: %o', to_be_deleted.length, to_be_deleted);
  2014. mark_duplicate(to_be_deleted);
  2015. return to_be_deleted;
  2016. }
  2017. function is_same_job(job1, job2)
  2018. {
  2019. var s1 = new Date(job1.start);
  2020. var s2 = new Date(job2.start);
  2021. var f1 = new Date(job1.finish);
  2022. var f2 = new Date(job2.finish);
  2023. if ( (job1.tos == job2.tos) &&
  2024. (job1.staff == job2.staff) &&
  2025. (job1.client == job2.client) &&
  2026. (s1 - s2 == 0) &&
  2027. (f1 - f2 == 0) )
  2028. {
  2029. return true;
  2030. }
  2031. return false;
  2032. }
  2033. function mark_duplicate(ids)
  2034. {
  2035. ids.forEach(function(id){
  2036. var selector = '#' + id;
  2037. ensure_visible(selector);
  2038. $(selector).addClass('to_be_deleted_duplicate');
  2039. });
  2040. }
  2041. function do_edit_new_job(id)
  2042. {
  2043. open_modal('editor');
  2044. set_modal_title('editor', "Editing New Job ");
  2045. var new_job = bts().job_map_new[id];
  2046. new_job.editorid = id;
  2047. //set_modal_data('editor', {jobid: id, job_copy:job_copy});
  2048. var html = $('#jobv1_editor').html();
  2049. html = Mustache.render(html, new_job);
  2050. set_modal_content('editor', html);
  2051. //update GUI
  2052. dtp_init();
  2053. //init editor
  2054. var e = new JobEditor('#editor_' + id, new_job);
  2055. //console.log("e is instance of JobEditor %o", e instanceof JobEditor);
  2056. }
  2057. function do_edit_job(id)
  2058. {
  2059. //make a copy of the job
  2060. var child = bts().job_map[id];
  2061. if (! child.allow_edit()){
  2062. var msg =`You should not Edit this job,
  2063. it's locked by Xero,
  2064. Unless you insist to proceed
  2065. Are you sure you want edit it?
  2066. `;
  2067. if(!confirm(msg))
  2068. return;
  2069. }
  2070. var el = $("#job_" + id);
  2071. el.addClass('Editing');
  2072. open_modal('editor');
  2073. set_modal_title('editor', "Editing Job: " + id);
  2074. var job_copy = Object.assign(Object.create(Object.getPrototypeOf(child)), child); //a shallow copy only;
  2075. job_copy.editorid = bts_random_number();
  2076. //set_modal_data('editor', {jobid: id, job_copy:job_copy});
  2077. var html = $('#jobv1_editor').html();
  2078. html = Mustache.render(html, job_copy);
  2079. set_modal_content('editor', html);
  2080. //update GUI
  2081. dtp_init();
  2082. //init editor
  2083. var e = new JobEditor('#editor_' + job_copy.editorid, job_copy);
  2084. //console.log("e is instance of JobEditor %o", e instanceof JobEditor);
  2085. }
  2086. $( ".boundary_datepicker" ).datepicker();
  2087. $( ".boundary_datepicker" ).datepicker("option", "dateFormat", "yy-mm-dd");
  2088. $(document).on('click', 'div.clientlist div[name="title"] a', function(e){
  2089. e.preventDefault();
  2090. e.stopPropagation();
  2091. var id = $(this).closest('label.peopleitem').attr('data-id').substring(1);
  2092. var str = 'https://acaresydncy.com.au/feedback_card/' + id;
  2093. var name = $(this).html();
  2094. if ( confirm ("Email feedback link of : " + name + "\n\n\n" + str + "\n\n\n to helen@acaresydney.com.au?")){
  2095. $.post(bts().ajax_url, { // POST request
  2096. _ajax_nonce: bts().nonce, // nonce
  2097. action: "email_feedback_url", // action
  2098. client : id,
  2099. }, function(response, status, xhr){
  2100. //alert('please check your email on the phone and SMS the link to your client');
  2101. }).fail(function(){
  2102. alert('network error ');
  2103. });
  2104. }
  2105. });
  2106. init_ts();
  2107. $('div.divTableHeading div.bsave span.ticon-save').mouseenter(function(){//highlight unsaved
  2108. var el = $('div.jobTable.dirty .bsave');
  2109. if (el.length > 0){
  2110. el.addClass('blink_me');
  2111. el.get(0).scrollIntoView();
  2112. }
  2113. })
  2114. $('div.divTableHeading div.bsave span.ticon-save').mouseleave(function(){//highlight unsaved
  2115. var el = $('div.jobTable.dirty .bsave');
  2116. if (el.length > 0 ) {
  2117. el.removeClass('blink_me');
  2118. }
  2119. })
  2120. $(document).on('click', 'div.jobTable.to_be_deleted_duplicate div.bedit span.ticon-check-circle',function(){//highlight unsaved
  2121. var el = $(this).closest('div.jobTable');
  2122. if (!confirm ("Mark this is none duplicate?")){
  2123. return;
  2124. }
  2125. el.removeClass('to_be_deleted_duplicate');
  2126. });
  2127. $('div.divTableHeading div.bsave span.ticon-save').click(save_unsaved_copy);
  2128. function save_unsaved_copy(event)
  2129. {
  2130. event.preventDefault();
  2131. var num = $('div.jobTable.dirty:visible').length;
  2132. if (num > 0){
  2133. if ( !confirm('save all '+ num + ' jobs?')){
  2134. return;
  2135. }
  2136. $('div.jobTable.dirty:visible').each(function(){
  2137. $(this).find('span.ticon.ticon-save').trigger('click');
  2138. })
  2139. }else{
  2140. alert("nothing to save");
  2141. }
  2142. }
  2143. $('div.divTableHeading div.bsave span.ticon-save').contextmenu(function(event){
  2144. //clearn all unsaved jobs.
  2145. event.preventDefault();
  2146. var num = $('div.jobTable.dirty:visible').length;
  2147. if (num > 0){
  2148. if ( !confirm('delete all '+ num + ' unsaved?')){
  2149. return;
  2150. }
  2151. $('div.jobTable.dirty:visible').each(function(){
  2152. var newjob_id = $(this).data().newjob_id;
  2153. delete bts().job_map_new[newjob_id]
  2154. $(this).remove();
  2155. })
  2156. }else{
  2157. alert("nothing to clean up");
  2158. }
  2159. });
  2160. /*________________________________________________________________________*/
  2161. });
  2162. })(jQuery);
  2163. /*______________scrolling______________________________________________*/
  2164. jQuery(document).ready(function(){
  2165. var timeoutid =0;
  2166. jQuery('button.peoplelist[name="down"]').mousedown(function(){
  2167. var button = this;
  2168. timeoutid = setInterval(function(){
  2169. //console.log("down scrotop %d ", jQuery(button).parent().find(".userlist").get(0).scrollTop );
  2170. jQuery(button).parent().find(".userlist").get(0).scrollTop +=240;
  2171. }, 100);
  2172. }).on('mouseup mouseleave', function(){
  2173. clearTimeout(timeoutid);
  2174. });
  2175. jQuery('button.peoplelist[name="up"]').mousedown(function(){
  2176. var button = this;
  2177. timeoutid = setInterval(function(){
  2178. //console.log("up scrotop %d ",jQuery(button).parent().find(".userlist").get(0).scrollTop );
  2179. jQuery(button).parent().find(".userlist").get(0).scrollTop -=240;
  2180. }, 100);
  2181. }).on('mouseup mouseleave', function(){
  2182. clearTimeout(timeoutid);
  2183. });
  2184. });