From c612dae3338d1269ad70343a073619674f4a1caf Mon Sep 17 00:00:00 2001 From: patrick Date: Mon, 12 Aug 2019 17:55:25 +1000 Subject: [PATCH] xero sync with ui flash completed --- NdisPrice.php | 18 ++ TimeSheet.php | 29 ++- css/xeroc.css | 88 ++++++- html/bts_client_invoice_template.html | 55 +++++ html/bts_csv_template.html | 0 html/bts_staff_hours_template.html | 136 +++++------ html/timesheet.html | 4 + img/arrow.gif | Bin 0 -> 48023 bytes js/scrollintoview.js | 240 +++++++++++++++++++ js/xeroc.js | 247 +++++++++++++++++++- ts.php | 320 ++++++++++++++++++++++++-- 11 files changed, 1040 insertions(+), 97 deletions(-) create mode 100644 html/bts_client_invoice_template.html create mode 100644 html/bts_csv_template.html create mode 100644 img/arrow.gif create mode 100644 js/scrollintoview.js diff --git a/NdisPrice.php b/NdisPrice.php index e49d01d..4082646 100644 --- a/NdisPrice.php +++ b/NdisPrice.php @@ -61,4 +61,22 @@ class NdisPrice{ } return ""; } + + public function get_tos_price($ndis_code) + { + foreach ($this->tos as $r){ + if ($ndis_code == $r->code) + return (float) $r->price; + } + return 0; + } + + public function get_tos_unit($ndis_code) + { + foreach ($this->tos as $r){ + if ($ndis_code == $r->code) + return $r->unit; + } + return ""; + } } \ No newline at end of file diff --git a/TimeSheet.php b/TimeSheet.php index df5d37f..c956f2b 100644 --- a/TimeSheet.php +++ b/TimeSheet.php @@ -28,6 +28,10 @@ class TimeSheet{ // )); } + public function refresh_remote(){ + $this->get_remote_timesheets(); + } + private function get_remote_timesheets() { $this->remote_timesheets = $this->xero->load('PayrollAU\\Timesheet') @@ -80,6 +84,10 @@ class TimeSheet{ { $to_save=[]; foreach ( $this->local_timesheets as $t){ + $buddy = $this->get_buddy_timesheet_by_ts($t); + if ($buddy != NULL && $buddy->getStatus() != "DRAFT"){ + continue;//we encountered approved timesheet; + } $t->setDirty('EmployeeID'); $t->setDirty('StartDate'); $t->setDirty('EndDate'); @@ -107,6 +115,12 @@ class TimeSheet{ ->setEndDate(new \DateTime($this->end_date)) ->setStatus("DRAFT"); + foreach($ts->getTimesheetLines() as $line){ + $eid = $line->getEarningsRateID(); + $zeroline= $this->create_empty_timesheet_lines($eid); + $empty->addTimesheetLine($zeroline); + } + if ( $ts->getStatus() == "DRAFT" ){//good, we can save it; $to_save[] = $empty;//add it to save }else{ @@ -121,6 +135,15 @@ class TimeSheet{ $this->xero->saveAll($to_save, false); } + private function create_empty_timesheet_lines($EarningsRateID) + { + $line = new \XeroPHP\Models\PayrollAU\Timesheet\TimesheetLine($xero); + $line->setEarningsRateID($EarningsRateID); + for ($i=0; $i<14; $i++) + $line->addNumberOfUnit(0); + return $line; + } + private function get_timesheet_id_by_employee_id($id) { foreach ($this->remote_timesheets as $ts) @@ -138,9 +161,10 @@ class TimeSheet{ return $this->warning_timesheets; } - private function approve_all(){ + public function approve_all(){ $to_save=[]; - foreach ( $this->local_timesheets as $t){ + foreach ( $this->remote_timesheets as $t){ + if($t->getStatus() == 'DRAFT'){ $t->setDirty('EmployeeID'); $t->setDirty('StartDate'); $t->setDirty('EndDate'); @@ -150,6 +174,7 @@ class TimeSheet{ $t->setDirty('TimesheetID'); $t->setStatus('APPROVED'); $to_save[]=$t; + } } $this->xero->saveAll($to_save, false); } diff --git a/css/xeroc.css b/css/xeroc.css index fc1b851..d5cb828 100644 --- a/css/xeroc.css +++ b/css/xeroc.css @@ -1,17 +1,32 @@ @CHARSET "UTF-8"; +.blink_me { + animation: blinker 0.3s linear infinite; +} + +@keyframes blinker { + 50% { + opacity: 0; + } +} + .container{ width:100%; } -#cstart, #cfinish, #paydate{ - color: black; - font-weight:900; +#cstart, #cfinish, #paydate, +#invoice_start, #invoice_finish{ width:100%; - background-color: #EDEDED; border:none; + text-align: center; + color: green; + font-weight: 900; + background: #f0fbff; + font-size: 1.5em; } + + .hidden{ display:none; } @@ -20,6 +35,12 @@ td.sync_detail{ padding:0px; } +table.staffhours, +table.clientinvoice{ + background-color:white; +} + +div.invoice_button, table.hours{ margin-bottom:0px; } @@ -83,7 +104,7 @@ table.blueTable tfoot .links a{ } -.week1color { +tr.DRAFT .week1color { color: black; /* Permalink - use to edit and share this gradient: https://colorzilla.com/gradient-editor/#ebe9f9+0,d8d0ef+50,cec7ec+51,c1bfea+100;Purple+3D+%231 */ @@ -95,7 +116,7 @@ table.blueTable tfoot .links a{ } -.week2color { +tr.DRAFT .week2color { color: white; /* Permalink - use to edit and share this gradient: https://colorzilla.com/gradient-editor/#627d4d+0,1f3b08+100;Olive+3D */ @@ -104,4 +125,57 @@ table.blueTable tfoot .links a{ background: -webkit-linear-gradient(top, #627d4d 0%,#1f3b08 100%); /* Chrome10-25,Safari5.1-6 */ background: linear-gradient(to bottom, #627d4d 0%,#1f3b08 100%); /* W3C, IE10+, FF16+, Chrome26+, Opera12+, Safari7+ */ filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#627d4d', endColorstr='#1f3b08',GradientType=0 ); /* IE6-9 */ -} \ No newline at end of file +} + +#staff tr.loading, +#staff td.loading{ + text-align:center; + height:500px; +} + +td.loading img{ + margin-top:200px; +} + +table.hours th, +table.hours td{ + text-align: center; + width:7.14% ; +} + +.mismatch { + background-color:yellow; + color:red; + font-size:1.3em; + font-weight:900; +} + +td.invoice_nubmer{ + text-align:center; +} +img.waiting_invoice_number{ + height:32px; + display:none; +} + +.invoice_nameonly_row{ + cursor:pointer; +} +.invoice_nameonly_row:hover{ + background-color:yellow; +} + +.invoice_detail_row th{ + cursor:pointer; +} +.invoice_detail_row th:hover{ + color:green; +} +.invoice_nameonly_row_dummy{ + color:white; + background-color: black; + display:none; + animation: blinker 0.3s linear infinite; +} + + diff --git a/html/bts_client_invoice_template.html b/html/bts_client_invoice_template.html new file mode 100644 index 0000000..96ffde3 --- /dev/null +++ b/html/bts_client_invoice_template.html @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + {{#jobs}} + + + + + + + + + + + + {{/jobs}} + +
{{client_name}}ServiceStartFinishHoursUnit PriceStaffPriceInvoice Number
+ + +
{{{tos}}}{{start}}{{finish}}{{hours}}{{unitprice}}{{staff_name}}{{price}} + + +
+ + \ No newline at end of file diff --git a/html/bts_csv_template.html b/html/bts_csv_template.html new file mode 100644 index 0000000..e69de29 diff --git a/html/bts_staff_hours_template.html b/html/bts_staff_hours_template.html index 46a8f67..f23b011 100644 --- a/html/bts_staff_hours_template.html +++ b/html/bts_staff_hours_template.html @@ -1,68 +1,76 @@ {{#lines}} - - {{staff_name}} - {{rate_name}} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
{{days_1}}{{days_2}}{{days_3}}{{days_4}}{{days_5}}{{days_6}}{{days_7}}{{days_8}}{{days_9}}{{days_10}}{{days_11}}{{days_12}}{{days_13}}{{days_14}}
{{local_1}}{{local_2}}{{local_3}}{{local_4}}{{local_5}}{{local_6}}{{local_7}}{{local_8}}{{local_9}}{{local_10}}{{local_11}}{{local_12}}{{local_13}}{{local_14}}
-
To Xero - -
-
{{xero_1}}{{xero_2}}{{xero_3}}{{xero_4}}{{xero_5}}{{xero_6}}{{xero_7}}{{xero_8}}{{xero_9}}{{xero_10}}{{xero_11}}{{xero_12}}{{xero_13}}{{xero_14}}
+ + {{staff_name}}
Total: {{local_total}} hours + Earnings + + + + + + + + + + + + + + + + + +
{{days_1}}{{days_2}}{{days_3}}{{days_4}}{{days_5}}{{days_6}}{{days_7}}{{days_8}}{{days_9}}{{days_10}}{{days_11}}{{days_12}}{{days_13}}{{days_14}}
- {{Xero_Status}} + {{remote_total}} hours
{{Xero_Status}} + {{#ratetype}} + + {{rate_name}} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
{{local_1}}{{local_2}}{{local_3}}{{local_4}}{{local_5}}{{local_6}}{{local_7}}{{local_8}}{{local_9}}{{local_10}}{{local_11}}{{local_12}}{{local_13}}{{local_14}}
+
To Xero + +
+
{{xero_1}}{{xero_2}}{{xero_3}}{{xero_4}}{{xero_5}}{{xero_6}}{{xero_7}}{{xero_8}}{{xero_9}}{{xero_10}}{{xero_11}}{{xero_12}}{{xero_13}}{{xero_14}}
+ + + {{/ratetype}} {{/lines}} \ No newline at end of file diff --git a/html/timesheet.html b/html/timesheet.html index 3ef2f4e..a1dd7d7 100644 --- a/html/timesheet.html +++ b/html/timesheet.html @@ -169,15 +169,19 @@
+
+
+
+
$
diff --git a/img/arrow.gif b/img/arrow.gif new file mode 100644 index 0000000000000000000000000000000000000000..0ca47456972c97db1e9463feb7bef31d47ff0d75 GIT binary patch literal 48023 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 X6r0PC7sMg)G97(FWd#9nkmCOUShmqz literal 0 HcmV?d00001 diff --git a/js/scrollintoview.js b/js/scrollintoview.js new file mode 100644 index 0000000..0edd85c --- /dev/null +++ b/js/scrollintoview.js @@ -0,0 +1,240 @@ +/*! + * jQuery scrollintoview() plugin and :scrollable selector filter + * + * Version 1.9.4 (06 April 2016) + * Requires jQuery 1.4 or newer + * + * Copyright (c) 2011 Robert Koritnik + * Licensed under the terms of the MIT license + * http://www.opensource.org/licenses/mit-license.php + */ + +!function(root, factory) { + if (typeof define === 'function' && define.amd) { + define(['jquery'], factory); + } else if (typeof exports === 'object') { + factory(require('jquery')); + } else { + factory(root.jQuery); + } +} +(this, function($) { + var converter = { + vertical: { x: false, y: true }, + horizontal: { x: true, y: false }, + both: { x: true, y: true }, + x: { x: true, y: false }, + y: { x: false, y: true } + }; + + var settings = { + duration: "fast", + direction: "both", + viewPadding: 0 + }; + + var rootrx = /^(?:html)$/i; + + // gets border dimensions + var borders = function(domElement, styles) { + styles = styles || (document.defaultView && document.defaultView.getComputedStyle ? document.defaultView.getComputedStyle(domElement, null) : domElement.currentStyle); + var px = document.defaultView && document.defaultView.getComputedStyle ? true : false; + var b = { + top: (parseFloat(px ? styles.borderTopWidth : $.css(domElement, "borderTopWidth")) || 0), + left: (parseFloat(px ? styles.borderLeftWidth : $.css(domElement, "borderLeftWidth")) || 0), + bottom: (parseFloat(px ? styles.borderBottomWidth : $.css(domElement, "borderBottomWidth")) || 0), + right: (parseFloat(px ? styles.borderRightWidth : $.css(domElement, "borderRightWidth")) || 0) + }; + return { + top: b.top, + left: b.left, + bottom: b.bottom, + right: b.right, + vertical: b.top + b.bottom, + horizontal: b.left + b.right + }; + }; + + var dimensions = function($element) { + var elem = $element[0], + isRoot = rootrx.test(elem.nodeName), + $elem = isRoot ? $(window) : $element; + return { + border: isRoot ? { top: 0, left: 0, bottom: 0, right: 0 } : borders(elem), + scroll: { + top: $elem.scrollTop(), + left: $elem.scrollLeft(), + maxtop: elem.scrollHeight - elem.clientHeight, + maxleft: elem.scrollWidth - elem.clientWidth + }, + scrollbar: isRoot + ? { right: 0, bottom: 0 } + : { + right: $elem.innerWidth() - elem.clientWidth, + bottom: $elem.innerHeight() - elem.clientHeight + }, + rect: isRoot ? { top: 0, left: 0, bottom: elem.clientHeight, right: elem.clientWidth } : elem.getBoundingClientRect() + }; + }; + + $.fn.extend({ + scrollintoview: function(options) { + ///

Scrolls the first element in the set into view by scrolling its closest scrollable parent. + /// Additional options that can configure scrolling: + /// duration (default: "fast") - jQuery animation speed (can be a duration string or number of milliseconds) + /// direction (default: "both") - select possible scrollings ("vertical" or "y", "horizontal" or "x", "both") + /// complete (default: none) - a function to call when scrolling completes (called in context of the DOM element being scrolled) + /// + /// Returns the same jQuery set that this function was run on. + + options = $.extend({}, settings, options); + options.direction = converter[typeof (options.direction) === "string" && options.direction.toLowerCase()] || converter.both; + + if (typeof options.viewPadding == "number") { + options.viewPadding = { x: options.viewPadding, y: options.viewPadding }; + } else if (typeof options.viewPadding == "object") { + if (options.viewPadding.x == undefined) { + options.viewPadding.x = 0; + } + if (options.viewPadding.y == undefined) { + options.viewPadding.y = 0; + } + } + + var dirStr = ""; + if (options.direction.x === true) dirStr = "horizontal"; + if (options.direction.y === true) dirStr = dirStr ? "both" : "vertical"; + + var el = this.eq(0); + var scroller = el.parent().closest(":scrollable(" + dirStr + ")"); + + // check if there's anything to scroll in the first place + if (scroller.length > 0) { + scroller = scroller.eq(0); + + var dim = { + e: dimensions(el), + s: dimensions(scroller) + }; + + var rel = { + top: dim.e.rect.top - (dim.s.rect.top + dim.s.border.top), + bottom: dim.s.rect.bottom - dim.s.border.bottom - dim.s.scrollbar.bottom - dim.e.rect.bottom, + left: dim.e.rect.left - (dim.s.rect.left + dim.s.border.left), + right: dim.s.rect.right - dim.s.border.right - dim.s.scrollbar.right - dim.e.rect.right + }; + + var animProperties = {}; + + // vertical scroll + if (options.direction.y === true) { + if (rel.top < 0) { + animProperties.scrollTop = Math.max(0, dim.s.scroll.top + rel.top - options.viewPadding.y); + } else if (rel.top > 0 && rel.bottom < 0) { + animProperties.scrollTop = Math.min(dim.s.scroll.top + Math.min(rel.top, -rel.bottom) + options.viewPadding.y, dim.s.scroll.maxtop); + } + } + + // horizontal scroll + if (options.direction.x === true) { + if (rel.left < 0) { + animProperties.scrollLeft = Math.max(0, dim.s.scroll.left + rel.left - options.viewPadding.x); + } else if (rel.left > 0 && rel.right < 0) { + animProperties.scrollLeft = Math.min(dim.s.scroll.left + Math.min(rel.left, -rel.right) + options.viewPadding.x, dim.s.scroll.maxleft); + } + } + + // scroll if needed + if (!$.isEmptyObject(animProperties)) { + var scrollExpect = {}, + scrollListener = scroller; + + if (rootrx.test(scroller[0].nodeName)) { + scroller = $("html,body"); + scrollListener = $(window); + } + + function animateStep(now, tween) { + scrollExpect[tween.prop] = Math.floor(now); + }; + + function onscroll(event) { + $.each(scrollExpect, function(key, value) { + if (Math.floor(scrollListener[key]()) != Math.floor(value)) { + options.complete = null; // don't run complete function if the scrolling was interrupted + scroller.stop('scrollintoview'); + } + }); + } + + scrollListener.on('scroll', onscroll); + + scroller + .stop('scrollintoview') + .animate(animProperties, { duration: options.duration, step: animateStep, queue: 'scrollintoview' }) + .eq(0) // we want function to be called just once (ref. "html,body") + .queue('scrollintoview', function(next) { + scrollListener.off('scroll', onscroll); + $.isFunction(options.complete) && options.complete.call(scroller[0]); + next(); + }) + + scroller.dequeue('scrollintoview'); + } else { + // when there's nothing to scroll, just call the "complete" function + $.isFunction(options.complete) && options.complete.call(scroller[0]); + } + } + + // return set back + return this; + } + }); + + var scrollValue = { + auto: true, + scroll: true, + visible: false, + hidden: false + }; + + var scroll = function(element, direction) { + direction = converter[typeof (direction) === "string" && direction.toLowerCase()] || converter.both; + var styles = (document.defaultView && document.defaultView.getComputedStyle ? document.defaultView.getComputedStyle(element, null) : element.currentStyle); + var overflow = { + x: scrollValue[styles.overflowX.toLowerCase()] || false, + y: scrollValue[styles.overflowY.toLowerCase()] || false, + isRoot: rootrx.test(element.nodeName) + }; + + // check if completely unscrollable (exclude HTML element because it's special) + if (!overflow.x && !overflow.y && !overflow.isRoot) { + return false; + } + + var size = { + height: { + scroll: element.scrollHeight, + client: element.clientHeight + }, + width: { + scroll: element.scrollWidth, + client: element.clientWidth + }, + // check overflow.x/y because iPad (and possibly other tablets) don't dislay scrollbars + scrollableX: function() { + return (overflow.x || overflow.isRoot) && this.width.scroll > this.width.client; + }, + scrollableY: function() { + return (overflow.y || overflow.isRoot) && this.height.scroll > this.height.client; + } + }; + return direction.y && size.scrollableY() || direction.x && size.scrollableX(); + }; + + $.expr[":"].scrollable = $.expr.createPseudo(function(direction) { + return function(element) { + return scroll(element, direction); + }; + }); +}); diff --git a/js/xeroc.js b/js/xeroc.js index 023127b..7e01fd8 100644 --- a/js/xeroc.js +++ b/js/xeroc.js @@ -25,9 +25,15 @@ $( ".boundary_datepicker" ).datepicker("option", "dateFormat", "yy-mm-dd"); } + function loading() + { + return "

Sync to Xero

"; + } + + function display_hour_lines(response) { - $('#staff').html(); + $('#staff').html(''); var temp = $('#bts_staff_hours_template').html(); var html = Mustache.render(temp, response); $('#staff').append(html); @@ -41,6 +47,7 @@ } function get_timesheet_from_xero(){ + $('#staff').html(loading()); $.post(bts().ajax_url, { // POST request _ajax_nonce: bts().nonce, // nonce action: "get_timesheet_from_xero", // action @@ -57,13 +64,13 @@ } function sync_timesheet_from_xero(){ + $('#staff').html(loading()); $.post(bts().ajax_url, { // POST request _ajax_nonce: bts().nonce, // nonce action: "get_timesheet_from_xero", // action sync: true, }).done(function(response){ set_payroll_calendar(response.payroll_calendar); - console.log("%o", response); display_hour_lines(response); }).fail(function(){ console.warn('failed'); @@ -71,9 +78,243 @@ console.log('completed'); }); } + + function approve_all_timesheet() + { + $.post(bts().ajax_url, { // POST request + _ajax_nonce: bts().nonce, // nonce + action: "approve_all_timesheet", // action + }).done(function(response){ + if (response.status == 'success') + alert("approve all succeed"); + else + alert(response.err); + }).fail(function(){ + alert("Network Error, cannot approve all"); + }).always(function(){ + console.log('completed'); + }); + + } $('#sync_timesheet').click(function(){ sync_timesheet_from_xero(); }); + $('#approve_all').click(function(){ + approve_all_timesheet(); + }); + + function display_invoice_items_test(response) + { + var template = $('#bts_client_invoice_template').html(); + for (var i=1; i<10; i++){ + data = { + client_name: "Martin", + jobs:[ + { + tos: "service a " + i, + staff_name: "joe", + start: "2019-07-01", + finish: "2019-07-14", + hours: i, + price: 336, + }, + { + tos: "service b " + i, + staff_name: "joe dne", + start: "2019-07-01", + finish: "2019-07-14", + hours: i, + price: 16, + } + ] + } + html = Mustache.render(template, data); + $('#clientinvoice').append(html); + } + } + + function display_invoice_items(selector, response) + { + var template = $('#bts_client_invoice_template').html(); + html = Mustache.render(template, response); + el = $(html); + $(selector).after(el); + //el.SlideDown(); + el.show(); + } + + function get_invoice_item(selector, client_id) + { + $.post(bts().ajax_url, { // POST request + _ajax_nonce: bts().nonce, // nonce + action: "get_invoice_item", // action + client: client_id, + start: get_invoice_start(), + finish: get_invoice_finish(), + }).done(function(response){ + if (response.status == 'success'){ + display_invoice_items(selector, response); + $(selector).hide(); + }else{ + alert(response.err); + } + }).fail(function(){ + alert("Network Error, cannot approve all"); + }).always(function(){ + console.log('completed'); + }); + } + + function create_invoice_number(client_id) + { + start_showing_invoice_request(client_id); + $.post(bts().ajax_url, { // POST request + _ajax_nonce: bts().nonce, // nonce + action: "create_invoice_in_xero", // action + client: client_id, + start: get_invoice_start(), + finish: get_invoice_finish(), + }).done(function(response){ + if (response.status == 'success'){ + show_invoice_number(response); + }else{ + alert(response.err); + } + }).fail(function(){ + alert("Network Error, cannot approve all"); + }).always(function(){ + console.log('completed'); + }); + } + + function start_showing_invoice_request(client_id) + { + $('td.invoice_nubmer img').show(); + animate_into_top('#invoice_' + client_id); + return; + $('#invoice_' + client_id).scrollintoview({ + duration: 2500, + direction: "vertical", + viewPadding: { y: 10 }, + complete: function() { + // highlight the element so user's focus gets where it needs to be + } + }); + } + + function animate_into_top(el) + { + var offset = $(el).offset(); // Contains .top and .left + offset.left -= 20; + offset.top -= 20; + $('html, body').animate({ + scrollTop: offset.top, + scrollLeft: offset.left + },1000); + } + + function show_invoice_number(response) + { + $('td.invoice_nubmer').html(response.invoice_number); + $('td.invoice_button div').hide(); + } + + function get_invoice_start() + { + return $('#invoice_start').attr('value'); + } + function get_invoice_finish() + { + return $('#invoice_finish').attr('value'); + } + + $(document).on('click', 'td.client_nameonly', function(){ + var id = $(this).attr('data-client-id'); + $('#nameonly_' + id).hide(); + $('#dummyui_' + id).show(); + if( $('#invoice_' + id).length == 0 ){ + get_invoice_item('#dummyui_' + id, id); + }else{ + $('#dummyui_' + id).hide(); + $('#invoice_' + id).show(); + } + }); + + $(document).on('click', 'th.client_invoice', function(){ + var id = $(this).closest('td.client_invoice').attr('data-client-id'); + $('#nameonly_' + id).show(); + $('#invoice_' + id).hide(); + }); + + $(document).on('hide','#maintabs',function(){ + alert('abc'); + }); + + $(document).on("afterShow.vc.accordion", function(e, opt) { + console.log("%o, %o", e, opt); + if (e.target.hash =="#1565353205981-c3582e44-83d2"){ + get_timesheet_from_xero(); + } + }) + + function format_date(date){ + var dd = date.getDate(); + var mm = date.getMonth() + 1; //January is 0! + + var yyyy = date.getFullYear(); + if (dd < 10) { + dd = '0' + dd; + } + if (mm < 10) { + mm = '0' + mm; + } + return yyyy + '-' + mm + '-' +dd ; + } + + function daysInMonth (month, year) { + return new Date(year, month, 0).getDate(); + } + + function setup_invoice_start_finish() + { + var date = new Date(); + var firstDay = new Date(date.getFullYear(), + date.getMonth(), 1); + var lastDay = new Date(date.getFullYear(), + date.getMonth(), daysInMonth(date.getMonth()+1, + date.getFullYear())); + $('#invoice_start').attr('value', format_date(firstDay)); + $('#invoice_finish').attr('value', format_date(lastDay)); + } + + $('#invoice_start').change(function(){ + clear_all_invoice(); + }); + $('#invoice_finish').change(function(){ + clear_all_invoice(); + }); + + $(document).on('click', 'a.invoice_button', function(e){ + e.stopPropagation(); + var id = $(this).attr('data-client-login'); + create_invoice_number(id); + return false; + }); + + function clear_all_invoice() + { + if ( $(".invoice_detail_row").length >0 ){ + if (!confirm("Change Date will clear all invoice details")) + return; + } + $(".invoice_detail_row").remove(); + $(".invoice_nameonly_row").show(); + } + + + datebox(); + setup_invoice_start_finish(); + /*_____________________________________________*/ }); -})(jQuery); \ No newline at end of file +})(jQuery); diff --git a/ts.php b/ts.php index d19f433..fd7d777 100644 --- a/ts.php +++ b/ts.php @@ -49,7 +49,9 @@ class AcareOffice{ add_shortcode( 'bts_feedback_card', array($this, 'bts_feedback_card')); add_shortcode( 'bb_timesheet_canvas', array($this, 'bb_timesheet_canvas')); add_shortcode( 'bts_staff_hours_template', array($this, 'bts_staff_hours_template')); - + add_shortcode( 'bts_client_invoice_template', array($this, 'bts_client_invoice_template')); + add_shortcode( 'bts_csv_template', array($this, 'bts_csv_template')); + add_shortcode( 'bts_invoiced_client', array($this, 'bts_invoiced_client')); //user profile page add_shortcode( 'bts_user_name', array($this,'bts_user_name')); @@ -78,6 +80,10 @@ class AcareOffice{ add_action('wp_ajax_nopriv_client_ack_job', array($this,'client_ack_job' )); add_action('wp_ajax_get_timesheet_from_xero', array($this,'get_timesheet_from_xero' )); + add_action('wp_ajax_approve_all_timesheet', array($this,'approve_all_timesheet' )); + add_action('wp_ajax_get_invoice_item', array($this,'get_invoice_item' )); + add_action('wp_ajax_create_invoice_in_xero', array($this,'create_invoice_in_xero' )); + // hook add_rewrite_rules function into rewrite_rules_array add_filter('rewrite_rules_array', array($this,'my_add_rewrite_rules')); @@ -112,7 +118,32 @@ class AcareOffice{ $this->xero->init_wp(); //$abc = new AddrMap("01515b52-6936-46b2-a000-9ad4cd7a5b50", "0768db6d-e5f4-4b45-89a2-29f7e8d2953c"); - $abc = new AddrMap("122eb1d0-d8c4-4fc3-8bf8-b7825bee1a01", "0768db6d-e5f4-4b45-89a2-29f7e8d2953c"); + //$abc = new AddrMap("122eb1d0-d8c4-4fc3-8bf8-b7825bee1a01", "0768db6d-e5f4-4b45-89a2-29f7e8d2953c"); + + $this->check_csv_download(); + } + + private function check_csv_download() + { + $url = $_SERVER['REQUEST_URI']; + $matches=[]; + preg_match("/\/ndiscsv\/start-([^\/]+)\/finish-([^\/]+)\/?$/", $url, $matches); + if ( $matches !=3 || $_SERVER['REQUEST_URI'] != $matches[0] ) + return; + $start = $matches[1]; + $finish = $matches[2]; + $filename="{$start}___{$finish}.csv"; + + header("Expires: 0"); + header("Cache-Control: no-cache, no-store, must-revalidate"); + header('Cache-Control: pre-check=0, post-check=0, max-age=0', false); + header("Pragma: no-cache"); + header("Content-type: text/csv"); + header("Content-Disposition:attachment; filename=$filename"); + header("Content-Type: application/force-download"); + //readfile(dirname(__FILE__) . "/img/circle.png"); + echo "fuck this file"; + exit(); } @@ -191,6 +222,8 @@ class AcareOffice{ 'feedback_card/([^/]+)/week-([^/]+)/?$' => 'index.php?pagename=feedback_card&bts_user_id=$matches[1]&bts_week_id=$matches[2]', 'feedback_card/([^/]+)/start-([^/]+)/finish-([^/]+)/?$' => 'index.php?pagename=feedback_card&bts_user_id=$matches[1]&bts_job_start=$matches[2]&bts_job_finish=$matches[3]', + 'ndiscsv/start-([^/]+)/finish-([^/]+)/?$' => 'index.php?pagename=ndiscsv&bts_job_start=$matches[1]&bts_job_finish=$matches[2]', + ); $aRules = $aNewRules + $aRules; return $aRules; @@ -460,6 +493,7 @@ class AcareOffice{ wp_enqueue_style( 'bts_xeroc', plugins_url('css/xeroc.css', __FILE__)); wp_enqueue_script( 'bts_xeroc', plugins_url('js/xeroc.js', __FILE__), array( 'jquery' , 'bts' )); wp_enqueue_script('mustache', plugins_url('js/mustache.min.js', __FILE__), array('jquery')); + wp_enqueue_script('scrollintoview', plugins_url('js/scrollintoview.js', __FILE__), array('jquery')); global $wp_scripts; wp_enqueue_script('jquery-ui-datepicker'); @@ -610,6 +644,41 @@ class AcareOffice{ public function bts_staff_hours_template($attr){ return $this->template('bts_staff_hours_template', 'bts_staff_hours_template.html'); } + + public function bts_client_invoice_template($attr){ + return $this->template('bts_client_invoice_template', 'bts_client_invoice_template.html'); + } + + public function bts_csv_template($attr){ + return $this->template('bts_csv_template', 'bts_csv_template.html'); + } + + public function bts_invoiced_client($attr) + { + $result = ""; + $users = $users = get_users(array('role' => 'client')); + $row = << + + %s + + + + + %s is loading .... please wait... + + +ZOT; + foreach ($users as $u) + { + $payment = get_user_meta($u->ID, 'payment', true); + if( $payment != 'invoice' ) + continue; //bypass + $result .= sprintf($row, $u->user_login, $u->user_login, $u->user_login, $u->display_name, + $u->user_login, $u->user_login, $u->user_login, $u->display_name); + } + return $result; + } //generate template based on html file private function template($id, $file) { @@ -1029,6 +1098,7 @@ class AcareOffice{ if ($sync){ $xx->set_local_timesheet($local_ts); $xx->save_to_xero(); + $xx->refresh_remote(); } @@ -1047,33 +1117,52 @@ class AcareOffice{ 'staff_name' => $this->get_user_name_by_login ($staff_login), 'staff_id' => $staff_login, 'Xero_Status' => 'Empty', + 'rowspan' =>1, + 'local_total' =>0, + 'remote_total'=>0, + 'ratetype' => [], ); - //for local + $buddy = $xx->get_buddy_timesheets($staff_login, new \DateTime($start), new \DateTime($finish)); + if ($buddy != NULL){ + $item['Xero_Status'] = $buddy->getStatus(); + }else{ + $item['Xero_Status'] = "Not Exist"; + } + + foreach($details as $rate => $hours){ - $item['rate_name'] = $this->get_rate_name_by_id($rate); + $item['rowspan'] ++; + + $ratetype_item=[]; + $ratetype_item['rate_name'] = $this->get_rate_name_by_id($rate); + //for local for ($i=1; $i<=14; $i++) { - $item["local_$i"] = $hours[$i-1]; + $ratetype_item["local_$i"] = $hours[$i-1]; + $item['local_total'] += $hours[$i-1]; } - } - - //for remote - $buddy = $xx->get_buddy_timesheets($staff_login, new \DateTime($start), new \DateTime($finish)); - if ( $buddy != NULL ) - { - $item['Xero_Status'] = $buddy->getStatus(); - $remote_lines = $buddy->getTimesheetLines(); - foreach($remote_lines as $rl) + + //for remote + if ( $buddy != NULL ) { - if ( $rl->getEarningsRateID() == $rate){ - for ($i=1; $i<=14; $i++) - { - $item["xero_$i"] = $rl->getNumberOfUnits()[$i-1]; - } - break;//we found it - } + $remote_lines = $buddy->getTimesheetLines(); + foreach($remote_lines as $rl) + { + if ( $rl->getEarningsRateID() == $rate){ + for ($i=1; $i<=14; $i++) + { + $ratetype_item["xero_$i"] = $rl->getNumberOfUnits()[$i-1]; + $item['remote_total'] += $rl->getNumberOfUnits()[$i-1]; + if ($ratetype_item["xero_$i"] != $ratetype_item["local_$i"]){ + $ratetype_item["xero_{$i}_mismatch"] = "mismatch"; + } + } + break;//we found it + } + } } + $item['ratetype'][] = $ratetype_item; } $item = array_merge($item, $days); @@ -1086,6 +1175,31 @@ class AcareOffice{ wp_send_json($response); } + public function approve_all_timesheet() + { + check_ajax_referer('acaresydney'); + //set up payroll calendar + $pc = $this->xero->get_payroll_calendar(); + + $start = $pc->getStartDate()->format('Y-m-d'); + $finish = new \DateTime($start); + $finish = $finish->modify("+13 days")->format('Y-m-d'); + $paydate = $pc->getPaymentDate()->format('Y-m-d'); + + //prepare response + $response = array( + 'status' => 'success', + 'payroll_calendar' => array( + 'start' => $start, + 'finish' => $finish, + 'paydate'=> $paydate, + ), + ); + $xx = new \Biukop\TimeSheet($this->xero->get_xero_handle(), $finish); + $xx->approve_all(); + wp_send_json($response); + } + static public function get_user_name_by_login($login) { $user = get_user_by('login', $login); @@ -1095,6 +1209,167 @@ class AcareOffice{ return "Invalid Name"; } + + //ajax + public function get_invoice_item() + { + check_ajax_referer('acaresydney'); + $client = $_POST['client']; + //$client = "8cb3d205-6cdc-4187-ae39-9216923dd86d"; + $start = $_POST['start']; //2019-07-01'; + $finish= $_POST['finish'];//2019-07-31'; + + $sql = "SELECT * from $this->table_name WHERE tos != '00_000_0000_0_0' and start>='$start 00:00:00' and start<='$finish 23:59:59' and client='$client' ORDER BY start"; + $rows = $this->db->get_results($sql); + + $response=[ + 'status' =>'success', + 'client_login' => $client, + 'client_name' => $this->get_user_name_by_login($client), + 'jobs'=>[], + 'err'=>'' + ]; + $now = new \DateTime(); + $price = new NdisPrice($now->format("Y"));//current year; + + $summary=[];// by ndis code + foreach($rows as $r){ + $quantity = $this->get_job_hours($r->start, $r->finish); + $unit = $price->get_tos_unit($r->tos); + if ($unit != "Hour") + { + $quantity = 1; + } + $unitprice = $price->get_tos_price($r->tos); + $description = $this->get_job_description_for_invoice($price, $r); + + $data = [ + 'tos' => $price->get_tos_str($r->tos), + 'start' => $r->start, + 'finish' => $r->finish, + 'hours' => $quantity, + 'unitprice'=> $price->get_tos_price($r->tos), + 'staff_name' => $this->get_user_name_by_login($r->staff), + 'price' => sprintf("%.2f", $quantity * $unitprice), + ]; + $response['jobs'][] = $data; + $summary[$r->tos] += $quantity; + } + + if (count($response['jobs']) ==0) + { + $response['nojob'] = true; + } + + foreach ($summary as $key => $val) + { + $response['summary'][] = array( + 'ndis' => $key, + 'tos' => $price->get_tos_str($key), + 'Hours'=> $val, + ); + } + + + wp_send_json($response); + } + + public function create_invoice_in_xero() + { + check_ajax_referer('acaresydney'); + $client = $_POST['client']; + //$client = "8cb3d205-6cdc-4187-ae39-9216923dd86d"; + $start = $_POST['start']; //2019-07-01'; + $finish= $_POST['finish'];//2019-07-31'; + // + + $response=[ + 'status'=>success, + 'invoice_number' => '', + 'err'=> '', + ]; + + try{ + $invoice = $this->create_invoice_by_client($client, $start, $finish); + $response['invoice_number'] = sprintf("%s", + $invoice->getInvoiceID(), $invoice->getInvoiceNumber()); + }catch(\Exception $e){ + $response['status'] = 'error'; + $response['err'] = "XERO Invoice Error"; + } + + wp_send_json($response); + } + + + public function create_invoice_by_client($client_login, $start, $finish) + { + $user = get_user_by('login', $client_login); + if ( !$this->is_client($user) ) + return NULL; + $payment = get_user_meta($user->ID, "payment", true); + if ($payment != "invoice") + return NULL; + + $sql = "SELECT * from $this->table_name WHERE tos != '00_000_0000_0_0' and start>='$start 00:00:00' and start<='$finish 23:59:59' and client='$client_login' ORDER BY start"; + $rows = $this->db->get_results($sql); + + $xero = $this->xero->get_xero_handle(); + + //crate invoice + $invoice = new \XeroPHP\Models\Accounting\Invoice($xero); + $contact= $xero->loadByGUID('Accounting\\Contact', $client_login); + $now = new \DateTime(); + $due = new \DateTime(); + $due->modify("+14 days"); + $invoice->setType("ACCREC") + ->setStatus("DRAFT") + ->setContact($contact) + ->setDate($now) + ->setDueDate($due); + $to_save=[];//all invoices to save; + $price = new NdisPrice($now->format("Y"));//current year; + + foreach($rows as $r){ + $quantity = $this->get_job_hours($r->start, $r->finish); + $unit = $price->get_tos_unit($r->tos); + if ($unit != "Hour") + { + $quantity = 1; + } + $unitprice = $price->get_tos_price($r->tos); + $description = $this->get_job_description_for_invoice($price, $r); + $lineItem = new \XeroPHP\Models\Accounting\Invoice\LineItem($xero); + $lineItem->setDescription($description) + ->setQuantity($quantity) + ->setUnitAmount($unitprice) + ->setAccountCode(220) + ->setTaxType("EXEMPTOUTPUT"); + $invoice->addLineItem($lineItem); + } + + //prevent zero lineitems + if ( count($invoice->getLineItems()) >0 ) + $invoice->save(); + return $invoice; + } + + public function get_job_description_for_invoice($price, $job) + { + $description = sprintf( +'%s +[NDIS code: %s] +Time: %s - %s +By Carer : %s', + $price->get_tos_str($job->tos), + $job->tos, + $job->start, + $job->finish, + $this->get_user_name_by_login($job->staff) + ); + return $description; + } + public function create_timesheet_from_db($start, $finish){ $results = []; @@ -1177,6 +1452,9 @@ if ( defined( 'WP_CLI' ) && WP_CLI ) { \WP_CLI::add_command( 'feedback_url', array($bb, 'feedback_url')); \WP_CLI::add_command( 'produce_invoice', array($bb, 'produce_invoice')); } + +//$bb->class_loader(); +//$bb->create_invoice_by_client("8cb3d205-6cdc-4187-ae39-9216923dd86d", "2019-07-01", "2019-07-31"); //$idx = $bb->convert_date_to_idx("2019-07-02 14:30:00", "2019-07-01", "2019-07-02"); //wp_send_json($idx); //$bb->create_timesheet_from_db("2019-07-01", "2019-07-14"); \ No newline at end of file