| @@ -0,0 +1,214 @@ | |||
| <?php | |||
| namespace Biukop; | |||
| class TimeSheet{ | |||
| private $xero ; | |||
| private $start_date; | |||
| private $end_date; | |||
| private $remote_timesheets; | |||
| private $local_timesheets =[]; | |||
| private $buddy_timesheets =[]; | |||
| private $warning_timesheets =[]; | |||
| public function __construct($xero, $end_date){ | |||
| $this->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(); | |||
| } | |||
| } | |||
| @@ -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; | |||
| } | |||
| } | |||
| @@ -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; | |||
| } | |||
| @@ -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 */ | |||
| } | |||
| @@ -0,0 +1,68 @@ | |||
| {{#lines}} | |||
| <tr> | |||
| <td>{{staff_name}}</td> | |||
| <td>{{rate_name}}</td> | |||
| <td class=sync_detail> | |||
| <table class='hours'> | |||
| <thead id="p{{staff_id}}"> | |||
| <th>{{days_1}}</th> | |||
| <th>{{days_2}}</th> | |||
| <th>{{days_3}}</th> | |||
| <th>{{days_4}}</th> | |||
| <th>{{days_5}}</th> | |||
| <th>{{days_6}}</th> | |||
| <th>{{days_7}}</th> | |||
| <th>{{days_8}}</th> | |||
| <th>{{days_9}}</th> | |||
| <th>{{days_10}}</th> | |||
| <th>{{days_11}}</th> | |||
| <th>{{days_12}}</th> | |||
| <th>{{days_13}}</th> | |||
| <th>{{days_14}}</th> | |||
| </thead> | |||
| <tbody> | |||
| <tr> | |||
| <td class='local week1color' id='a_0'>{{local_1}}</td> | |||
| <td class='local week1color' id='a_1'>{{local_2}}</td> | |||
| <td class='local week1color' id='a_2'>{{local_3}}</td> | |||
| <td class='local week1color' id='a_3'>{{local_4}}</td> | |||
| <td class='local week1color' id='a_4'>{{local_5}}</td> | |||
| <td class='local week1color' id='a_5'>{{local_6}}</td> | |||
| <td class='local week1color' id='a_6'>{{local_7}}</td> | |||
| <td class='local week2color' id='a_7'>{{local_8}}</td> | |||
| <td class='local week2color' id='a_8'>{{local_9}}</td> | |||
| <td class='local week2color' id='a_9'>{{local_10}}</td> | |||
| <td class='local week2color' id='a_10'>{{local_11}}</td> | |||
| <td class='local week2color' id='a_11'>{{local_12}}</td> | |||
| <td class='local week2color' id='a_12'>{{local_13}}</td> | |||
| <td class='local week2color' id='a_13'>{{local_14}}</td> | |||
| </tr> | |||
| <tr> | |||
| <td colspan="14"> | |||
| <div style="text-align: center">To Xero | |||
| <span class="ticon ticon-arrow-down"></span> | |||
| </div> | |||
| </td> | |||
| </tr> | |||
| <tr> | |||
| <td class='xero week1color' id='b_0'>{{xero_1}}</td> | |||
| <td class='xero week1color' id='b_1'>{{xero_2}}</td> | |||
| <td class='xero week1color' id='b_2'>{{xero_3}}</td> | |||
| <td class='xero week1color' id='b_3'>{{xero_4}}</td> | |||
| <td class='xero week1color' id='b_4'>{{xero_5}}</td> | |||
| <td class='xero week1color' id='b_5'>{{xero_6}}</td> | |||
| <td class='xero week1color' id='b_6'>{{xero_7}}</td> | |||
| <td class='xero week2color' id='b_7'>{{xero_8}}</td> | |||
| <td class='xero week2color' id='b_8'>{{xero_9}}</td> | |||
| <td class='xero week2color' id='b_9'>{{xero_10}}</td> | |||
| <td class='xero week2color' id='b_10'>{{xero_11}}</td> | |||
| <td class='xero week2color' id='b_11'>{{xero_12}}</td> | |||
| <td class='xero week2color' id='b_12'>{{xero_13}}</td> | |||
| <td class='xero week2color' id='b_13'>{{xero_14}}</td> | |||
| </tr> | |||
| </tbody> | |||
| </table> | |||
| </td> | |||
| <td style="vertical-align:bottom;">{{Xero_Status}}</td> | |||
| </tr> | |||
| {{/lines}} | |||
| @@ -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); | |||
| @@ -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 = "<select> \n"; | |||
| $staff = $this->get_people_by_role('staff'); | |||
| @@ -569,6 +606,10 @@ class AcareOffice{ | |||
| { | |||
| return file_get_contents(plugin_dir_path(__FILE__) . "/html/timesheet.html"); | |||
| } | |||
| public function bts_staff_hours_template($attr){ | |||
| return $this->template('bts_staff_hours_template', 'bts_staff_hours_template.html'); | |||
| } | |||
| //generate template based on html file | |||
| private function template($id, $file) | |||
| { | |||
| @@ -772,7 +813,7 @@ class AcareOffice{ | |||
| $u = get_user_by('login', $staff); | |||
| if ($this->is_staff($u)){ | |||
| $n = new UserJob($staff); | |||
| $resp = $n->list_jobs("$start 00:00:00", "$finish 23:59:59"); | |||
| $resp = $n->list_jobs_by_staff("$start 00:00:00", "$finish 23:59:59"); | |||
| if ($resp['status']=='success' && $resp['job_count'] >0 ){ | |||
| $msg = sprintf("Email to <strong>%s</strong> (with job=%d) \n", $u->user_email, $resp['job_count']); | |||
| $this->send_email_with_job_link($u, $start, $finish); | |||
| @@ -953,13 +994,179 @@ class AcareOffice{ | |||
| wp_send_json($response); | |||
| } | |||
| public function get_timesheet_from_xero() | |||
| { | |||
| check_ajax_referer('acaresydney'); | |||
| //check if we need sync | |||
| $sync = $_POST['sync']; | |||
| if ($sync != "true") | |||
| $sync = false; | |||
| else | |||
| $sync = true; | |||
| //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); | |||
| $local_ts = $this->create_timesheet_from_db($start, $finish); | |||
| if ($sync){ | |||
| $xx->set_local_timesheet($local_ts); | |||
| $xx->save_to_xero(); | |||
| } | |||
| $days=[]; | |||
| $d = new \DateTime($start); | |||
| for ($i=1; $i<=14; $i++){ | |||
| $days["days_$i"] = $d->format("d/F"); | |||
| $d->modify("+1 day"); | |||
| } | |||
| $lines=[]; | |||
| foreach ($local_ts as $staff_login => $details) | |||
| { | |||
| $item = array( | |||
| 'staff_name' => $this->get_user_name_by_login ($staff_login), | |||
| 'staff_id' => $staff_login, | |||
| 'Xero_Status' => 'Empty', | |||
| ); | |||
| //for local | |||
| foreach($details as $rate => $hours){ | |||
| $item['rate_name'] = $this->get_rate_name_by_id($rate); | |||
| for ($i=1; $i<=14; $i++) | |||
| { | |||
| $item["local_$i"] = $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) | |||
| { | |||
| if ( $rl->getEarningsRateID() == $rate){ | |||
| for ($i=1; $i<=14; $i++) | |||
| { | |||
| $item["xero_$i"] = $rl->getNumberOfUnits()[$i-1]; | |||
| } | |||
| break;//we found it | |||
| } | |||
| } | |||
| } | |||
| $item = array_merge($item, $days); | |||
| $lines[]=$item; | |||
| } | |||
| $ts = json_decode(file_get_contents(dirname(__FILE__) . "/sample/timesheets.json")); | |||
| $response['ts'] = $ts; | |||
| $response['lines'] = $lines; | |||
| wp_send_json($response); | |||
| } | |||
| static public function get_user_name_by_login($login) | |||
| { | |||
| $user = get_user_by('login', $login); | |||
| if ($user->ID !=0 ) | |||
| return $user->display_name; | |||
| else | |||
| return "Invalid Name"; | |||
| } | |||
| public function create_timesheet_from_db($start, $finish){ | |||
| $results = []; | |||
| $sql = "SELECT * from $this->table_name WHERE start>='$start 00:00:00' and start<='$finish 23:59:59'"; | |||
| $rows = $this->db->get_results($sql); | |||
| foreach ($rows as $r){ | |||
| if (!array_key_exists($r->staff, $results)){ | |||
| $results[$r->staff] = []; | |||
| } | |||
| if (!array_key_exists($r->rate, $results[$r->staff])){ | |||
| $results[$r->staff][$r->rate] = []; | |||
| for ($i=0; $i<14; $i++){ | |||
| $results[$r->staff][$r->rate][$i] = 0; //14 days init to 0; | |||
| } | |||
| } | |||
| $idx = $this->convert_date_to_idx($r->start, $start, $finish); | |||
| if ($idx >=0 && $idx <=13){ | |||
| $hours = $this->get_job_hours($r->start, $r->finish); | |||
| $results[$r->staff][$r->rate][$idx] += $hours; | |||
| //$results[$r->staff][$r->id] = $hours; | |||
| //$results[$r->staff][$r->id . '_index'] = $idx; | |||
| }else{ | |||
| $msg = sprintf("ACARE_TS_ERR: found invalid job index for job id=%d, on %s, idx is %d, start=%s, finish=%s\n", | |||
| $r->id, date('Y-m-d'), $idx, $start, $finish); | |||
| $this->log($msg); | |||
| } | |||
| } | |||
| //wp_send_json($results); | |||
| return $results; | |||
| } | |||
| //convert date (no time) to index, 0 day is $start, 13day is finish, -1 is not found | |||
| public function convert_date_to_idx($date, $start, $finish) | |||
| {//start, finish format must be yyyy-mm-dd | |||
| $idx = -1; | |||
| $cur = new \DateTime($date); | |||
| $cur->setTime(0,0,0);//clear time; | |||
| $s = new \DateTime($start); | |||
| $s->setTime(0,0,0); | |||
| $f = new \DateTime($finish); | |||
| $f->setTime(0,0,0); | |||
| if ( $s <= $cur && $cur <=$f ){ | |||
| $datediff = date_diff($s,$cur); | |||
| $idx = $datediff->days; | |||
| } | |||
| return $idx; | |||
| } | |||
| private function get_job_hours($start, $finish) | |||
| { | |||
| $hours = 0; | |||
| $s = strtotime($start); | |||
| $f = strtotime($finish); | |||
| $diff = $f- $s; | |||
| $hours = ($diff * 1.0 / 3600); //can be float; | |||
| return $hours; | |||
| } | |||
| public function feedback_url() | |||
| { | |||
| $users = get_users(array('role'=>'client')); | |||
| foreach($users as $u){ | |||
| echo sprintf("%s:\t https://acaresydney.com.au/feedback_card/%s/\n", $u->display_name, $u->user_login); | |||
| echo sprintf("Hi %s:\n\nPlease rate our service, https://acaresydney.com.au/feedback_card/%s/\n\nAcareSydney\n\n", $u->display_name, $u->user_login); | |||
| } | |||
| } | |||
| static public function log($msg){ | |||
| openlog("ACARE_TS", LOG_PID | LOG_PERROR, LOG_SYSLOG); | |||
| //something wrong, we dont touch it, but give some error here | |||
| syslog(LOG_WARNING, $msg); | |||
| closelog(); | |||
| } | |||
| } | |||
| $bb = new AcareOffice(); | |||
| @@ -968,7 +1175,8 @@ if ( defined( 'WP_CLI' ) && WP_CLI ) { | |||
| \WP_CLI::add_command( 'sync_users', array($bb, 'sync_user_cli')); | |||
| \WP_CLI::add_command( 'email_jobs', array($bb, 'email_jobs')); | |||
| \WP_CLI::add_command( 'feedback_url', array($bb, 'feedback_url')); | |||
| \WP_CLI::add_command( 'produce_invoice', array($bb, 'produce_invoice')); | |||
| } | |||
| //$bb->class_loader(); | |||
| //$bb->list_job_by_staff(); | |||
| //$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"); | |||