removed xeroauth1 files missing sync_wp add clients and add employeesmaster
| <?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, | |||||
| // )); | |||||
| } | |||||
| public function refresh_remote(){ | |||||
| $this->get_remote_timesheets(); | |||||
| } | |||||
| 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){ | |||||
| $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'); | |||||
| $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"); | |||||
| 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{ | |||||
| $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 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) | |||||
| { | |||||
| $staff_login = $ts->getEmployeeID(); | |||||
| if($staff_login == $id){ | |||||
| return $ts->getTimesheetID(); | |||||
| } | |||||
| } | |||||
| return NULL; | |||||
| } | |||||
| public function warning_timesheet() | |||||
| { | |||||
| return $this->warning_timesheets; | |||||
| } | |||||
| public function approve_all(){ | |||||
| $to_save=[]; | |||||
| foreach ( $this->remote_timesheets as $t){ | |||||
| if($t->getStatus() == 'DRAFT'){ | |||||
| $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(); | |||||
| } | |||||
| } |
| <?php | |||||
| namespace Biukop; | |||||
| use \XeroPHP\Application\PrivateApplication; | |||||
| use \XeroPHP\Remote\Exception\RateLimitExceededException; | |||||
| use \XeroPHP\Remote\Exception\NotFoundException; | |||||
| class XeroOauth1 { | |||||
| private $xero; | |||||
| private $clientgroup="48646f3d-cf5e-4fea-8c8b-5812bd540e1b"; | |||||
| private $minimum_sync_interval_in_seconds = 600; | |||||
| private function default_config(){ | |||||
| $config = array( | |||||
| 'oauth' => [ | |||||
| 'callback' => 'http://acaresydney.com.au/', | |||||
| 'consumer_key' => 'G6AJIRWTH0X3C5SVS3ETZXNFCMDNGG', | |||||
| 'consumer_secret' => 'LP0PTSBCJLBB4CGYYKOHDXYF2NWXWD', | |||||
| 'rsa_private_key' => 'file://' . dirname(__FILE__) . '/keys/privatekey.pem', | |||||
| ], | |||||
| ); | |||||
| return $config; | |||||
| } | |||||
| private function office_config(){ | |||||
| $office_config = [ | |||||
| 'xero' => [ | |||||
| // 'payroll_version' =>'1.9', | |||||
| ], | |||||
| 'curl' => [ | |||||
| CURLOPT_USERAGENT =>'AcareSydneyWebOffice', | |||||
| ], | |||||
| 'oauth' => [ | |||||
| 'callback' => 'http://acaresydney.com.au/', | |||||
| 'consumer_key' => 'JE4LWKCJ6NHED30RFZWCT7WQYTS8JD', | |||||
| 'consumer_secret' => 'JXVZAZWKGM7MLUZSVIMK7ZSJE9GNYQ', | |||||
| 'rsa_private_key' => 'file://' . dirname(__FILE__) . '/keys/privatekey.pem', | |||||
| ], | |||||
| ]; | |||||
| return $office_config; | |||||
| } | |||||
| public function __construct(){ | |||||
| $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); | |||||
| $contacts = $cg->getContacts(); | |||||
| return $contacts; | |||||
| } | |||||
| public function getContact($id){ | |||||
| $user = $this->xero->loadByGUID("Accounting\\Contact",$id); | |||||
| return $user; | |||||
| } | |||||
| public function getEmployees(){ | |||||
| $employees = $this->xero->load("PayrollAU\\Employee") | |||||
| ->where('EmployeeGroupName="Web-Employee"') | |||||
| ->execute(); | |||||
| return $employees; | |||||
| } | |||||
| public function getEmployee($id){ | |||||
| $employee = $this->xero->loadByGUID("PayrollAU\\Employee",$id); | |||||
| return $employee; | |||||
| } | |||||
| // | |||||
| //sync users to wordpress system | |||||
| //does not work for too many users or employees | |||||
| public function sync_users($mininterval, $employeeonly, $clientsonly){ | |||||
| $this->usage(); | |||||
| $this->minimum_sync_interval_in_seconds = $mininterval; | |||||
| $msg="Sync users with minimum interval set to $mininterval \n"; | |||||
| $this->logConsole($msg); | |||||
| try{ | |||||
| if ($clientsonly){ | |||||
| $this->sync_clients(); | |||||
| }else if ($employeeonly){ | |||||
| $this->sync_employees(); | |||||
| }else{ | |||||
| $this->sync_clients(); | |||||
| $this->sync_employees(); | |||||
| } | |||||
| }catch(RateLimitExceededException $e){ | |||||
| $msg= "XeroOauth1 API rate limit exceeded, please try again later, existing sync within 600 seconds will by passed automatically\n"; | |||||
| $this->logConsole($msg); | |||||
| }catch(NotFoundException $e){ | |||||
| $msg= "XeroOauth1 API resource not found rate limit exceeded, please try again later, existing sync within 600 seconds will by passed automatically\n"; | |||||
| $this->logConsole($msg); | |||||
| } | |||||
| } | |||||
| private function sync_clients($add_new_only = false){ | |||||
| $contacts = $this->getClients($this->clientgroup); | |||||
| //add new first; | |||||
| $to_update = []; | |||||
| foreach ($contacts as $c){ | |||||
| $login = $c->getContactID(); | |||||
| $user = get_user_by('login', $login); | |||||
| if ($user === false){ | |||||
| $this->add_new_contact($c); | |||||
| }else{ | |||||
| $to_update[] = $c; // to update users later; | |||||
| } | |||||
| } | |||||
| if ($add_new_only) | |||||
| return; | |||||
| //sync existing | |||||
| foreach ($to_update as $c){ | |||||
| $msg = sprintf("SYNC Client name=[%s] {%s} \n", $c->getName(), $c->getContactID()); | |||||
| $this->logConsole($msg); | |||||
| $this->update_existing_contact($c); | |||||
| } | |||||
| } | |||||
| private function sync_employees($add_new_only = false){ | |||||
| $employees = $this->getEmployees(); | |||||
| //add new staff | |||||
| foreach ( $employees as $e){ | |||||
| $login = $e->getEmployeeID(); | |||||
| $user = get_user_by('login', $login); | |||||
| if ($user === false){ | |||||
| $this->add_new_staff($e); | |||||
| }else{ | |||||
| $to_update[] = $e; // to update exiting users later; | |||||
| } | |||||
| } | |||||
| if ($add_new_only) | |||||
| return; | |||||
| foreach ( $to_update as $e){ | |||||
| $msg = sprintf("SYNC employee name=[%s %s] {%s} \n", $e->getFirstName(), $e->getLastName(), $e->getEmployeeID()); | |||||
| $this->logConsole($msg); | |||||
| $this->update_existing_staff($e); | |||||
| } | |||||
| } | |||||
| private function add_new_contact($contact){ | |||||
| $login = $contact->getContactID(); | |||||
| $user = get_user_by('login', $login); | |||||
| if ($user === false){ | |||||
| $msg = sprintf("ADD Client name=[%s] {%s} \n", $contact->getName(), $contact->getContactID()); | |||||
| $this->logConsole($msg); | |||||
| $xero_contact = $this->getContact($login); | |||||
| $args = $this->xero_contact_profile($xero_contact); | |||||
| $id = wp_insert_user($args); | |||||
| if (! $id instanceof \WP_Error){ | |||||
| $user = get_user_by('ID', $id); | |||||
| update_user_meta($user->ID, 'address', $args['address']); | |||||
| update_user_meta($user->ID, 'account', $args['account']); | |||||
| }else{ | |||||
| $msg = "==(Add Client failed)=="; | |||||
| $this->logConsole($msg); | |||||
| $msg = sprintf("ADD Client name=[%s] {%s} Failed\n", $contact->getName(), $contact->getContactID()); | |||||
| error_log("ACARE add client failed: $msg"); | |||||
| } | |||||
| } | |||||
| } | |||||
| private function update_existing_contact($contact){ | |||||
| $login = $contact->getContactID(); | |||||
| $user = get_user_by('login', $login); | |||||
| if ($user !== false) | |||||
| {//update user - must be existing user; | |||||
| if ($this->is_too_close_to_sync($user)){ | |||||
| return; | |||||
| } | |||||
| $xero_contact = $this->getContact($login); | |||||
| $args = $this->xero_contact_profile($xero_contact); | |||||
| $args['ID'] = $user->ID; | |||||
| unset($args['user_pass']); //we don't change password | |||||
| wp_update_user($args); | |||||
| update_user_meta($user->ID, 'address', $args['address']); | |||||
| update_user_meta($user->ID, 'account', $args['account']); | |||||
| } | |||||
| $this->mark_updated($user->ID); | |||||
| } | |||||
| private function xero_contact_profile($c){ | |||||
| $args = [ | |||||
| 'user_login' => $username = $c->getContactID(), | |||||
| 'user_pass' => md5(uniqid(rand() + time(), true)), | |||||
| 'display_name' =>$c->getName(), | |||||
| 'user_email' => $c->getEmailAddress(), | |||||
| 'first_name' => $c->getFirstName(), | |||||
| 'last_name' => $c->getLastName(), | |||||
| 'nickname' => $c->getName(), | |||||
| 'account' => $c->getAccountNumber(), | |||||
| 'address'=> $this->get_post_address($c), | |||||
| 'role' => 'client', | |||||
| ]; | |||||
| return $args; | |||||
| } | |||||
| private function get_post_address($client){ | |||||
| $result = ""; | |||||
| $addr = $this->get_client_address_by_type($client, 'POBOX'); | |||||
| if ( $addr != false){ | |||||
| if ($addr->getAddressLine1() != ""){ | |||||
| $result .= $addr->getAddressLine1() . ";"; | |||||
| } | |||||
| if ($addr->getAddressLine2() != ""){ | |||||
| $result .= $addr->getAddressLine2() . ";"; | |||||
| } | |||||
| if ($addr->getAddressLine3() != ""){ | |||||
| $result .= $addr->getAddressLine3() . ";"; | |||||
| } | |||||
| if ($addr->getAddressLine4() != ""){ | |||||
| $result .= $addr->getAddressLine4() . ";"; | |||||
| } | |||||
| if ($addr->getCity() != ""){ | |||||
| $result .= $addr->getCity() . ";"; | |||||
| } | |||||
| if ($addr->getPostalCode() != ""){ | |||||
| $result .= $addr->getPostalCode() . ";"; | |||||
| } | |||||
| } | |||||
| echo "result for client is " . $result . "\n"; | |||||
| return $result; | |||||
| } | |||||
| private function get_client_address_by_type($client, $t){ | |||||
| $addr = false; | |||||
| foreach( $client->getAddresses() as $a){ | |||||
| if( $a->getAddressType() == $t){ | |||||
| $addr = $a; | |||||
| break; | |||||
| } | |||||
| } | |||||
| return $addr; | |||||
| } | |||||
| private function add_new_staff($employee) | |||||
| { | |||||
| $login = $employee->getEmployeeID(); | |||||
| $user = get_user_by('login', $login); | |||||
| if ($user === false){ | |||||
| $msg = sprintf("ADD employee name=[%s %s] {%s} \n", | |||||
| $employee->getFirstName(), | |||||
| $employee->getLastName(), | |||||
| $employee->getEmployeeID()); | |||||
| $this->logConsole($msg); | |||||
| $xero_employee = $this->getEmployee($login); | |||||
| $args = $this->xero_employee_profile($xero_employee); | |||||
| $id = wp_insert_user($args); | |||||
| if (! $id instanceof \WP_Error){ | |||||
| $user = get_user_by('ID', $id); | |||||
| update_user_meta($user->ID, 'mobile', $args['mobile']); | |||||
| update_user_meta($user->ID, 'address', $args['address']); | |||||
| }else{ | |||||
| $msg = "==(Add staff failed)=="; | |||||
| $this->logConsole($msg); | |||||
| $msg = sprintf("ADD employee name=[%s %s] {%s} Failed\n", | |||||
| $employee->getFirstName(), | |||||
| $employee->getLastName(), | |||||
| $employee->getEmployeeID()); | |||||
| error_log("ACARE add employee failed: $msg"); | |||||
| } | |||||
| } | |||||
| } | |||||
| private function update_existing_staff($employee) | |||||
| { | |||||
| $login = $employee->getEmployeeID(); | |||||
| $user = get_user_by('login', $login); | |||||
| if ($this->is_too_close_to_sync($user)){ | |||||
| return; | |||||
| } | |||||
| if ($user != false) { | |||||
| $xero_employee = $this->getEmployee($login); | |||||
| $args = $this->xero_employee_profile($xero_employee); | |||||
| $args['ID'] = $user->ID; | |||||
| unset($args['user_pass']); | |||||
| wp_update_user($args); | |||||
| update_user_meta($user->ID, 'mobile', $args['mobile']); | |||||
| update_user_meta($user->ID, 'address', $args['address']); | |||||
| } | |||||
| $this->mark_updated($user->ID); | |||||
| } | |||||
| private function xero_employee_profile($e){ | |||||
| $args = [ | |||||
| 'user_login' => $username = $e->getEmployeeID(), | |||||
| 'user_pass' => md5(uniqid(rand() + time(), true)), | |||||
| 'display_name' =>$e->getFirstName() . " " . $e->getLastName(), | |||||
| 'user_email' => $e->getEmail(), | |||||
| 'first_name' => $e->getFirstName(), | |||||
| 'last_name' => $e->getLastName(), | |||||
| 'nickname' => $e->getFirstName(), | |||||
| 'mobile' => $e->getMobile(), | |||||
| 'address'=> $this->get_employee_address($e), | |||||
| 'role' => 'staff', | |||||
| ]; | |||||
| return $args; | |||||
| } | |||||
| private function get_employee_address($e){ | |||||
| // "HomeAddress": { | |||||
| // "AddressLine1": "16 Quist Avenue", | |||||
| // "City": "Lurnea", | |||||
| // "Region": "NSW", | |||||
| // "PostalCode": "2170", | |||||
| // "Country": "AUSTRALIA" | |||||
| // }, | |||||
| $addr = ""; | |||||
| $home = $e->getHomeAddress(); | |||||
| $addr .= $home->getAddressLine1() .","; | |||||
| $addr .= $home->getAddressLine2() .","; | |||||
| $addr .= $home->getCity() . ","; | |||||
| $addr .= $home->getRegion() . " "; | |||||
| $addr .= $home->getCountry() ." "; | |||||
| $addr .= $home->getPostalCode(); | |||||
| return $addr; | |||||
| } | |||||
| private function mark_updated($userid){ | |||||
| update_user_meta($userid, 'lastsync', time()); | |||||
| } | |||||
| private function get_last_sync($userid){ | |||||
| $lastsync = get_user_meta($userid, 'lastsync', true); | |||||
| return (int)($lastsync); | |||||
| } | |||||
| private function is_too_close_to_sync($user){ | |||||
| $userid = $user->ID; | |||||
| $lastsync = $this->get_last_sync($user->ID); | |||||
| $now = time(); | |||||
| $diff = $now - (int) $lastsync; | |||||
| if ($diff < $this->minimum_sync_interval_in_seconds){ | |||||
| $msg = sprintf("\tSKIP userid(%d),login=%s,display_name=%s,(lastsync=%d secs ago, mininterval=%d) \n", | |||||
| $user->ID, $user->user_login, $user->display_name, $diff, $this->minimum_sync_interval_in_seconds); | |||||
| $this->logConsole($msg); | |||||
| return true; | |||||
| } | |||||
| return false; | |||||
| } | |||||
| private function logConsole($str){ | |||||
| //if is commandline | |||||
| if ( defined( 'WP_CLI' ) && WP_CLI ) { | |||||
| echo $str; | |||||
| } | |||||
| } | |||||
| private function usage(){ | |||||
| $msg = "_____________________________________________\n"; | |||||
| $msg .= "run this command at public_html/, where wp-config.php exist \n"; | |||||
| $msg .= "wp sync_users --mininterval=6000 \n"; | |||||
| $msg .= "6000 means those users synced within 6000 seconds will be bypassed \n"; | |||||
| $msg .= "to sync everything \n"; | |||||
| $msg .= "wp sync_users --mininterval=0 [--clientsonly] [--employeeonly]\n"; | |||||
| $msg .= "but it may hit XERO rate limit, 60call/sec, 5000/day \n"; | |||||
| $msg .= "---------------------------------------------\n"; | |||||
| $this->logConsole($msg); | |||||
| } | |||||
| /* sync payitems to wp options */ | |||||
| public function init_wp(){ | |||||
| try{ | |||||
| $this->sync_payitem(); | |||||
| $this->add_new_client(); | |||||
| $this->add_new_employee(); | |||||
| $this->sync_payroll_calendar(); | |||||
| }catch(\XeroPHP\Remote\Exception $e){ | |||||
| } | |||||
| } | |||||
| private function add_new_client(){ | |||||
| if ($this->too_close_to_add_client()){ | |||||
| return; | |||||
| } | |||||
| update_option('bts_add_client_last_sync', time()); | |||||
| $this->sync_clients(true);//add new only; | |||||
| } | |||||
| private function add_new_employee(){ | |||||
| if ($this->too_close_to_add_employee()){ | |||||
| return; | |||||
| } | |||||
| update_option('bts_add_employee_last_sync', time()); | |||||
| $this->sync_employees(true);//add new only; | |||||
| } | |||||
| private function sync_payroll_calendar() | |||||
| { | |||||
| if ($this->too_close_to_sync_payroll_calendar()){ | |||||
| return; | |||||
| } | |||||
| update_option('bts_pay_roll_calendar_last_sync', time()); | |||||
| $pc = $this->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'); | |||||
| $calendar["start"] = $start; | |||||
| $calendar["finish"] = $finish; | |||||
| $calendar["paydate"] = $paydate; | |||||
| update_option('bts_pay_roll_calendar', $calendar); | |||||
| } | |||||
| private function sync_payitem(){ | |||||
| if ($this->too_close_to_sync_payitem()){ | |||||
| return; | |||||
| } | |||||
| $payitems = $this->xero->load('PayrollAU\\PayItem')->execute(); | |||||
| $payitem_options = array(); | |||||
| foreach ($payitems[0]->getEarningsRates() as $e){ | |||||
| // "EarningsRateID": "34e17d08-237a-4ae2-8115-375d1ff8a9ed", | |||||
| // "Name": "Overtime Hours (exempt from super)", | |||||
| // "EarningsType": "OVERTIMEEARNINGS", | |||||
| // "RateType": "MULTIPLE", | |||||
| // "AccountCode": "477", | |||||
| // "Multiplier": 1.5, | |||||
| // "IsExemptFromTax": true, | |||||
| // "IsExemptFromSuper": true, | |||||
| // "AccrueLeave": false, | |||||
| // "IsReportableAsW1": true, | |||||
| // "UpdatedDateUTC": "2019-03-16T13:18:19+00:00", | |||||
| // "CurrentRecord": false | |||||
| if ($e->getCurrentRecord() == "true"){ | |||||
| $payitem_options[]= array( | |||||
| 'EarningsRateID' => $e->getEarningsRateID(), | |||||
| 'Name'=> $e->getName(), | |||||
| 'EarningsType'=> $e->getEarningstype(), | |||||
| 'RatePerUnit' => $e->getRatePerUnit(), | |||||
| 'RateType' => $e->getRateType(), | |||||
| 'AccountCode' => $e->getAccountCode(), | |||||
| "Multiplier"=> $e->getMultiplier(), | |||||
| "IsExemptFromTax" => $e->getIsExemptFromTax(), | |||||
| "IsExemptFromSuper"=> $e->getIsExemptFromSuper(), | |||||
| "AccrueLeave" => $e->getAccrueLeave(), | |||||
| "TypeOfUnits" => $e->getTypeOfUnits(), | |||||
| "CurrentRecord"=> $e->getCurrentRecord(), | |||||
| ); | |||||
| } | |||||
| } | |||||
| update_option('bts_payitem_earnings_rate', $payitem_options); | |||||
| update_option('bts_payitem_last_sync', time()); | |||||
| } | |||||
| private function too_close_to_sync_payitem(){ | |||||
| $lastsync = get_option('bts_payitem_last_sync', 0); | |||||
| $now = time(); | |||||
| $diff = $now - (int) $lastsync; | |||||
| return $diff < $this->minimum_sync_interval_in_seconds; //default 10 minutes | |||||
| } | |||||
| private function too_close_to_add_employee(){ | |||||
| $lastsync = get_option('bts_add_employee_last_sync', 0); | |||||
| $now = time(); | |||||
| $diff = $now - (int) $lastsync; | |||||
| return $diff < 1.5 * $this->minimum_sync_interval_in_seconds; //default 1.1 * 10 minutes | |||||
| } | |||||
| private function too_close_to_add_client(){ | |||||
| $lastsync = get_option('bts_add_client_last_sync', 0); | |||||
| $now = time(); | |||||
| $diff = $now - (int) $lastsync; | |||||
| return $diff < 2.0 * $this->minimum_sync_interval_in_seconds; //default 1.2 * 10 minutes | |||||
| } | |||||
| private function too_close_to_sync_payroll_calendar(){ | |||||
| $lastsync = get_option('bts_pay_roll_calendar_last_sync', 0); | |||||
| $now = time(); | |||||
| $diff = $now - (int) $lastsync; | |||||
| return $diff < 3.0 * $this->minimum_sync_interval_in_seconds; //default 1.3 * 10 minutes | |||||
| } | |||||
| public function get_payroll_calendar() | |||||
| { | |||||
| $id = "33dc7df5-3060-4d76-b4da-57c20685d77d"; //fortnightly | |||||
| $pc = $this->xero->loadByGUID('PayrollAU\\PayrollCalendar', $id); | |||||
| return $pc; | |||||
| } | |||||
| } |
| class XeroOAuth2 | class XeroOAuth2 | ||||
| { | { | ||||
| private $office; // parent | private $office; // parent | ||||
| private $clientID = '83CC79EEC6A54B4E8C2CA7AD61D1BF69'; | private $clientID = '83CC79EEC6A54B4E8C2CA7AD61D1BF69'; | ||||
| private $clientSecret = 'axgKF-Ri60D89conDFhqZsi1wu7uLdQFGvMpino9nI-nfO3f'; | private $clientSecret = 'axgKF-Ri60D89conDFhqZsi1wu7uLdQFGvMpino9nI-nfO3f'; | ||||
| private $clientContactGroupID="48646f3d-cf5e-4fea-8c8b-5812bd540e1b"; | private $clientContactGroupID="48646f3d-cf5e-4fea-8c8b-5812bd540e1b"; | ||||
| private $shortcodes; | |||||
| public $provider; | public $provider; | ||||
| public $options = [ | public $options = [ | ||||
| 'scope' => ['openid email profile offline_access assets projects accounting.settings accounting.transactions accounting.contacts accounting.journals.read accounting.reports.read accounting.attachments payroll.employees payroll.payruns payroll.payslip payroll.timesheets payroll.settings files'], | |||||
| 'scope1' => [ | |||||
| 'openid', | |||||
| 'email', | |||||
| 'profile', | |||||
| 'offline_access', | |||||
| 'assets', | |||||
| 'projects', | |||||
| 'accounting.settings', | |||||
| 'accounting.transactions', | |||||
| 'accounting.contacts', | |||||
| 'accounting.journals.read', | |||||
| 'accounting.reports.read', | |||||
| 'accounting.attachments', | |||||
| 'payroll.employees', | |||||
| 'payroll.payruns', | |||||
| 'payroll.payslip', | |||||
| 'payroll.timesheets', | |||||
| 'payroll.settings' | |||||
| ] | |||||
| 'scope' => ['openid email profile offline_access assets projects accounting.settings accounting.transactions accounting.contacts accounting.journals.read accounting.reports.read accounting.attachments payroll.employees payroll.payruns payroll.payslip payroll.timesheets payroll.settings files'] | |||||
| ]; | ]; | ||||
| public $storage; | public $storage; | ||||
| public $config; | public $config; | ||||
| public $apiAccountingInstance; //accounting instance | public $apiAccountingInstance; //accounting instance | ||||
| public $apiPayrollInstance; // payroll au instance | public $apiPayrollInstance; // payroll au instance | ||||
| public $xeroTenantId; | public $xeroTenantId; | ||||
| private $shortcodes; | |||||
| public function __construct($office) | public function __construct($office) | ||||
| { | { |
| { | { | ||||
| ini_set('display_errors', 'On'); | ini_set('display_errors', 'On'); | ||||
| $apiInstance = $this->oauth2->get_accounting_instance(); | $apiInstance = $this->oauth2->get_accounting_instance(); | ||||
| $apiResponse = $apiInstance->getOrganisations($this->xeroTenantId); | |||||
| $apiResponse = $apiInstance->getOrganisations($this->oauth2->getTenantId()); | |||||
| return 'Organisation Name: <b> ' . $apiResponse->getOrganisations()[0]->getName() . "</b> "; | return 'Organisation Name: <b> ' . $apiResponse->getOrganisations()[0]->getName() . "</b> "; | ||||
| } | } | ||||
| ini_set('display_errors', 'On'); | ini_set('display_errors', 'On'); | ||||
| $apiInstance = $this->oauth2->get_accounting_instance(); | $apiInstance = $this->oauth2->get_accounting_instance(); | ||||
| $apiResponse = $apiInstance->getContacts($this->xeroTenantId); | |||||
| $apiResponse = $apiInstance->getContacts($this->oauth2->getTenantId()); | |||||
| $contacts = $apiResponse->getContacts(); | $contacts = $apiResponse->getContacts(); | ||||
| $message = "<table> "; | $message = "<table> "; | ||||
| $message .= "<tr> <td> # </td> | $message .= "<tr> <td> # </td> | ||||
| <td> Phone </td> | <td> Phone </td> | ||||
| <td> Mobile </td> | <td> Mobile </td> | ||||
| <td> Start </td> | <td> Start </td> | ||||
| <td> Group </td> | |||||
| </tr> "; | </tr> "; | ||||
| $count = 1; | $count = 1; | ||||
| foreach ($employees as $r){ | foreach ($employees as $r){ | ||||
| "<td> " . $r->getPhone() . "</td> " . | "<td> " . $r->getPhone() . "</td> " . | ||||
| "<td> " . $r->getMobile() . "</td> " . | "<td> " . $r->getMobile() . "</td> " . | ||||
| "<td> " . $r->getStartDateAsDate()->format("M d Y") . "</td> " . | "<td> " . $r->getStartDateAsDate()->format("M d Y") . "</td> " . | ||||
| "<td> " . $r->getEmployeeGroupName() . "</td> " . | |||||
| "</tr>"; | "</tr>"; | ||||
| $count ++; | $count ++; | ||||
| } | } |
| { | { | ||||
| foreach ($this->remote_timesheets as $t){ | foreach ($this->remote_timesheets as $t){ | ||||
| if ( $t->getEmployeeID() == $employee_id && | if ( $t->getEmployeeID() == $employee_id && | ||||
| $t->getStartDateAsDate()->format('Y-m-d') == $start->format('Y-m-d') && | |||||
| $t->getEndDateAsDate()->format('Y-m-d') == $end->format('Y-m-d') ) | |||||
| $t->getStartDateAsDate()->format('Y-m-d') == $start && | |||||
| $t->getEndDateAsDate()->format('Y-m-d') == $end ) | |||||
| { | { | ||||
| return $t; | return $t; | ||||
| } | } |
| <td>{{staff_name}}</td> | <td>{{staff_name}}</td> | ||||
| <td>{{price}}</td> | <td>{{price}}</td> | ||||
| <td class='invoice_nubmer'> | <td class='invoice_nubmer'> | ||||
| <img class="waiting_invoice_number" src="http://acaresydney.com.au/wp-content/plugins/ts/img/arrow.gif"> | |||||
| <img class="waiting_invoice_number" src="http://acaresydney.com.au/wp-content/plugins/ts/img/xero.png"> | |||||
| <img class="waiting_invoice_number" src="https://acaresydney.com.au/wp-content/plugins/ts/img/arrow.gif"> | |||||
| <img class="waiting_invoice_number" src="https://acaresydney.com.au/wp-content/plugins/ts/img/xero.png"> | |||||
| </td> | </td> | ||||
| </tr> | </tr> | ||||
| {{/jobs}} | {{/jobs}} |
| */ | */ | ||||
| namespace Biukop; | namespace Biukop; | ||||
| use XeroPHP\Models\Accounting\Address; | |||||
| use XeroPHP\Models\Accounting\Payment; | |||||
| require_once(dirname(__FILE__) . '/autoload.php'); | require_once(dirname(__FILE__) . '/autoload.php'); | ||||
| //require_once(dirname(__FILE__) . '/vendor/autoload.php'); | |||||
| require_once (ABSPATH . 'wp-includes/pluggable.php'); | require_once (ABSPATH . 'wp-includes/pluggable.php'); | ||||
| $loader->register(); | $loader->register(); | ||||
| // Add our namespace and the folder it maps to | // Add our namespace and the folder it maps to | ||||
| $loader->addNamespace('\XeroPHP', dirname(__FILE__) . '/xero-php-master/src/XeroPHP'); | |||||
| // $loader->addNamespace('\XeroPHP', dirname(__FILE__) . '/xero-php-master/src/XeroPHP'); | |||||
| $loader->addNamespace('\Biukop', dirname(__FILE__) . '/' ); | $loader->addNamespace('\Biukop', dirname(__FILE__) . '/' ); | ||||
| 'ratetype' => [], | 'ratetype' => [], | ||||
| ); | ); | ||||
| $buddy = $xx->get_buddy_timesheets($staff_login, new \DateTime($start), new \DateTime($finish)); | |||||
| $buddy = $xx->get_buddy_timesheets($staff_login, $start, $finish); | |||||
| if ($buddy != NULL){ | if ($buddy != NULL){ | ||||
| $item['Xero_Status'] = $buddy->getStatus(); | $item['Xero_Status'] = $buddy->getStatus(); | ||||
| }else{ | }else{ | ||||
| try{ | try{ | ||||
| $invoice = $this->create_invoice_by_client($client, $start, $finish); | $invoice = $this->create_invoice_by_client($client, $start, $finish); | ||||
| $response['invoice_number'] = sprintf("<a href='https://go.xero.com/AccountsReceivable/Edit.aspx?InvoiceID=%s' target='_blank'>%s</a>", | $response['invoice_number'] = sprintf("<a href='https://go.xero.com/AccountsReceivable/Edit.aspx?InvoiceID=%s' target='_blank'>%s</a>", | ||||
| $invoice->getInvoiceID(), $invoice->getInvoiceNumber()); | |||||
| $invoice->getInvoiceId(), $invoice->getInvoiceNumber()); | |||||
| }catch(\Exception $e){ | }catch(\Exception $e){ | ||||
| $response['status'] = 'error'; | $response['status'] = 'error'; | ||||
| $response['err'] = "XERO Invoice Error"; | $response['err'] = "XERO Invoice Error"; | ||||
| $sql = "SELECT * from $this->table_name WHERE tos != 1 and start>='$start 00:00:00' and start<='$finish 23:59:59' and client='$client_login' ORDER BY start"; | $sql = "SELECT * from $this->table_name WHERE tos != 1 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); | $rows = $this->db->get_results($sql); | ||||
| $xero = $this->xero->get_xero_handle(); | |||||
| $api = $this->XeroOauth2->get_accounting_instance(); | |||||
| //crate invoice | //crate invoice | ||||
| $invoice = new \XeroPHP\Models\Accounting\Invoice($xero); | |||||
| $contact= $xero->loadByGUID('Accounting\\Contact', $client_login); | |||||
| $invoice = new \XeroAPI\XeroPHP\Models\Accounting\Invoice; | |||||
| $contacts = $api->getContact($this->XeroOauth2->getTenantId(), $client_login); | |||||
| $now = new \DateTime(); | $now = new \DateTime(); | ||||
| $due = new \DateTime(); | $due = new \DateTime(); | ||||
| $due->modify("+14 days"); | $due->modify("+14 days"); | ||||
| $invoice->setType("ACCREC") | |||||
| ->setStatus("DRAFT") | |||||
| ->setContact($contact) | |||||
| ->setDate($now) | |||||
| ->setDueDate($due); | |||||
| $to_save=[];//all invoices to save; | |||||
| $invoice->setType("ACCREC"); | |||||
| $invoice->setStatus("DRAFT"); | |||||
| $invoice->setContact($contacts[0]); | |||||
| $invoice->setDate($now); | |||||
| $invoice->setDueDate($due); | |||||
| $price = new NdisPrice(2019);//always 2019 until its being changed; | $price = new NdisPrice(2019);//always 2019 until its being changed; | ||||
| $lineItems=[]; | |||||
| foreach($rows as $r){ | foreach($rows as $r){ | ||||
| $quantity = $this->get_job_hours($r->start, $r->finish); | $quantity = $this->get_job_hours($r->start, $r->finish); | ||||
| $unit = $price->get_tos_unit($r->tos); | $unit = $price->get_tos_unit($r->tos); | ||||
| } | } | ||||
| $unitprice = $price->get_tos_price($r->tos); | $unitprice = $price->get_tos_price($r->tos); | ||||
| $description = $this->get_job_description_for_invoice($price, $r); | $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); | |||||
| $lineItem = new \XeroAPI\XeroPHP\Models\Accounting\LineItem; | |||||
| $lineItem->setDescription($description); | |||||
| $lineItem->setQuantity($quantity); | |||||
| $lineItem->setUnitAmount($unitprice); | |||||
| $lineItem->setAccountCode(220); | |||||
| $lineItem->setTaxType("EXEMPTOUTPUT"); | |||||
| $lineItems[]= $lineItem; | |||||
| } | } | ||||
| $invoice->setLineItems($lineItems); | |||||
| //prevent zero lineitems | //prevent zero lineitems | ||||
| if ( count($invoice->getLineItems()) >0 ) | |||||
| $invoice->save(); | |||||
| if ( count($invoice->getLineItems()) >0 ){ | |||||
| $to_save[] = $invoice; | |||||
| $invoices = new \XeroAPI\XeroPHP\Models\Accounting\Invoices; | |||||
| $invoices->setInvoices($to_save); | |||||
| $result = $api->createInvoices($this->XeroOauth2->getTenantId(), $invoices); | |||||
| $createdInvoices = $result->getInvoices(); | |||||
| return $createdInvoices[0]; | |||||
| } | |||||
| return $invoice; | return $invoice; | ||||
| } | } | ||||