diff --git a/img/arrow.gif b/img/arrow.gif
new file mode 100644
index 0000000..0ca4745
Binary files /dev/null and b/img/arrow.gif differ
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 "
.load_job_img +")
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