diff --git a/TimeSheet.php b/TimeSheet.php new file mode 100644 index 0000000..df5d37f --- /dev/null +++ b/TimeSheet.php @@ -0,0 +1,214 @@ +xero = $xero; + $this->end_date = $end_date; + $this->cal_start(); //set start_date; + $this->get_remote_timesheets(); + } + + private function cal_start(){ + $d = new \DateTime($this->end_date); + $d->modify("-13 days"); + $this->start_date = $d->format("Y-m-d"); +// wp_send_json(array( +// 'start'=> $this->start_date, +// 'finish'=> $this->end_date, +// )); + } + + private function get_remote_timesheets() + { + $this->remote_timesheets = $this->xero->load('PayrollAU\\Timesheet') + ->where('EndDate==DateTime.Parse("'. $this->end_date .'")') + ->execute(); + } + + public function get_xero_timesheet() + { + return $this->remote_timesheets; + } + + public function get_local_timesheet() + { + return $this->local_timesheets; + } + + public function set_local_timesheet($lines) + { + $this->local_timesheets = []; + //convert $val to Timesheet format; + foreach ($lines as $staff_login => $rateshours){ + $ts = ""; + if (array_key_exists($staff_login, $this->local_timesheets)){ + $ts = $this->local_timesheets[$staff_login]; + }else{ + $ts = new \XeroPHP\Models\PayrollAU\Timesheet($this->xero); + $ts->setEmployeeID($staff_login) + ->setTimeSheetID($this->get_timesheet_id_by_employee_id($staff_login)) + ->setStartDate(new \DateTime($this->start_date)) + ->setEndDate(new \DateTime($this->end_date)) + ->setStatus("DRAFT"); + } + //adding lines + foreach ($rateshours as $rateid => $hours) + { + $ts_line = new \XeroPHP\Models\PayrollAU\Timesheet\TimesheetLine($this->xero); + $ts_line->setEarningsRateID($rateid); + for ($i=0; $i<14; $i++){ + $ts_line->addNumberOfUnit($hours[$i]); + } + $ts->addTimesheetLine($ts_line); + } + //update this timesheet; + $this->local_timesheets[$staff_login] = $ts; + } + } + + public function save_to_xero() + { + $to_save=[]; + foreach ( $this->local_timesheets as $t){ + $t->setDirty('EmployeeID'); + $t->setDirty('StartDate'); + $t->setDirty('EndDate'); + $t->setDirty('TimesheetLines'); + $t->setDirty('Status'); + $t->setDirty('Hours'); + $t->setDirty('TimesheetID'); + $t->setStatus('DRAFT'); + $to_save[]=$t; + } + //empty remote timesheet which are not available in local + // + //some of buddy timesheets might be removed from local already + //we cannot delete it but we can set it to 0 hours + // + foreach ($this->remote_timesheets as $ts) + { + $staff_login = $ts->getEmployeeID(); + if (!array_key_exists($staff_login, $this->local_timesheets)){//not found + //we create empty timesheets for him/her + $empty = new \XeroPHP\Models\PayrollAU\Timesheet($this->xero); + $empty->setEmployeeID($staff_login) + ->setTimeSheetID($ts->getTimesheetID()) + ->setStartDate(new \DateTime($this->start_date)) + ->setEndDate(new \DateTime($this->end_date)) + ->setStatus("DRAFT"); + + if ( $ts->getStatus() == "DRAFT" ){//good, we can save it; + $to_save[] = $empty;//add it to save + }else{ + $staff_name = \Biukop\AcareOffice::get_user_name_by_login($staff_login); + $msg = sprintf("%s : %s is APPROVED, but needs to be empty it", + $staff_name, + $ts->getTimeSheetID()); + \Biukop\AcareOffice::log($msg); + } + } + } + $this->xero->saveAll($to_save, false); + } + + private function get_timesheet_id_by_employee_id($id) + { + foreach ($this->remote_timesheets as $ts) + { + $staff_login = $ts->getEmployeeID(); + if($staff_login == $id){ + return $ts->getTimesheetID(); + } + } + return NULL; + } + + public function warning_timesheet() + { + return $this->warning_timesheets; + } + + private function approve_all(){ + $to_save=[]; + foreach ( $this->local_timesheets as $t){ + $t->setDirty('EmployeeID'); + $t->setDirty('StartDate'); + $t->setDirty('EndDate'); + $t->setDirty('TimesheetLines'); + $t->setDirty('Status'); + $t->setDirty('Hours'); + $t->setDirty('TimesheetID'); + $t->setStatus('APPROVED'); + $to_save[]=$t; + } + $this->xero->saveAll($to_save, false); + } + + public function get_buddy_timesheets($employee_id, $start, $end) + { + foreach ($this->remote_timesheets as $t){ + if ( $t->getEmployeeID() == $employee_id && + $t->getStartDate()->format('Y-m-d') == $start->format('Y-m-d') && + $t->getEndDate()->format('Y-m-d') == $end->format('Y-m-d') ) + { + return $t; + } + } + return NULL; //not found; + } + + private function get_buddy_timesheet_by_ts($t) + { + $employee_id = $t->getEmployeeID(); + $start = $t->getStartDate(); + $end = $t->getEndDate(); + return $this->get_buddy_timesheets($employee_id, $start, $end); + } + + public function main(){ + $this->warning_timesheets = []; + $this->buddy_timesheets = []; + $this->local_timesheets = $this->create_timesheet_from_db( + new DateTime("2019-07-01"), + new DateTime("2019-07-14") + ); + $length = count($this->local_timesheets); + $to_save =[]; + for ($i =0; $i < $length; $i++) + { + $me = $this->local_timesheets[$i]; + $buddy = $this->get_buddy_timesheet_by_ts($me); + + $this->buddy_timesheets[$i] = $buddy; + + if ( $buddy != NULL ) { + $timesheet_id = $buddy->getTimeSheetID(); + $me->setTimeSheetID($timesheet_id); + if ( $buddy->getStatus() != 'DRAFT'){ + $this->warning_timesheets[]=$me; + continue; + }else{ + $to_save[]=$me; + } + }else{ + $to_save[]=$me; + } + + } + $this->xero->saveAlL($to_save, false); //false do not check GUID, always use POST; not PUT + //$this->approve_all(); + } + + + +} diff --git a/Xero.php b/Xero.php index 7e09f84..5af19d7 100644 --- a/Xero.php +++ b/Xero.php @@ -43,6 +43,11 @@ class Xero { $this->xero = new PrivateApplication($this->office_config()); } + public function get_xero_handle() + { + return $this->xero; + } + public function getClients($contact_group_id){ $xero = $this->xero; $cg = $xero->loadByGUID("Accounting\\ContactGroup", $contact_group_id); @@ -68,7 +73,7 @@ class Xero { } - + // //sync users to wordpress system //does not work for too many users or employees @@ -302,7 +307,11 @@ class Xero { /* sync payitems to wp options */ public function init_wp(){ - $this->sync_payitem(); + try{ + $this->sync_payitem(); + }catch(\XeroPHP\Remote\Exception $e){ + + } } private function sync_payitem(){ @@ -350,4 +359,12 @@ class Xero { return $diff < $this->minimum_sync_interval_in_seconds; //default 10 minutes } + + public function get_payroll_calendar() + { + $id = "33dc7df5-3060-4d76-b4da-57c20685d77d"; //fortnightly + $pc = $this->xero->loadByGUID('PayrollAU\\PayrollCalendar', $id); + return $pc; + } + } \ No newline at end of file diff --git a/css/bts_timesheet.css b/css/bts_timesheet.css index a7588f9..b7fd08b 100644 --- a/css/bts_timesheet.css +++ b/css/bts_timesheet.css @@ -240,7 +240,7 @@ div.peopleitem label:hover :checked + .card { background-color: white; box-shadow: inset 0px 0px 2px dotted white; border-radius: 100px; - background-image: url(http://acaresydney.com.au/wp-content/uploads/2019/06/xero.png); + background-image: url(../img/xero.png); background-size: 40px; box-shadow: 0px 0px 10px white; } @@ -264,7 +264,7 @@ div.peopleitem label:hover :checked + .card { background-color: white; box-shadow: inset 0px 0px 2px black; border-radius: 100px; - background-image: url(http://acaresydney.com.au/wp-content/uploads/2019/06/wnet.png); + background-image: url(../img/wnet.png); background-size: 40px; box-shadow: 0px 0px 10px white; } @@ -278,7 +278,7 @@ div.peopleitem label:hover :checked + .card { background-color: white; box-shadow: inset 0px 0px 2px black; border-radius: 100px; - background-image: url(http://acaresydney.com.au/wp-content/uploads/2019/08/csv.png); + background-image: url(../img/csv.png); background-size: 40px; box-shadow: 0px 0px 10px white; } diff --git a/css/xeroc.css b/css/xeroc.css new file mode 100644 index 0000000..fc1b851 --- /dev/null +++ b/css/xeroc.css @@ -0,0 +1,107 @@ +@CHARSET "UTF-8"; + +.container{ + width:100%; +} + +#cstart, #cfinish, #paydate{ + color: black; + font-weight:900; + width:100%; + background-color: #EDEDED; + border:none; +} + +.hidden{ + display:none; +} + +td.sync_detail{ + padding:0px; +} + +table.hours{ + margin-bottom:0px; +} + +table.blueTable { + border: 1px solid #1C6EA4; + background-color: #EEEEEE; + width: 100%; + text-align: left; + border-collapse: collapse; +} +table.blueTable td, table.blueTable th { + border: 1px solid #AAAAAA; + padding: 3px 2px; +} +table.blueTable tbody td { + font-size: 13px; +} +table.blueTable tr:nth-child(even) { + background: #D0E4F5; +} +table.blueTable thead { + background: #1C6EA4; + background: -moz-linear-gradient(top, #5592bb 0%, #327cad 66%, #1C6EA4 100%); + background: -webkit-linear-gradient(top, #5592bb 0%, #327cad 66%, #1C6EA4 100%); + background: linear-gradient(to bottom, #5592bb 0%, #327cad 66%, #1C6EA4 100%); + border-bottom: 2px solid #444444; +} +table.blueTable thead th { + font-size: 15px; + font-weight: bold; + color: #FFFFFF; + border-left: 2px solid #D0E4F5; +} +table.blueTable thead th:first-child { + border-left: none; +} + +table.blueTable tfoot { + font-size: 14px; + font-weight: bold; + color: #FFFFFF; + background: #D0E4F5; + background: -moz-linear-gradient(top, #dcebf7 0%, #d4e6f6 66%, #D0E4F5 100%); + background: -webkit-linear-gradient(top, #dcebf7 0%, #d4e6f6 66%, #D0E4F5 100%); + background: linear-gradient(to bottom, #dcebf7 0%, #d4e6f6 66%, #D0E4F5 100%); + border-top: 2px solid #444444; +} +table.blueTable tfoot td { + font-size: 14px; +} +table.blueTable tfoot .links { + text-align: right; +} +table.blueTable tfoot .links a{ + display: inline-block; + background: #1C6EA4; + color: #FFFFFF; + padding: 2px 8px; + border-radius: 5px; +} + + +.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 */ + background: #ebe9f9; /* Old browsers */ + background: -moz-linear-gradient(top, #ebe9f9 0%, #d8d0ef 50%, #cec7ec 51%, #c1bfea 100%); /* FF3.6-15 */ + background: -webkit-linear-gradient(top, #ebe9f9 0%,#d8d0ef 50%,#cec7ec 51%,#c1bfea 100%); /* Chrome10-25,Safari5.1-6 */ + background: linear-gradient(to bottom, #ebe9f9 0%,#d8d0ef 50%,#cec7ec 51%,#c1bfea 100%); /* W3C, IE10+, FF16+, Chrome26+, Opera12+, Safari7+ */ + filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#ebe9f9', endColorstr='#c1bfea',GradientType=0 ); /* IE6-9 */ +} + + +.week2color { + color: white; + + /* Permalink - use to edit and share this gradient: https://colorzilla.com/gradient-editor/#627d4d+0,1f3b08+100;Olive+3D */ + background: #627d4d; /* Old browsers */ + background: -moz-linear-gradient(top, #627d4d 0%, #1f3b08 100%); /* FF3.6-15 */ + 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 diff --git a/html/bts_staff_hours_template.html b/html/bts_staff_hours_template.html new file mode 100644 index 0000000..46a8f67 --- /dev/null +++ b/html/bts_staff_hours_template.html @@ -0,0 +1,68 @@ +{{#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}}
+ + {{Xero_Status}} + +{{/lines}} \ No newline at end of file diff --git a/img/csv.png b/img/csv.png new file mode 100644 index 0000000..ccfbbda Binary files /dev/null and b/img/csv.png differ diff --git a/img/wnet.png b/img/wnet.png new file mode 100644 index 0000000..f05a8ec Binary files /dev/null and b/img/wnet.png differ diff --git a/img/xero.png b/img/xero.png new file mode 100644 index 0000000..f98d58f Binary files /dev/null and b/img/xero.png differ diff --git a/js/xeroc.js b/js/xeroc.js new file mode 100644 index 0000000..023127b --- /dev/null +++ b/js/xeroc.js @@ -0,0 +1,79 @@ +(function ($) { + $(function () { +/*_____________________________________________*/ + + function test(){ + var temp = $('#bts_staff_hours_template').html(); + var lines = []; + for (var i=1; i<10; i++){ + var data = { + staff_name: 'john', + rate_name : 'some rate name', + staff_id:"abc_"+ i, + }; + data.days={}; + for (var j =1; j<=14; j++){ + data['days_' + j] = j + '/July'; + } + lines.push(data); + } + var html = Mustache.render(temp, {lines:lines}); + $('#staff').append(html); + } + function datebox(){ + $( ".boundary_datepicker" ).datepicker(); + $( ".boundary_datepicker" ).datepicker("option", "dateFormat", "yy-mm-dd"); + } + + function display_hour_lines(response) + { + $('#staff').html(); + var temp = $('#bts_staff_hours_template').html(); + var html = Mustache.render(temp, response); + $('#staff').append(html); + } + + function set_payroll_calendar(cal) + { + $('#cstart').attr('value', cal.start); + $('#cfinish').attr('value', cal.finish); + $('#paydate').attr('value', cal.paydate); + } + + function get_timesheet_from_xero(){ + $.post(bts().ajax_url, { // POST request + _ajax_nonce: bts().nonce, // nonce + action: "get_timesheet_from_xero", // action + sync: false, + }).done(function(response){ + set_payroll_calendar(response.payroll_calendar); + console.log("%o", response); + display_hour_lines(response); + }).fail(function(){ + console.warn('failed'); + }).always(function(){ + console.log('completed'); + }); + } + + function sync_timesheet_from_xero(){ + $.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'); + }).always(function(){ + console.log('completed'); + }); + } + $('#sync_timesheet').click(function(){ + sync_timesheet_from_xero(); + }); +/*_____________________________________________*/ + }); +})(jQuery); \ No newline at end of file diff --git a/ts.php b/ts.php index 1f25f57..d19f433 100644 --- a/ts.php +++ b/ts.php @@ -48,6 +48,7 @@ class AcareOffice{ add_shortcode( 'bts_staff_job_summary', array($this, 'bts_staff_job_summary')); 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')); //user profile page @@ -76,6 +77,7 @@ class AcareOffice{ add_action('wp_ajax_client_ack_job', array($this,'client_ack_job' )); 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' )); // hook add_rewrite_rules function into rewrite_rules_array add_filter('rewrite_rules_array', array($this,'my_add_rewrite_rules')); @@ -363,6 +365,7 @@ class AcareOffice{ $this->register_timesheet_js_css(); $this->register_task_js_css(); $this->register_feedback_card_js_css(); + $this->register_xeroc_js_css(); } private function register_bts_js() { @@ -449,6 +452,21 @@ class AcareOffice{ ) ); } + private function register_xeroc_js_css(){ + global $pagename; + if ($pagename != 'xeroc'){ + return; + } + 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')); + + global $wp_scripts; + wp_enqueue_script('jquery-ui-datepicker'); + $url = plugins_url('jquery-ui-1.11.4.theme/jquery-ui.min.css', __FILE__); + wp_enqueue_style('jquery-ui-smoothness', $url, false, null); + } + public function sync_users() { //dummy sync @@ -482,6 +500,16 @@ class AcareOffice{ return; } + public function produce_invoice($args = array(), $assoc_args = array()) + { + $users = get_users(array('role' => 'client')); + foreach ($users as $u) + { + $pay = get_user_meta($u->id, 'payment',true); + echo sprintf("%s: %s\n", $u->display_name, $pay); + } + } + private function send_email_with_job_link($staff, $start, $finish) { $message = file_get_contents(plugin_dir_path(__FILE__) . "/html/email_job.html"); @@ -522,6 +550,15 @@ class AcareOffice{ return $result; } + private function get_rate_name_by_id($id) + { + $options = get_option('bts_payitem_earnings_rate'); + foreach($options as $o){ + if ( $o['EarningsRateID'] == $id ) + return sprintf("$%3.2f-%s", $o['RatePerUnit'], $o['Name']); + } + } + public function bts_select_staff($attr){ $result = "