timesheet source code
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

495 lines
18KB

  1. <?php
  2. namespace Biukop;
  3. use \XeroPHP\Application\PrivateApplication;
  4. use \XeroPHP\Remote\Exception\RateLimitExceededException;
  5. use \XeroPHP\Remote\Exception\NotFoundException;
  6. class Xero {
  7. private $xero;
  8. private $clientgroup="48646f3d-cf5e-4fea-8c8b-5812bd540e1b";
  9. private $minimum_sync_interval_in_seconds = 600;
  10. private function default_config(){
  11. $config = array(
  12. 'oauth' => [
  13. 'callback' => 'http://acaresydney.com.au/',
  14. 'consumer_key' => 'G6AJIRWTH0X3C5SVS3ETZXNFCMDNGG',
  15. 'consumer_secret' => 'LP0PTSBCJLBB4CGYYKOHDXYF2NWXWD',
  16. 'rsa_private_key' => 'file://' . dirname(__FILE__) . '/keys/privatekey.pem',
  17. ],
  18. );
  19. return $config;
  20. }
  21. private function office_config(){
  22. $office_config = [
  23. 'xero' => [
  24. // 'payroll_version' =>'1.9',
  25. ],
  26. 'curl' => [
  27. CURLOPT_USERAGENT =>'AcareSydneyWebOffice',
  28. ],
  29. 'oauth' => [
  30. 'callback' => 'http://acaresydney.com.au/',
  31. 'consumer_key' => 'JE4LWKCJ6NHED30RFZWCT7WQYTS8JD',
  32. 'consumer_secret' => 'JXVZAZWKGM7MLUZSVIMK7ZSJE9GNYQ',
  33. 'rsa_private_key' => 'file://' . dirname(__FILE__) . '/keys/privatekey.pem',
  34. ],
  35. ];
  36. return $office_config;
  37. }
  38. public function __construct(){
  39. $this->xero = new PrivateApplication($this->office_config());
  40. }
  41. public function get_xero_handle()
  42. {
  43. return $this->xero;
  44. }
  45. public function getClients($contact_group_id){
  46. $xero = $this->xero;
  47. $cg = $xero->loadByGUID("Accounting\\ContactGroup", $contact_group_id);
  48. $contacts = $cg->getContacts();
  49. return $contacts;
  50. }
  51. public function getContact($id){
  52. $user = $this->xero->loadByGUID("Accounting\\Contact",$id);
  53. return $user;
  54. }
  55. public function getEmployees(){
  56. $employees = $this->xero->load("PayrollAU\\Employee")
  57. ->where('EmployeeGroupName="Web-Employee"')
  58. ->execute();
  59. return $employees;
  60. }
  61. public function getEmployee($id){
  62. $employee = $this->xero->loadByGUID("PayrollAU\\Employee",$id);
  63. return $employee;
  64. }
  65. //
  66. //sync users to wordpress system
  67. //does not work for too many users or employees
  68. public function sync_users($mininterval, $employeeonly, $clientsonly){
  69. $this->usage();
  70. $this->minimum_sync_interval_in_seconds = $mininterval;
  71. $msg="Sync users with minimum interval set to $mininterval \n";
  72. $this->logConsole($msg);
  73. try{
  74. if ($clientsonly){
  75. $this->sync_clients();
  76. }else if ($employeeonly){
  77. $this->sync_employees();
  78. }else{
  79. $this->sync_clients();
  80. $this->sync_employees();
  81. }
  82. }catch(RateLimitExceededException $e){
  83. $msg= "Xero API rate limit exceeded, please try again later, existing sync within 600 seconds will by passed automatically\n";
  84. $this->logConsole($msg);
  85. }catch(NotFoundException $e){
  86. $msg= "Xero API resource not found rate limit exceeded, please try again later, existing sync within 600 seconds will by passed automatically\n";
  87. $this->logConsole($msg);
  88. }
  89. }
  90. private function sync_clients($add_new_only = false){
  91. $contacts = $this->getClients($this->clientgroup);
  92. //add new first;
  93. $to_update = [];
  94. foreach ($contacts as $c){
  95. $login = $c->getContactID();
  96. $user = get_user_by('login', $login);
  97. if ($user === false){
  98. $this->add_new_contact($c);
  99. }else{
  100. $to_update[] = $c; // to update users later;
  101. }
  102. }
  103. if ($add_new_only)
  104. return;
  105. //sync existing
  106. foreach ($to_update as $c){
  107. $msg = sprintf("SYNC Client name=[%s] {%s} \n", $c->getName(), $c->getContactID());
  108. $this->logConsole($msg);
  109. $this->update_existing_contact($c);
  110. }
  111. }
  112. private function sync_employees($add_new_only = false){
  113. $employees = $this->getEmployees();
  114. //add new staff
  115. foreach ( $employees as $e){
  116. $login = $e->getEmployeeID();
  117. $user = get_user_by('login', $login);
  118. if ($user === false){
  119. $this->add_new_staff($e);
  120. }else{
  121. $to_update[] = $e; // to update exiting users later;
  122. }
  123. }
  124. if ($add_new_only)
  125. return;
  126. foreach ( $to_update as $e){
  127. $msg = sprintf("SYNC employee name=[%s %s] {%s} \n", $e->getFirstName(), $e->getLastName(), $e->getEmployeeID());
  128. $this->logConsole($msg);
  129. $this->update_existing_staff($e);
  130. }
  131. }
  132. private function add_new_contact($contact){
  133. $login = $contact->getContactID();
  134. $user = get_user_by('login', $login);
  135. if ($user === false){
  136. $msg = sprintf("ADD Client name=[%s] {%s} \n", $contact->getName(), $contact->getContactID());
  137. $this->logConsole($msg);
  138. $xero_contact = $this->getContact($login);
  139. $args = $this->xero_contact_profile($xero_contact);
  140. $id = wp_insert_user($args);
  141. if (! id instanceof \WP_Error){
  142. $user = get_user_by('ID', $id);
  143. update_user_meta($user->ID, 'address', $args['address']);
  144. update_user_meta($user->ID, 'account', $args['account']);
  145. }else{
  146. $msg = "==(Add Client failed)==";
  147. $this->logConsole($msg);
  148. $msg = sprintf("ADD Client name=[%s] {%s} Failed\n", $contact->getName(), $contact->getContactID());
  149. error_log("ACARE add client failed: $msg");
  150. }
  151. }
  152. }
  153. private function update_existing_contact($contact){
  154. $login = $contact->getContactID();
  155. $user = get_user_by('login', $login);
  156. if ($user !== false)
  157. {//update user - must be existing user;
  158. if ($this->is_too_close_to_sync($user)){
  159. return;
  160. }
  161. $xero_contact = $this->getContact($login);
  162. $args = $this->xero_contact_profile($xero_contact);
  163. $args['ID'] = $user->ID;
  164. unset($args['user_pass']); //we don't change password
  165. wp_update_user($args);
  166. update_user_meta($user->ID, 'address', $args['address']);
  167. update_user_meta($user->ID, 'account', $args['account']);
  168. }
  169. $this->mark_updated($user->ID);
  170. }
  171. private function xero_contact_profile($c){
  172. $args = [
  173. 'user_login' => $username = $c->getContactID(),
  174. 'user_pass' => md5(uniqid(rand() + time(), true)),
  175. 'display_name' =>$c->getName(),
  176. 'user_email' => $c->getEmailAddress(),
  177. 'first_name' => $c->getFirstName(),
  178. 'last_name' => $c->getLastName(),
  179. 'nickname' => $c->getName(),
  180. 'account' => $c->getAccountNumber(),
  181. 'address'=> $this->get_post_address($c),
  182. 'role' => 'client',
  183. ];
  184. return $args;
  185. }
  186. private function get_post_address($client){
  187. $result = "";
  188. $addr = $this->get_client_address_by_type($client, 'POBOX');
  189. if ( $addr != false){
  190. if ($addr->getAddressLine1() != ""){
  191. $result .= $addr->getAddressLine1() . ";";
  192. }
  193. if ($addr->getAddressLine2() != ""){
  194. $result .= $addr->getAddressLine2() . ";";
  195. }
  196. if ($addr->getAddressLine3() != ""){
  197. $result .= $addr->getAddressLine3() . ";";
  198. }
  199. if ($addr->getAddressLine4() != ""){
  200. $result .= $addr->getAddressLine4() . ";";
  201. }
  202. if ($addr->getCity() != ""){
  203. $result .= $addr->getCity() . ";";
  204. }
  205. if ($addr->getPostalCode() != ""){
  206. $result .= $addr->getPostalCode() . ";";
  207. }
  208. }
  209. echo "result for client is " . $result . "\n";
  210. return $result;
  211. }
  212. private function get_client_address_by_type($client, $t){
  213. $addr = false;
  214. foreach( $client->getAddresses() as $a){
  215. if( $a->getAddressType() == $t){
  216. $addr = $a;
  217. break;
  218. }
  219. }
  220. return $addr;
  221. }
  222. private function add_new_staff($employee)
  223. {
  224. $login = $employee->getEmployeeID();
  225. $user = get_user_by('login', $login);
  226. if ($user === false){
  227. $msg = sprintf("ADD employee name=[%s %s] {%s} \n",
  228. $employee->getFirstName(),
  229. $employee->getLastName(),
  230. $employee->getEmployeeID());
  231. $this->logConsole($msg);
  232. $xero_employee = $this->getEmployee($login);
  233. $args = $this->xero_employee_profile($xero_employee);
  234. $id = wp_insert_user($args);
  235. if (! id instanceof \WP_Error){
  236. $user = get_user_by('ID', $id);
  237. update_user_meta($user->ID, 'mobile', $args['mobile']);
  238. update_user_meta($user->ID, 'address', $args['address']);
  239. }else{
  240. $msg = "==(Add staff failed)==";
  241. $this->logConsole($msg);
  242. $msg = sprintf("ADD employee name=[%s %s] {%s} Failed\n",
  243. $employee->getFirstName(),
  244. $employee->getLastName(),
  245. $employee->getEmployeeID());
  246. error_log("ACARE add employee failed: $msg");
  247. }
  248. }
  249. }
  250. private function update_existing_staff($employee)
  251. {
  252. $login = $employee->getEmployeeID();
  253. $user = get_user_by('login', $login);
  254. if ($this->is_too_close_to_sync($user)){
  255. return;
  256. }
  257. if ($user != false) {
  258. $xero_employee = $this->getEmployee($login);
  259. $args = $this->xero_employee_profile($xero_employee);
  260. $args['ID'] = $user->ID;
  261. unset($args['user_pass']);
  262. wp_update_user($args);
  263. update_user_meta($user->ID, 'mobile', $args['mobile']);
  264. update_user_meta($user->ID, 'address', $args['address']);
  265. }
  266. $this->mark_updated($user->ID);
  267. }
  268. private function xero_employee_profile($e){
  269. $args = [
  270. 'user_login' => $username = $e->getEmployeeID(),
  271. 'user_pass' => md5(uniqid(rand() + time(), true)),
  272. 'display_name' =>$e->getFirstName() . " " . $e->getLastName(),
  273. 'user_email' => $e->getEmail(),
  274. 'first_name' => $e->getFirstName(),
  275. 'last_name' => $e->getLastName(),
  276. 'nickname' => $e->getFirstName(),
  277. 'mobile' => $e->getMobile(),
  278. 'address'=> $this->get_employee_address($e),
  279. 'role' => 'staff',
  280. ];
  281. return $args;
  282. }
  283. private function get_employee_address($e){
  284. // "HomeAddress": {
  285. // "AddressLine1": "16 Quist Avenue",
  286. // "City": "Lurnea",
  287. // "Region": "NSW",
  288. // "PostalCode": "2170",
  289. // "Country": "AUSTRALIA"
  290. // },
  291. $addr = "";
  292. $home = $e->getHomeAddress();
  293. $addr .= $home->getAddressLine1() .",";
  294. $addr .= $home->getAddressLine2() .",";
  295. $addr .= $home->getCity() . ",";
  296. $addr .= $home->getRegion() . " ";
  297. $addr .= $home->getCountry() ." ";
  298. $addr .= $home->getPostalCode();
  299. return $addr;
  300. }
  301. private function mark_updated($userid){
  302. update_user_meta($userid, 'lastsync', time());
  303. }
  304. private function get_last_sync($userid){
  305. $lastsync = get_user_meta($userid, 'lastsync', true);
  306. return (int)($lastsync);
  307. }
  308. private function is_too_close_to_sync($user){
  309. $userid = $user->ID;
  310. $lastsync = $this->get_last_sync($user->ID);
  311. $now = time();
  312. $diff = $now - (int) $lastsync;
  313. if ($diff < $this->minimum_sync_interval_in_seconds){
  314. $msg = sprintf("\tSKIP userid(%d),login=%s,display_name=%s,(lastsync=%d secs ago, mininterval=%d) \n",
  315. $user->ID, $user->user_login, $user->display_name, $diff, $this->minimum_sync_interval_in_seconds);
  316. $this->logConsole($msg);
  317. return true;
  318. }
  319. return false;
  320. }
  321. private function logConsole($str){
  322. //if is commandline
  323. if ( defined( 'WP_CLI' ) && WP_CLI ) {
  324. echo $str;
  325. }
  326. }
  327. private function usage(){
  328. $msg = "_____________________________________________\n";
  329. $msg .= "run this command at public_html/, where wp-config.php exist \n";
  330. $msg .= "wp sync_users --mininterval=6000 \n";
  331. $msg .= "6000 means those users synced within 6000 seconds will be bypassed \n";
  332. $msg .= "to sync everything \n";
  333. $msg .= "wp sync_users --mininterval=0 [--clientsonly] [--employeeonly]\n";
  334. $msg .= "but it may hit XERO rate limit, 60call/sec, 5000/day \n";
  335. $msg .= "---------------------------------------------\n";
  336. $this->logConsole($msg);
  337. }
  338. /* sync payitems to wp options */
  339. public function init_wp(){
  340. try{
  341. $this->sync_payitem();
  342. $this->add_new_client();
  343. $this->add_new_employee();
  344. $this->sync_payroll_calendar();
  345. }catch(\XeroPHP\Remote\Exception $e){
  346. }
  347. }
  348. private function add_new_client(){
  349. if ($this->too_close_to_add_client()){
  350. return;
  351. }
  352. update_option('bts_add_client_last_sync', time());
  353. $this->sync_clients(true);//add new only;
  354. }
  355. private function add_new_employee(){
  356. if ($this->too_close_to_add_employee()){
  357. return;
  358. }
  359. update_option('bts_add_employee_last_sync', time());
  360. $this->sync_employees(true);//add new only;
  361. }
  362. private function sync_payroll_calendar()
  363. {
  364. if ($this->too_close_to_sync_payroll_calendar()){
  365. return;
  366. }
  367. update_option('bts_pay_roll_calendar_last_sync', time());
  368. $pc = $this->get_payroll_calendar();
  369. $start = $pc->getStartDate()->format('Y-m-d');
  370. $finish = new \DateTime($start);
  371. $finish = $finish->modify("+13 days")->format('Y-m-d');
  372. $paydate = $pc->getPaymentDate()->format('Y-m-d');
  373. $calendar["start"] = $start;
  374. $calendar["finish"] = $finish;
  375. $calendar["paydate"] = $paydate;
  376. update_option('bts_pay_roll_calendar', $calendar);
  377. }
  378. private function sync_payitem(){
  379. if ($this->too_close_to_sync_payitem()){
  380. return;
  381. }
  382. $payitems = $this->xero->load('PayrollAU\\PayItem')->execute();
  383. $payitem_options = array();
  384. foreach ($payitems[0]->getEarningsRates() as $e){
  385. // "EarningsRateID": "34e17d08-237a-4ae2-8115-375d1ff8a9ed",
  386. // "Name": "Overtime Hours (exempt from super)",
  387. // "EarningsType": "OVERTIMEEARNINGS",
  388. // "RateType": "MULTIPLE",
  389. // "AccountCode": "477",
  390. // "Multiplier": 1.5,
  391. // "IsExemptFromTax": true,
  392. // "IsExemptFromSuper": true,
  393. // "AccrueLeave": false,
  394. // "IsReportableAsW1": true,
  395. // "UpdatedDateUTC": "2019-03-16T13:18:19+00:00",
  396. // "CurrentRecord": false
  397. if ($e->getCurrentRecord() == "true"){
  398. $payitem_options[]= array(
  399. 'EarningsRateID' => $e->getEarningsRateID(),
  400. 'Name'=> $e->getName(),
  401. 'EarningsType'=> $e->getEarningstype(),
  402. 'RatePerUnit' => $e->getRatePerUnit(),
  403. 'RateType' => $e->getRateType(),
  404. 'AccountCode' => $e->getAccountCode(),
  405. "Multiplier"=> $e->getMultiplier(),
  406. "IsExemptFromTax" => $e->getIsExemptFromTax(),
  407. "IsExemptFromSuper"=> $e->getIsExemptFromSuper(),
  408. "AccrueLeave" => $e->getAccrueLeave(),
  409. "TypeOfUnits" => $e->getTypeOfUnits(),
  410. "CurrentRecord"=> $e->getCurrentRecord(),
  411. );
  412. }
  413. }
  414. update_option('bts_payitem_earnings_rate', $payitem_options);
  415. update_option('bts_payitem_last_sync', time());
  416. }
  417. private function too_close_to_sync_payitem(){
  418. $lastsync = get_option('bts_payitem_last_sync', 0);
  419. $now = time();
  420. $diff = $now - (int) $lastsync;
  421. return $diff < $this->minimum_sync_interval_in_seconds; //default 10 minutes
  422. }
  423. private function too_close_to_add_employee(){
  424. $lastsync = get_option('bts_add_employee_last_sync', 0);
  425. $now = time();
  426. $diff = $now - (int) $lastsync;
  427. return $diff < 1.5 * $this->minimum_sync_interval_in_seconds; //default 1.1 * 10 minutes
  428. }
  429. private function too_close_to_add_client(){
  430. $lastsync = get_option('bts_add_client_last_sync', 0);
  431. $now = time();
  432. $diff = $now - (int) $lastsync;
  433. return $diff < 2.0 * $this->minimum_sync_interval_in_seconds; //default 1.2 * 10 minutes
  434. }
  435. private function too_close_to_sync_payroll_calendar(){
  436. $lastsync = get_option('bts_pay_roll_calendar_last_sync', 0);
  437. $now = time();
  438. $diff = $now - (int) $lastsync;
  439. return $diff < 3.0 * $this->minimum_sync_interval_in_seconds; //default 1.3 * 10 minutes
  440. }
  441. public function get_payroll_calendar()
  442. {
  443. $id = "33dc7df5-3060-4d76-b4da-57c20685d77d"; //fortnightly
  444. $pc = $this->xero->loadByGUID('PayrollAU\\PayrollCalendar', $id);
  445. return $pc;
  446. }
  447. }