timesheet source code
Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.

1747 linhas
64KB

  1. <?php
  2. /**
  3. * Plugin Name: Acare Advanced Office
  4. * Plugin URI: http://biukop.com.au/acaresydney/timesheets
  5. * Description: Advanced Office system, timesheet, Payroll for AcareSydney
  6. * Version: 2.1
  7. * Author: Biukop Intelligence
  8. * Author URI: http://biukop.com.au/
  9. */
  10. namespace Biukop;
  11. require_once(dirname(__FILE__) . '/autoload.php');
  12. //require_once(dirname(__FILE__) . '/vendor/autoload.php');
  13. require_once (ABSPATH . 'wp-includes/pluggable.php');
  14. class AcareOffice{
  15. private $acare_ndis_registration = "4050024758";
  16. private $nonce; //for ajax verification
  17. //private $pages = array('time-sheets', 'user-list');
  18. private $bts_user_id = 0;
  19. private $bts_week_id = 1; //week 1, we will try to calculate current week;
  20. // private $xero ;
  21. private $db;
  22. private $table_name; //default to job_table
  23. private $job_table;
  24. private $allowance_table;
  25. private $addr_table;
  26. private $ndis_table;
  27. private $apiv1;
  28. private $XeroOauth2;
  29. private $ndis_price;
  30. public function __construct() {
  31. $this->setup_db_name();
  32. $this->class_loader();
  33. $this->apiv1 = new Apiv1($this, $this->job_table);
  34. $this->XeroOauth2 = new XeroOauth2($this);
  35. //$this->check_csv_download();
  36. add_option( "acare_ts_db_version", "1.0" );
  37. register_activation_hook( __FILE__, array($this, 'db_install') );
  38. //add_action('init', array($this, 'class_loader'));
  39. add_action('init', array($this, 'check_csv_download'));
  40. add_action('wp', array($this, 'check_auth'));
  41. add_action('wp_enqueue_scripts', array($this, 'register_js_css'), 99);
  42. add_filter('show_admin_bar', '__return_false');
  43. //bts-xx for webpage
  44. add_shortcode( 'bts_staff_item', array($this, 'bts_staff_item'));
  45. add_shortcode( 'bts_client_item', array($this, 'bts_client_item'));
  46. add_shortcode( 'bts_job_item', array($this, 'bts_job_item'));
  47. add_shortcode( 'bts_rate_options', array($this, 'bts_rate_options'));
  48. add_shortcode( 'bts_select_staff', array($this, 'bts_select_staff'));
  49. add_shortcode( 'bts_select_client', array($this, 'bts_select_client'));
  50. add_shortcode( 'bts_type_of_service', array($this, 'bts_type_of_service'));
  51. add_shortcode( 'bts_staff_job_summary', array($this, 'bts_staff_job_summary'));
  52. add_shortcode( 'bts_feedback_card', array($this, 'bts_feedback_card'));
  53. add_shortcode( 'bb_timesheet_canvas', array($this, 'bb_timesheet_canvas'));
  54. add_shortcode( 'bts_staff_hours_template', array($this, 'bts_staff_hours_template'));
  55. add_shortcode( 'bts_client_invoice_template', array($this, 'bts_client_invoice_template'));
  56. add_shortcode( 'bts_csv_template', array($this, 'bts_csv_template'));
  57. add_shortcode( 'bts_invoiced_client', array($this, 'bts_invoiced_client'));
  58. //user profile page
  59. add_shortcode( 'bts_user_name', array($this,'bts_user_name'));
  60. $this->ajax_hook('list_staff');
  61. $this->ajax_hook('list_client');
  62. $this->ajax_hook('list_tos');
  63. $this->ajax_hook('save_job');
  64. $this->ajax_hook('list_job');
  65. $this->ajax_hook('delete_job');
  66. $this->ajax_hook('email_job');
  67. $this->ajax_hook('email_feedback_url');
  68. $this->ajax_hook('earnings_rate');
  69. $this->ajax_hook('list_job_by_staff');
  70. $this->ajax_hook('staff_ack_job');
  71. $this->ajax_hook('list_job_by_client');
  72. $this->ajax_hook('client_ack_job');
  73. $this->ajax_hook('get_timesheet_from_xero');
  74. $this->ajax_hook('approve_all_timesheet');
  75. $this->ajax_hook('get_invoice_item');
  76. $this->ajax_hook('create_invoice_in_xero');
  77. // hook add_rewrite_rules function into rewrite_rules_array
  78. add_filter('rewrite_rules_array', array($this,'my_add_rewrite_rules'));
  79. // hook add_query_vars function into query_vars
  80. add_filter('query_vars', array($this,'add_query_vars'));
  81. }
  82. private function ajax_hook($code, $admin_only = false)
  83. {
  84. add_action("wp_ajax_$code", array($this,$code ));
  85. if (!$admin_only) {
  86. add_action("wp_ajax_nopriv_$code", array($this,$code));
  87. }
  88. }
  89. private function setup_db_name()
  90. {
  91. global $wpdb;
  92. $this->db = $wpdb;
  93. $this->table_name = $wpdb->prefix . 'acare_ts'; //for backward compatability;
  94. $this->job_table = $wpdb->prefix . 'acare_ts';
  95. $this->allowance_table = $wpdb->prefix . 'acare_allowance';
  96. $this->addr_table = $wpdb->prefix . 'acare_addr_distance';
  97. $this->ndis_table = $wpdb->prefix . 'acare_ndis_price';
  98. }
  99. /**
  100. * Autoload the custom theme classes
  101. */
  102. public function class_loader()
  103. {
  104. // Create a new instance of the autoloader
  105. $loader = new \Psr4AutoloaderClass();
  106. // Register this instance
  107. $loader->register();
  108. // Add our namespace and the folder it maps to
  109. // $loader->addNamespace('\XeroPHP', dirname(__FILE__) . '/xero-php-master/src/XeroPHP');
  110. $loader->addNamespace('\Biukop', dirname(__FILE__) . '/' );
  111. //$abc = new AddrMap("01515b52-6936-46b2-a000-9ad4cd7a5b50", "0768db6d-e5f4-4b45-89a2-29f7e8d2953c");
  112. //$abc = new AddrMap("122eb1d0-d8c4-4fc3-8bf8-b7825bee1a01", "0768db6d-e5f4-4b45-89a2-29f7e8d2953c");
  113. }
  114. public function check_csv_download()
  115. {
  116. $method = $_SERVER['REQUEST_METHOD'];
  117. if ( $method != "POST")
  118. return;
  119. global $wpdb;
  120. $url = $_SERVER['REQUEST_URI'];
  121. if ($url != "/ndiscsv/"){
  122. return;
  123. }
  124. $clients= $_POST['clients'];
  125. $start = $_POST['start'];;
  126. $finish = $_POST['finish'];;
  127. $filename="n" . time() . ".csv";
  128. header("Expires: 0");
  129. header("Cache-Control: no-cache, no-store, must-revalidate");
  130. header('Cache-Control: pre-check=0, post-check=0, max-age=0', false);
  131. header("Pragma: no-cache");
  132. header("Content-type: text/csv");
  133. header("Content-Disposition:attachment; filename=$filename");
  134. header("Content-Type: application/force-download");
  135. //readfile(dirname(__FILE__) . "/img/circle.png");
  136. $clients_in = "'" . implode("','", $clients) . "'";
  137. $sql = "SELECT * from $this->table_name WHERE client in ($clients_in) and start >='$start 00:00:00' and start<='$finish 23:59:59'";
  138. $results = $wpdb->get_results($sql);
  139. $csv_header = "RegistrationNumber,NDISNumber,SupportsDeliveredFrom,SupportsDeliveredTo,SupportNumber,ClaimReference,Quantity,Hours,UnitPrice,GSTCode,AuthorisedBy,ParticipantApproved,InKindFundingProgram,ClaimType,CancellationReason\n";
  140. echo $csv_header;
  141. foreach($results as $r)
  142. {
  143. echo $this->ndis_csv_line($r);
  144. }
  145. //echo $sql;
  146. exit();
  147. }
  148. private function get_ndis_price()
  149. {//help to ensure ndis_price is only build once per call
  150. if ( ! $this->ndis_price instanceof NdisPrice )
  151. $this->ndis_price = new NdisPrice();
  152. return $this->ndis_price;
  153. }
  154. private function ndis_csv_line($record)
  155. {
  156. $str = "";
  157. $price = $price = $this->get_ndis_price();
  158. $registration = $this->acare_ndis_registration;
  159. $ndisnumber = $this->get_client_ndis_account($record->client);
  160. $date = new \DateTime($record->start);
  161. $start = $date->format("Y-m-d");
  162. $date = new \Datetime($record->finish);
  163. $finish = $date->format("Y-m-d");
  164. $quantity = $this->get_job_hours($record->start, $record->finish);
  165. $hours = $this->get_job_hours_hh_mm($record->start, $record->finish);
  166. $unitprice = $this->get_ndis_price()->get_tos_price($record->tos);
  167. $authorizedby="helen";
  168. $participant_approved = "";
  169. $GST = $this->get_client_GST($record->client);
  170. $in_kind_program = $this->get_client_in_kind_program($record->client);
  171. $ClaimType = "";// standard;
  172. $CancellationReason="";
  173. $SupportNumber = $this->get_ndis_price()->get_tos_ndis_code($record->tos);
  174. return "$registration,$ndisnumber,$start,$finish,$SupportNumber,REC_{$record->id},$quantity,$hours,$unitprice,$GST,$authorizedby,$participant_approved,$in_kind_program,$ClaimType,$CancellationReason\n";
  175. }
  176. private function get_client_ndis_account($client)
  177. {
  178. $user = get_user_by('login', $client);
  179. return get_user_meta($user->ID,'account',true);
  180. }
  181. private function get_client_in_kind_program($client)
  182. {
  183. $user = get_user_by('login', $client);
  184. return get_user_meta($user->ID,'in_kind_prog',true);
  185. }
  186. private function get_client_GST($client)
  187. {
  188. $user = get_user_by('login', $client);
  189. $str = get_user_meta($user->ID,'gst',true);
  190. if ($str == "")
  191. return "P2";
  192. return $str;
  193. }
  194. //init database
  195. public function db_install () {
  196. global $wpdb;
  197. $charset_collate = $wpdb->get_charset_collate();
  198. //table name: timesheets jobs
  199. $table_name = $this->table_name;
  200. $sql = "CREATE TABLE $table_name (
  201. id INT NOT NULL AUTO_INCREMENT,
  202. tos VARCHAR(45) NULL,
  203. start DATETIME NULL,
  204. finish DATETIME NULL,
  205. rate VARCHAR(45) NULL,
  206. staff VARCHAR(45) NULL,
  207. client VARCHAR(45) NULL,
  208. ack TINYINT(4) NULL,
  209. rating INT(4) NULL DEFAULT 0,
  210. PRIMARY KEY (id)
  211. ) $charset_collate;";
  212. //addr distance
  213. $addr_table = $this->addr_table;
  214. $sql_addr = "CREATE TABLE $addr_table (
  215. id INT NOT NULL AUTO_INCREMENT,
  216. origin VARCHAR(1024) NULL,
  217. destination VARCHAR(1024) NULL,
  218. response VARCHAR(40960) NULL,
  219. distance INT NULL,
  220. PRIMARY KEY (id)
  221. ) $charset_collate;";
  222. $ndis_table = $this->ndis_table;
  223. $sql_ndis_price = "
  224. CREATE TABLE $ndis_table (
  225. code VARCHAR(45) NOT NULL,
  226. name VARCHAR(45) NULL,
  227. level INT NULL,
  228. unit VARCHAR(45) NULL,
  229. price FLOAT NULL,
  230. year INT NOT NULL,
  231. PRIMARY KEY (code, year)
  232. )$charset_collate;";
  233. //create database
  234. require_once( ABSPATH . 'wp-admin/includes/upgrade.php' );
  235. dbDelta( $sql );
  236. dbDelta( $sql_addr);
  237. dbDelta( $sql_ndis_price);
  238. }
  239. //
  240. //query var
  241. public function add_query_vars($aVars) {
  242. $aVars[] = "bts_user_id"; // represents the name of the product category as shown in the URL
  243. $aVars[] = "bts_week_id"; // represents the name of the product category as shown in the URL
  244. $aVars[] = "bts_job_start"; // represents the name of the product category as shown in the URL
  245. $aVars[] = "bts_job_finish"; // represents the name of the product category as shown in the URL
  246. return $aVars;
  247. }
  248. //for customer profile and broker trans
  249. public function my_add_rewrite_rules($aRules) {
  250. $aNewRules = array(
  251. 'user/([^/]+)/?$' => 'index.php?pagename=user&bts_user_id=$matches[1]',
  252. 'task/week-([^/]+)/?$' => 'index.php?pagename=task&bts_week_id=$matches[1]',
  253. 'task/start-([^/]+)/finish-([^/]+)/?$' => 'index.php?pagename=task&bts_job_start=$matches[1]&bts_job_finish=$matches[2]',
  254. 'task/([^/]+)/?$' => 'index.php?pagename=task&bts_user_id=$matches[1]',
  255. 'task/([^/]+)/week-([^/]+)/?$' => 'index.php?pagename=task&bts_user_id=$matches[1]&bts_week_id=$matches[2]',
  256. 'task/([^/]+)/start-([^/]+)/finish-([^/]+)/?$' => 'index.php?pagename=task&bts_user_id=$matches[1]&bts_job_start=$matches[2]&bts_job_finish=$matches[3]',
  257. 'feedback_card/week-([^/]+)/?$' => 'index.php?pagename=feedback_card&bts_week_id=$matches[1]',
  258. 'feedback_card/start-([^/]+)/finish-([^/]+)/?$' => 'index.php?pagename=feedback_card&bts_job_start=$matches[1]&bts_job_finish=$matches[2]',
  259. 'feedback_card/([^/]+)/?$' => 'index.php?pagename=feedback_card&bts_user_id=$matches[1]',
  260. 'feedback_card/([^/]+)/week-([^/]+)/?$' => 'index.php?pagename=feedback_card&bts_user_id=$matches[1]&bts_week_id=$matches[2]',
  261. 'feedback_card/([^/]+)/start-([^/]+)/finish-([^/]+)/?$' => 'index.php?pagename=feedback_card&bts_user_id=$matches[1]&bts_job_start=$matches[2]&bts_job_finish=$matches[3]',
  262. 'ndiscsv/start-([^/]+)/finish-([^/]+)/?$' => 'index.php?pagename=ndiscsv&bts_job_start=$matches[1]&bts_job_finish=$matches[2]',
  263. );
  264. $aRules = $aNewRules + $aRules;
  265. return $aRules;
  266. }
  267. //
  268. //
  269. ///check auth
  270. public function check_auth(){
  271. global $pagename;
  272. if ($this->is_superadmin()) //skip
  273. return;
  274. switch($pagename){
  275. case 'task':
  276. $this->cauth_task(); //for staff
  277. break;
  278. case 'time-sheets':
  279. case 'office':
  280. $this->cauth_time_sheet(); //for admin
  281. break;
  282. case 'xeroc':
  283. $this->cauth_xero_sync(); //for accountant
  284. break;
  285. case 'feedback_card':
  286. $this->cauth_feedback_card(); //for client
  287. break;
  288. }
  289. }
  290. private function cauth_task(){
  291. if ($this->is_superadmin()) //skip
  292. return;
  293. $login = get_query_var( 'bts_user_id' );
  294. $this->bts_job_start = get_query_var( 'bts_job_start' );
  295. $this->bts_job_finish = get_query_var( 'bts_job_finish' );
  296. $this->bts_week_id = get_query_var('bts_week_id');
  297. $redirect_url = $this->get_redirect_url_for_task();
  298. // wp_send_json(array(
  299. // 'week'=> $week,
  300. // 'userid'=>$login,
  301. // 'job_start' => $this->bts_job_start,
  302. // 'job_finish' => $this->bts_job_finish,
  303. // 'redirect' => $redirect_url,
  304. // ));
  305. if ($login != "")//perform autologin, and redirect
  306. {
  307. $staff = get_user_by('login', $login);
  308. if ($this->is_staff($staff)){//is valid staff;
  309. $current = wp_get_current_user();
  310. if($current->ID != $staff->ID){
  311. wp_logout();
  312. wp_set_current_user($staff->ID, $staff->display_name); //this is a must
  313. wp_set_auth_cookie($staff->ID, true);//only with this, wordpress calls login + redirect and lost week-%d
  314. }
  315. }
  316. wp_redirect($redirect_url);
  317. return;
  318. }
  319. //no auto login is required if reach here.
  320. $current = wp_get_current_user();
  321. if ($this->is_admin($current) ){
  322. wp_redirect("/office/");
  323. return;
  324. }
  325. if ($this->is_accountant($current)){
  326. wp_redirect("/xeroc/");
  327. return;
  328. }
  329. if (!$this->is_staff($current) && ! $this->is_admin($current) )
  330. {
  331. wp_logout();
  332. wp_redirect("/login/");
  333. return;
  334. }
  335. }
  336. private function cauth_feedback_card(){
  337. $login = get_query_var( 'bts_user_id' );
  338. $this->bts_job_start = get_query_var( 'bts_job_start' );
  339. $this->bts_job_finish = get_query_var( 'bts_job_finish' );
  340. $this->bts_week_id = get_query_var('bts_week_id');
  341. $redirect_url = $this->get_redirect_url_for_feedback_card();
  342. if ($login != "")//perform autologin, and redirect
  343. {
  344. $client = get_user_by('login', $login);
  345. if ($this->is_client($client)){//is valid client;
  346. $current = wp_get_current_user();
  347. if($current->ID != $client->ID){
  348. wp_logout();
  349. wp_set_current_user($client->ID, $client->display_name); //this is a must
  350. wp_set_auth_cookie($client->ID, true);//only with this, wordpress calls login + redirect and lost week-%d
  351. }
  352. }
  353. wp_redirect($redirect_url);
  354. return;
  355. }
  356. //no auto login is required if reach here.
  357. $current = wp_get_current_user();
  358. if ($this->is_admin($current)){
  359. wp_redirect("/office/");
  360. return;
  361. }
  362. if (!$this->is_client($current) && ! $this->is_admin($current))
  363. {
  364. wp_logout();
  365. wp_redirect("/login/");
  366. return;
  367. }
  368. }
  369. private function get_week_id()
  370. {
  371. $week = get_query_var( 'bts_week_id' );
  372. $week_id = intval($week);
  373. if ($week_id == 0 || $week_id >53 ||$week_id < 1)
  374. return $this->get_current_week_id();
  375. else
  376. return $week;
  377. }
  378. private function get_current_week_id()
  379. {
  380. $now = new \DateTime();
  381. $week = $now->format("W");
  382. return $week;
  383. }
  384. private function get_redirect_url_for_task()
  385. {
  386. if ($this->bts_week_id != "")
  387. return "/task/week-" . $this->bts_week_id . "/";
  388. if ($this->bts_job_start!="" && $this->bts_job_finish !="")
  389. return "/task/start-" . $this->bts_job_start . "/finish-" .$this->bts_job_finish . "/";
  390. return '/task/';
  391. }
  392. private function get_redirect_url_for_feedback_card()
  393. {
  394. if ($this->bts_week_id != "")
  395. return "/feedback_card/week-" . $this->bts_week_id . "/";
  396. if ($this->bts_job_start!="" && $this->bts_job_finish !="")
  397. return "/feedback_card/start-" . $this->bts_job_start . "/finish-" .$this->bts_job_finish . "/";
  398. return '/feedback_card/';
  399. }
  400. private function cauth_time_sheet()
  401. {
  402. $current = wp_get_current_user();
  403. if ($current->ID == 0 ) { //visitor not logged in
  404. wp_redirect("/wp-login.php?");
  405. return;
  406. }
  407. if ($this->is_staff($current)){
  408. wp_redirect("/task");
  409. return;
  410. }
  411. if ($this->is_accountant($current)){
  412. wp_redirect("/xeroc");
  413. return;
  414. }
  415. if ($this->is_admin($current)){
  416. //proceed
  417. return;
  418. }
  419. if ($this->is_client($current)){
  420. wp_redirect("/service");
  421. return;
  422. }
  423. //everything else
  424. wp_redirect("/?invalid-access");
  425. }
  426. private function cauth_xero_sync()
  427. {
  428. $current = wp_get_current_user();
  429. if ($this->is_admin($current) ||$this->is_accountant($current) ){
  430. //proceed
  431. return;
  432. }
  433. wp_logout();
  434. wp_redirect("/login/");
  435. }
  436. ///
  437. // enqueue / register css /js
  438. //
  439. public function register_js_css() {
  440. $this->nonce = wp_create_nonce('acaresydney');
  441. $this->bts_user_id = get_query_var( 'bts_user_id' ) ;
  442. $this->register_bts_js();
  443. $this->register_timesheet_js_css();
  444. $this->register_office_js_css();
  445. $this->register_task_js_css();
  446. $this->register_feedback_card_js_css();
  447. $this->register_xeroc_js_css();
  448. }
  449. private function register_bts_js()
  450. {
  451. wp_enqueue_style( 'bts', plugins_url('css/ts.css', __FILE__));
  452. wp_enqueue_script('bts', plugins_url('js/ts.js', __FILE__), array('jquery', 'jquery-ui-core'));
  453. wp_localize_script( 'bts', 'bts1', array(
  454. 'ajax_url' => admin_url( 'admin-ajax.php' ),
  455. 'nonce' => $this->nonce, // It is common practice to comma after
  456. 'display_name' => wp_get_current_user()->display_name,
  457. 'anonymous' => !is_user_logged_in(),
  458. 'me'=> get_current_user_id(),
  459. 'userid'=> $this->bts_user_id,
  460. 'load_user_img'=> plugins_url('img/loading_user.gif', __FILE__),
  461. 'load_job_img'=> plugins_url('img/loading_job.gif', __FILE__),
  462. 'driving' => "259ee8e8-a1e5-42e4-9c14-517543ecdc4b",
  463. 'high_pay_keywords' => ['sat ', 'sun ', 'high ', 'public holiday'], //space is important
  464. 'pay_calendar'=> get_option('bts_pay_roll_calendar'),
  465. ) );
  466. }
  467. private function register_timesheet_js_css(){
  468. global $pagename;
  469. if ($pagename != 'time-sheets'){
  470. return;
  471. }
  472. wp_enqueue_style( 'bts_ts', plugins_url('css/bts_timesheet.css', __FILE__));
  473. wp_enqueue_script( 'bts_ts', plugins_url('js/bts_timesheet.js', __FILE__), array( 'jquery' , 'bts' ));
  474. wp_enqueue_script('mustache', plugins_url('js/mustache.min.js', __FILE__), array('jquery'));
  475. global $wp_scripts;
  476. wp_enqueue_script('jquery-ui-datepicker');
  477. // get registered script object for jquery-ui
  478. //$ui = $wp_scripts->query('jquery-ui-core');
  479. // tell WordPress to load the Smoothness theme from Google CDN
  480. //$protocol = is_ssl() ? 'https' : 'http';
  481. // $url = "$protocol://ajax.googleapis.com/ajax/libs/jqueryui/{$ui->ver}/themes/smoothness/jquery-ui.min.css";
  482. $url = plugins_url('jquery-ui-1.11.4.theme/jquery-ui.min.css', __FILE__);
  483. wp_enqueue_style('jquery-ui-smoothness', $url, false, null);
  484. }
  485. private function register_office_js_css(){
  486. global $pagename;
  487. if ($pagename != 'office'){
  488. return;
  489. }
  490. wp_enqueue_style( 'bts_office', plugins_url('css/bts_office.css', __FILE__));
  491. wp_enqueue_script( 'bts_office', plugins_url('js/bts_office.js', __FILE__), array( 'jquery' , 'bts' ));
  492. wp_enqueue_script('mustache', plugins_url('js/mustache.min.js', __FILE__), array('jquery'));
  493. global $wp_scripts;
  494. wp_enqueue_script('jquery-ui-datepicker');
  495. // get registered script object for jquery-ui
  496. //$ui = $wp_scripts->query('jquery-ui-core');
  497. // tell WordPress to load the Smoothness theme from Google CDN
  498. //$protocol = is_ssl() ? 'https' : 'http';
  499. // $url = "$protocol://ajax.googleapis.com/ajax/libs/jqueryui/{$ui->ver}/themes/smoothness/jquery-ui.min.css";
  500. $url = plugins_url('jquery-ui-1.11.4.theme/jquery-ui.min.css', __FILE__);
  501. wp_enqueue_style('jquery-ui-smoothness', $url, false, null);
  502. }
  503. private function register_task_js_css(){
  504. global $pagename;
  505. if ($pagename != 'task'){
  506. return;
  507. }
  508. $this->bts_job_start = get_query_var( 'bts_job_start' );
  509. $this->bts_job_finish = get_query_var( 'bts_job_finish' );
  510. $this->bts_week_id = get_query_var('bts_week_id');
  511. wp_enqueue_style( 'bts_task', plugins_url('css/bts_task.css', __FILE__));
  512. wp_enqueue_script( 'bts_task', plugins_url('js/bts_task.js', __FILE__), array( 'jquery' , 'bts' ));
  513. wp_enqueue_script('mustache', plugins_url('js/mustache.min.js', __FILE__), array('jquery'));
  514. wp_localize_script('bts_task','bts_task1',array(
  515. 'ajax_url' => admin_url( 'admin-ajax.php' ),
  516. 'nonce' => wp_create_nonce('bts_task'),
  517. 'week_id' => $this->bts_week_id,
  518. 'bts_job_start' => $this->bts_job_start,
  519. 'bts_job_finish' => $this->bts_job_finish,
  520. ) );
  521. }
  522. private function register_feedback_card_js_css()
  523. {
  524. global $pagename;
  525. if ($pagename != 'feedback_card'){
  526. return;
  527. }
  528. $this->bts_job_start = get_query_var( 'bts_job_start' );
  529. $this->bts_job_finish = get_query_var( 'bts_job_finish' );
  530. $this->bts_week_id = get_query_var('bts_week_id');
  531. wp_enqueue_style( 'feedback_card', plugins_url('css/feedback_card.css', __FILE__));
  532. wp_enqueue_script( 'feedback_card', plugins_url('js/feedback_card.js', __FILE__), array( 'jquery' , 'bts' ));
  533. wp_enqueue_script('mustache', plugins_url('js/mustache.min.js', __FILE__), array('jquery'));
  534. wp_localize_script('feedback_card','feedback_card',array(
  535. 'ajax_url' => admin_url( 'admin-ajax.php' ),
  536. 'nonce' => wp_create_nonce('feedback_card'),
  537. 'week_id' => $this->bts_week_id,
  538. 'bts_job_start' => $this->bts_job_start,
  539. 'bts_job_finish' => $this->bts_job_finish,
  540. ) );
  541. }
  542. private function register_xeroc_js_css(){
  543. global $pagename;
  544. if ($pagename != 'xeroc'){
  545. return;
  546. }
  547. wp_enqueue_style( 'bts_xeroc', plugins_url('css/xeroc.css', __FILE__));
  548. wp_enqueue_script( 'bts_xeroc', plugins_url('js/xeroc.js', __FILE__), array( 'jquery' , 'bts' ));
  549. wp_enqueue_script('mustache', plugins_url('js/mustache.min.js', __FILE__), array('jquery'));
  550. wp_enqueue_script('scrollintoview', plugins_url('js/scrollintoview.js', __FILE__), array('jquery'));
  551. global $wp_scripts;
  552. wp_enqueue_script('jquery-ui-datepicker');
  553. $url = plugins_url('jquery-ui-1.11.4.theme/jquery-ui.min.css', __FILE__);
  554. wp_enqueue_style('jquery-ui-smoothness', $url, false, null);
  555. }
  556. // Usage: `wp sync_users --mininterval=123
  557. public function cli_sync_user($args = array(), $assoc_args = array()){
  558. $arguments = wp_parse_args( $assoc_args, array(
  559. 'mininterval' => 86400,
  560. 'employeeonly' => false,
  561. 'clientsonly' => false,
  562. ) );
  563. $this->XeroOauth2->sync_users($arguments['mininterval'], $arguments['employeeonly'], $arguments['clientsonly']);
  564. return;
  565. }
  566. public function cli_refresh_token($args = array(), $assoc_args = array()){
  567. $this->XeroOauth2->refresh_token(true);
  568. $date = new \DateTime("now", new \DateTimeZone('Australia/Sydney') );
  569. $strDate = $date->format('Y-m-d H:i:s');
  570. echo "refresh_token at: $strDate Sydney time \n";
  571. echo "accessToken: " . $this->XeroOauth2->storage->getAccessToken() . "\n";
  572. echo "refreshToken: " . $this->XeroOauth2->storage->getRefreshToken() . "\n" ;
  573. echo "expires: " . $this->XeroOauth2->storage->tokenExpiresHumanRedable() . "\n" ;
  574. }
  575. public function cli_email_jobs($args = array(), $assoc_args = array()){
  576. $users = get_users(array('role' => 'staff'));
  577. foreach ($users as $u){
  578. $n = new UserJob($u->user_login);
  579. $resp = $n->list_jobs_by_staff("2019-07-22 00:00:00", "2019-07-28 23:59:59");
  580. if ($resp['status']=='success' && $resp['job_count'] >0 ){
  581. if( $u->user_login != "9aa3308e-cc19-4c21-a110-f2c6abec4337" )
  582. continue;
  583. $msg = sprintf("Staff = %s, Login=%s, email=%s Job=%d\n", $u->display_name, $u->user_login, $u->user_email, $resp['job_count']);
  584. echo $msg;
  585. $this->send_email_with_job_link($u, "2019-07-22", "2019-07-28");
  586. }
  587. }
  588. return;
  589. }
  590. public function cli_produce_invoice($args = array(), $assoc_args = array())
  591. {
  592. $users = get_users(array('role' => 'client'));
  593. foreach ($users as $u)
  594. {
  595. $pay = get_user_meta($u->id, 'payment',true);
  596. echo sprintf("%s: %s\n", $u->display_name, $pay);
  597. }
  598. }
  599. public function cli_dev_change_ndis_price ($args = array(), $assoc_args = array())
  600. {
  601. echo "list ndis prices\n";
  602. $nd = new NdisPrice();
  603. echo "get tos array\n";
  604. $tosarray = $nd->get_tos_array();
  605. $map = array();
  606. foreach($tosarray as $item){
  607. $map[$item->code] = $item->id;
  608. if ( $item->year ==20200325) {
  609. $newid = $nd->get_id_by_tos($item->code, 20200701);
  610. if ($newid != 0){
  611. echo "UPDATE wp1m_acare_ts SET tos=$newid WHERE tos= $item->id and id > 0 and start>='2020-07-01 00:00:00'; \n" ;
  612. }
  613. }
  614. //echo "UPDATE wp1m_acare_ts SET tos_id=$item->id WHERE tos='$item->code' and id > 0; \n";
  615. }
  616. return;
  617. $sql = "SELECT * FROM $this->table_name WHERE start>='2020-03-25 00:00:00' order by start ASC ,staff ASC ";
  618. echo $sql . "\n";
  619. $jobs = $this->db->get_results($sql);
  620. foreach ($jobs as $job) {
  621. echo "id= ". $job->id . " tos= ". $job->tos . " start= " . $job->start . "\n";
  622. }
  623. }
  624. private function send_email_with_job_link($staff, $start, $finish)
  625. {
  626. $message = file_get_contents(plugin_dir_path(__FILE__) . "/html/email_job.html");
  627. $message = str_ireplace("{{display_name}}", $staff->display_name, $message);
  628. $message = str_ireplace("{{user_login}}", $staff->user_login, $message);
  629. $message = str_ireplace("{{job_start}}", $start, $message);
  630. $message = str_ireplace("{{job_finish}}", $finish, $message);
  631. $headers = ['Bcc: patrick@biukop.com.au, timesheet@acaresydney.com.au'];
  632. $subject = $staff->display_name . " Job arrangement $start ~ $finish";
  633. //wp_mail("sp@lawipac.com", $subject, $message, $headers);
  634. //wp_mail("timesheet@acaresydney.com.au", $subject, $message, $headers);
  635. wp_mail($staff->user_email, $subject, $message, $headers);
  636. }
  637. private function send_email_feedback_url($client)
  638. {
  639. $message = file_get_contents(plugin_dir_path(__FILE__) . "/html/email_feedback_url.html");
  640. $message = str_ireplace("{{display_name}}", $client->display_name, $message);
  641. $message = str_ireplace("{{user_login}}", $client->user_login, $message);
  642. $headers = ['Bcc: helenwang41@hotmail.com, timesheet@acaresydney.com.au'];
  643. $subject = $client->display_name . " Feedback Link";
  644. wp_mail( "helen@acaresydney.com.au", $subject, $message, $headers);
  645. //wp_mail( "patrick@biukop.com.au", $subject, $message, $headers);
  646. }
  647. public function bts_staff_item($attr){
  648. return $this->template('staff_item', 'staff.html');
  649. }
  650. public function bts_client_item($attr){
  651. return $this->template('client_item', 'client.html');
  652. }
  653. public function bts_job_item($attr){
  654. $html =$this->template('job_item', 'job.html');
  655. //$html = str_replace('[bts-tos-options]', $this->bts_tos_options([]), $html);
  656. $html = do_shortcode($html);
  657. return $html;
  658. }
  659. public function bts_rate_options($attr){
  660. $result = "<select> \n";
  661. $options = get_option('bts_payitem_earnings_rate');
  662. foreach($options as $o){
  663. if ($o['CurrentRecord'] !='true')
  664. continue;
  665. if ($o['RateType'] != 'RATEPERUNIT')
  666. continue;
  667. if (stripos($o['TypeOfUnits'],'hour') != false) //unit contains hour Hour
  668. continue;
  669. $result.=sprintf("<option value='%s'> $%3.2f-%s</option>",
  670. $o['EarningsRateID'], $o['RatePerUnit'], $o['Name']);
  671. }
  672. $result .="</select>";
  673. return $result;
  674. }
  675. private function get_rate_name_by_id($id)
  676. {
  677. $options = get_option('bts_payitem_earnings_rate');
  678. foreach($options as $o){
  679. if ( $o['EarningsRateID'] == $id )
  680. return sprintf("$%3.2f-%s", $o['RatePerUnit'], $o['Name']);
  681. }
  682. }
  683. public function bts_select_staff($attr){
  684. $result = "<select> \n";
  685. $staff = $this->get_people_by_role('staff');
  686. foreach ($staff as $u){
  687. $result .= sprintf("<option value=%s> %s</option>", $u->user_login, $u->first_name . " " . $u->last_name);
  688. }
  689. $result .="</select>";
  690. return $result;
  691. }
  692. public function bts_select_client($attr){
  693. $result = "<select> \n";
  694. $staff = $this->get_people_by_role('client');
  695. foreach ($staff as $u){
  696. $result .= sprintf("<option value=%s> %s</option>", $u->user_login, $u->display_name);
  697. }
  698. $result .="</select>";
  699. return $result;
  700. }
  701. public function bts_type_of_service($attr){
  702. $n = new NdisPrice(2019);
  703. return $n->get_html();
  704. }
  705. public function bts_user_name($attr)
  706. {
  707. $user = wp_get_current_user();
  708. return $user->display_name;
  709. }
  710. public function bts_staff_job_summary($attr)
  711. {
  712. $result ="<span>
  713. If there is more than one job, please click on 'confirm' to make sure it is included in your payment.
  714. </span>";
  715. return $result;
  716. }
  717. public function bts_feedback_card($attr)
  718. {
  719. return $this->template('bts_feedback_card', 'feedback_card.html');
  720. }
  721. public function bb_timesheet_canvas($attr)
  722. {
  723. return file_get_contents(plugin_dir_path(__FILE__) . "/html/timesheet.html");
  724. }
  725. public function bts_staff_hours_template($attr){
  726. return $this->template('bts_staff_hours_template', 'bts_staff_hours_template.html');
  727. }
  728. public function bts_client_invoice_template($attr){
  729. return $this->template('bts_client_invoice_template', 'bts_client_invoice_template.html');
  730. }
  731. public function bts_csv_template($attr){
  732. return $this->template('bts_csv_template', 'bts_csv_template.html');
  733. }
  734. public function bts_invoiced_client($attr)
  735. {
  736. $attr = shortcode_atts([
  737. 'preferred' => 'true',
  738. ], $attr);
  739. $result = "";
  740. $users = $users = get_users(array('role' => 'client'));
  741. $row = <<<ZOT
  742. <tr id="nameonly_%s" data-client-id='%s' class="invoice_nameonly_row %s">
  743. <td class="client_nameonly" data-client-id='%s'>
  744. <i class="vc_tta-icon fa fa-plus-square"></i> %s
  745. </td>
  746. </tr>
  747. <tr id="dummyui_%s" data-client-id='%s' class="invoice_nameonly_row_dummy %s">
  748. <td class="client_nameonly" data-client-id='%s'>
  749. <i class="vc_tta-icon fa fa-plus-square"></i> %s is loading .... please wait...
  750. </td>
  751. </tr>
  752. ZOT;
  753. foreach ($users as $u)
  754. {
  755. $payment = get_user_meta($u->ID, 'payment', true);
  756. if ( $attr['preferred'] == 'true' ){
  757. if( $payment == 'invoice' ){
  758. $invoice_preferred = "invoice_preferred";
  759. $result .= sprintf($row,
  760. $u->user_login, $u->user_login, $invoice_preferred, $u->user_login, $u->display_name,
  761. $u->user_login, $u->user_login, $invoice_preferred, $u->user_login, $u->display_name);
  762. }
  763. }else{
  764. $invoice_preferred = "";
  765. if( $payment != 'invoice' ){
  766. $result .= sprintf($row,
  767. $u->user_login, $u->user_login, $invoice_preferred, $u->user_login, $u->display_name,
  768. $u->user_login, $u->user_login, $invoice_preferred, $u->user_login, $u->display_name);
  769. }
  770. }
  771. }
  772. return $result;
  773. }
  774. //generate template based on html file
  775. private function template($id, $file)
  776. {
  777. $text = '<script id="' . $id .'" type="text/x-biukop-template">';
  778. $text .= file_get_contents(plugin_dir_path(__FILE__) . "/html/$file");
  779. $text .= '</script>';
  780. return $text;
  781. }
  782. function list_staff(){
  783. check_ajax_referer('acaresydney');
  784. // Handle the ajax request
  785. $response = array(
  786. 'status' =>'error',
  787. 'users' => [],
  788. );
  789. //search all users that are staff
  790. $staffq = new \WP_User_Query(array('role'=>'staff','meta_key'=>'first_name', 'orderby'=>'meta_value', 'order'=>'ASC'));
  791. $staff = $staffq->get_results();
  792. if (! empty($staff)){
  793. $response['status'] = 'success';
  794. foreach( $staff as $s){
  795. $response['users'][] = array(
  796. 'login' => $s->user_login,
  797. 'firstname'=> $s->first_name,
  798. 'lastname'=> $s->last_name,
  799. 'display_name' => str_replace("/", "/ ", $s->display_name),
  800. 'mobile'=> get_user_meta($s->ID, 'mobile', true),
  801. 'email'=> $s->user_email,
  802. 'wages'=> 0,
  803. 'hour' => 0 ,
  804. 'OT' => 0 ,
  805. 'petrol'=> 0 ,
  806. 'rating'=> 0,
  807. 'unconfirmedjob'=> 0,
  808. );
  809. }
  810. }
  811. wp_send_json($response);
  812. wp_die();
  813. }
  814. function list_client(){
  815. check_ajax_referer('acaresydney');
  816. $user = wp_get_current_user();
  817. // Handle the ajax request
  818. $response = array(
  819. 'status' =>'error',
  820. 'users' => [],
  821. 'role' => $user,
  822. );
  823. //search all users that are staff
  824. $clientq = new \WP_User_Query(array('role'=>'client', 'meta_key'=>'first_name', 'orderby'=>'meta_value', 'order'=>'ASC'));
  825. $client = $clientq->get_results();
  826. if (! empty($client)){
  827. $response['status'] = 'success';
  828. foreach( $client as $s){
  829. $data_item = array(
  830. 'login' => $s->user_login,
  831. 'firstname'=> $s->first_name,
  832. 'lastname'=> $s->last_name,
  833. 'display_name'=> $s->display_name,
  834. 'mobile'=> get_user_meta($s->ID, 'mobile', true),
  835. 'email'=> $s->user_email,
  836. 'account'=> get_user_meta($s->ID, 'account', true),
  837. 'address' => get_user_meta($s->ID, 'address', true),
  838. 'payment'=>get_user_meta($s->ID, 'payment', true),
  839. 'rating'=> 0,
  840. 'unconfirmedjob'=> 0,
  841. );
  842. $response['users'][] = $data_item;
  843. }
  844. }
  845. wp_send_json($response);
  846. wp_die();
  847. }
  848. //ajax
  849. function list_tos() {
  850. check_ajax_referer('acaresydney');
  851. // Handle the ajax request
  852. $response = array(
  853. 'status' =>'success',
  854. 'tos' => [],
  855. );
  856. $price = $this->get_ndis_price();
  857. $response['tos']= $price->get_tos_array();
  858. wp_send_json($response);
  859. }
  860. private function get_people_by_role($role){
  861. //search all users that are staff
  862. $staffq = new \WP_User_Query(array('role'=>$role, 'meta_key'=>'first_name', 'orderby'=>'meta_value', 'order'=>'ASC'));
  863. $staff = $staffq->get_results();
  864. return $staff;
  865. }
  866. //ajax get earnings rates
  867. function earnings_rate()
  868. {
  869. $response= array(
  870. 'status' => 'success',
  871. 'options'=> get_option('bts_payitem_earnings_rate'),
  872. );
  873. wp_send_json($response);
  874. }
  875. //ajax job CRUD
  876. function save_job()
  877. {
  878. check_ajax_referer('acaresydney');
  879. $r = $_POST['record'];
  880. $response = array();
  881. $d = array(
  882. 'tos' => $r['tos'],
  883. 'start' => $r['start'],
  884. 'finish' => $r['finish'],
  885. 'rate' => $r['rate'],
  886. 'staff' => $r['staff'],
  887. 'client' => $r['client'],
  888. 'ack' => $r['ack']=='true'?1:0,
  889. 'rating'=>$r['rating'],
  890. );
  891. //this is an update
  892. if ( isset($r['id']) && trim($r['id']) !='' && is_numeric($r['id'])){
  893. $response['isNew'] = false; //add or update?
  894. $result = $this->db->update($this->table_name, $d, array('id' =>$r['id']));
  895. if ($result !== false && $this->db->last_error == ''){
  896. $d['id'] = $r['id'];
  897. $response['status'] = 'success';
  898. //do data type conversion, string to int
  899. $response['newdata'] = $this->get_ts_record($r['id']);
  900. $response['errors'] = array(); //empty array
  901. }else{
  902. $response['status'] = 'error';
  903. $repsonse['errors'] = array(
  904. 'db' => "network database error" . $this->db->last_error,
  905. );
  906. }
  907. }else{
  908. $response['isNew'] = true;
  909. $result = $this->db->insert($this->table_name, $d);
  910. $lastid = $this->db->insert_id;
  911. if ($result != false && $this->db->last_error == ''){
  912. $response['status'] = 'success';
  913. $response['newdata'] = $this->get_ts_record($lastid);
  914. }else{
  915. $response['status'] = 'error';
  916. $response['errors'] = array(
  917. 'db' => 'network database error ' . $this->db->last_error,
  918. );
  919. }
  920. }
  921. wp_send_json($response);
  922. wp_die();
  923. }
  924. private function get_ts_record($id){
  925. $sql = "SELECT * FROM $this->table_name WHERE id=%d";
  926. $row = $this->db->get_row($this->db->prepare ($sql, array($id)));
  927. $response = [];
  928. if ($row != null){
  929. $response = array(
  930. 'id' => (int)$row->id,
  931. 'tos' => $row->tos,
  932. 'start' => $row->start,
  933. 'finish' => $row->finish,
  934. 'rate' => $row->rate,
  935. 'staff' => $row->staff,
  936. 'client' => $row->client,
  937. 'ack' => (int)$row->ack,
  938. 'rating' =>(int) $row->rating,
  939. );
  940. }
  941. return $response;
  942. }
  943. //ajax delete job
  944. function delete_job(){
  945. check_ajax_referer('acaresydney');
  946. $id = $_POST['jobid'];
  947. $result = $this->db->delete($this->table_name, array('id'=> $id));
  948. $response=array(
  949. 'status' => 'success',
  950. 'id' => $id,
  951. 'action'=> 'delete',
  952. 'error' => '',
  953. );
  954. if ($result == 1){
  955. wp_send_json($response);
  956. }else{
  957. $response['status'] = 'error';
  958. $response['error'] = $this->db->last_error;
  959. wp_send_json($response);
  960. }
  961. wp_die();
  962. }
  963. //ajax email staff their job arrangement
  964. function email_job()
  965. {
  966. check_ajax_referer('acaresydney');
  967. $staff = $_POST['staff'];
  968. $start = $_POST['start'];
  969. $finish = $_POST['finish'];
  970. $response=array(
  971. 'status' => 'success',
  972. 'staff' => $staff,
  973. 'start' => $start,
  974. 'finish' => $finish,
  975. 'error' => '',
  976. 'sent' => false,
  977. 'emailstatus'=>"Bypass (no job)",
  978. );
  979. $u = get_user_by('login', $staff);
  980. if ($this->is_staff($u)){
  981. $n = new UserJob($staff);
  982. $resp = $n->list_jobs_by_staff("$start 00:00:00", "$finish 23:59:59");
  983. if ($resp['status']=='success' && $resp['job_count'] >0 ){
  984. $msg = sprintf("Email to <strong>%s</strong> (with job=%d) \n", $u->user_email, $resp['job_count']);
  985. $this->send_email_with_job_link($u, $start, $finish);
  986. $response['sent'] = true;
  987. $response['emailstatus'] = $msg;
  988. }
  989. }
  990. wp_send_json($response);
  991. }
  992. //ajax email feedback url
  993. function email_feedback_url()
  994. {
  995. check_ajax_referer('acaresydney');
  996. $client = $_POST['client'];
  997. $response=array(
  998. 'status' => 'success',
  999. 'error' => '',
  1000. 'sent' => false,
  1001. 'emailstatus' =>'not sent',
  1002. );
  1003. $u = get_user_by('login', $client);
  1004. if ($this->is_client($u)){
  1005. $status = $this->send_email_feedback_url($u);
  1006. $response['emailstatus'] = $status;
  1007. }
  1008. wp_send_json($response);
  1009. }
  1010. //ajax browse job with different filters
  1011. function list_job(){
  1012. check_ajax_referer('acaresydney');
  1013. $start = $_POST['start'] . " 00:00:00";
  1014. $finish = $_POST['finish']. " 23:59:59";
  1015. $response = array(
  1016. 'status'=>'success',
  1017. 'jobs' => [],
  1018. );
  1019. $sql = "SELECT * FROM $this->table_name WHERE start>='%s' and start <='%s' order by start ASC ,staff ASC";
  1020. $query = $this->db->prepare ($sql, array($start, $finish));
  1021. $response['sql'] = $query;
  1022. $jobs = $this->db->get_results($query);
  1023. $calendar = get_option('bts_pay_roll_calendar');
  1024. if (! empty($jobs)){
  1025. $response['status'] = 'success';
  1026. foreach( $jobs as $s){
  1027. $item = array(
  1028. 'id' => $s->id,
  1029. 'tos' => $s->tos,
  1030. 'start'=> $s->start,
  1031. 'finish'=> $s->finish,
  1032. 'rate'=> $s->rate,
  1033. 'staff'=> $s->staff,
  1034. 'client'=> $s->client,
  1035. 'ack' => $s->ack,
  1036. 'rating' =>$s->rating,
  1037. );
  1038. $response['jobs'][] = $item;
  1039. }
  1040. }
  1041. wp_send_json($response);
  1042. wp_die();
  1043. }
  1044. public function list_job_by_staff()
  1045. {
  1046. check_ajax_referer('acaresydney');
  1047. $start = $_POST['start'];
  1048. $finish = $_POST['finish'];
  1049. //$start="2019-07-01 00:00:00";
  1050. //$finish="2019-07-14 23:59:59";
  1051. $user = wp_get_current_user();// should be staff;
  1052. if ( $this->is_staff($user) || $this->is_admin($user) ){
  1053. $n = new UserJob($user->user_login);
  1054. $response = $n->list_jobs_by_staff($start, $finish);
  1055. wp_send_json($response);
  1056. }else{
  1057. $response = array(
  1058. 'status' => 'error',
  1059. 'errmsg' => 'invalid access',
  1060. 'user' => $user,
  1061. );
  1062. wp_send_json($response);
  1063. }
  1064. wp_die();
  1065. }
  1066. public function list_job_by_client()
  1067. {
  1068. check_ajax_referer('acaresydney');
  1069. $start = $_POST['start'];
  1070. $finish = $_POST['finish'];
  1071. //$start="2019-07-01 00:00:00";
  1072. //$finish="2019-07-14 23:59:59";
  1073. $user = wp_get_current_user();// should be staff;
  1074. if ( $this->is_client($user) || $this->is_admin($user) ){
  1075. $n = new UserJob($user->user_login);
  1076. $response = $n->list_jobs_by_client($start, $finish);
  1077. wp_send_json($response);
  1078. }else{
  1079. $response = array(
  1080. 'status' => 'error',
  1081. 'errmsg' => 'invalid access',
  1082. 'user' => $user,
  1083. );
  1084. wp_send_json($response);
  1085. }
  1086. wp_die();
  1087. }
  1088. private function is_staff($user)
  1089. {
  1090. return ($user->ID !=0 && in_array('staff', $user->roles));
  1091. }
  1092. private function is_client($user)
  1093. {
  1094. return ($user->ID !=0 && in_array('client', $user->roles));
  1095. }
  1096. private function is_admin($user)
  1097. {
  1098. $allowed_roles = array('administrator', 'admin');
  1099. if( array_intersect($allowed_roles, $user->roles ) ) {
  1100. return true;
  1101. }
  1102. }
  1103. private function is_superadmin(): bool
  1104. {
  1105. $current = wp_get_current_user();
  1106. return $current->ID == 1;
  1107. }
  1108. private function is_accountant($user)
  1109. {
  1110. return ($user->ID !=0 && in_array('accountant', $user->roles));
  1111. }
  1112. public function staff_ack_job()
  1113. {
  1114. check_ajax_referer('acaresydney');
  1115. $jobs = $_POST['jobs'];
  1116. $response = array(
  1117. 'status'=>'success',
  1118. 'jobs'=>$jobs,
  1119. );
  1120. $yes=[];
  1121. $no=[];
  1122. foreach($jobs as $job){
  1123. if ( $job['ack'] == "true")
  1124. $yes[] =(int) $job['id'];
  1125. else
  1126. $no[] = (int) $job['id'];
  1127. }
  1128. $err = $this->ack_multiple_job($yes, $no);
  1129. if ($this->db->last_error !='')
  1130. {
  1131. $response['status']= 'error';
  1132. $response['err_msg']= $err;
  1133. }
  1134. $response['yes'] = $yes;
  1135. $response['no'] = $no;
  1136. wp_send_json($response);
  1137. wp_die();
  1138. }
  1139. public function ack_multiple_job($yes, $no)
  1140. {
  1141. $str_yes_ids = implode(",", $yes);
  1142. $str_no_ids = implode(",", $no);
  1143. $err = "";
  1144. if (count($yes) >0 ){
  1145. $sql = "UPDATE $this->table_name SET ack=1 WHERE id IN ( $str_yes_ids) ; ";
  1146. $r = $this->db->get_results($sql);
  1147. $err = $this->db->last_error;
  1148. }
  1149. if (count($no) >0 ){
  1150. $sql = "UPDATE $this->table_name SET ack=0 WHERE id IN ( $str_no_ids) ; ";
  1151. $r = $this->db->get_results($sql);
  1152. $err .= $this->db->last_error;
  1153. }
  1154. return $err;
  1155. }
  1156. public function client_ack_job()
  1157. {
  1158. check_ajax_referer('acaresydney');
  1159. $job_id = $_POST['job_id'];
  1160. $rating = $_POST['rating'];
  1161. $response = array(
  1162. 'status'=>'success',
  1163. );
  1164. $sql= "UPDATE $this->table_name SET rating=%d WHERE id = %d ; ";
  1165. $sql= $this->db->prepare($sql, array($rating, $job_id));
  1166. $result = $this->db->get_results($sql);
  1167. $response['rating'] = (int) $rating;
  1168. if ($this->db->last_error !='')
  1169. {
  1170. $response['status']= 'error';
  1171. $response['err_msg']= $this->db->last_error;
  1172. $response['rating'] = 0;
  1173. }
  1174. wp_send_json($response);
  1175. }
  1176. public function get_timesheet_from_xero()
  1177. {
  1178. check_ajax_referer('acaresydney');
  1179. //check if we need sync
  1180. $sync = $_POST['sync'];
  1181. if ($sync != "true")
  1182. $sync = false;
  1183. else
  1184. $sync = true;
  1185. //set up payroll calendar
  1186. $pc = $this->XeroOauth2->get_payroll_calendar();
  1187. $start = $pc->getStartDateAsDate()->format('Y-m-d');
  1188. $finish = new \DateTime($start);
  1189. $finish = $finish->modify("+13 days")->format('Y-m-d');
  1190. $paydate = $pc->getPaymentDateAsDate()->format('Y-m-d');
  1191. //prepare response
  1192. $response = array(
  1193. 'status' => 'success',
  1194. 'payroll_calendar' => array(
  1195. 'start' => $start,
  1196. 'finish' => $finish,
  1197. 'paydate'=> $paydate,
  1198. ),
  1199. );
  1200. $xx = new \Biukop\XeroOauth2Timesheet($this->XeroOauth2, $finish);
  1201. $local_ts = $this->create_timesheet_from_db($start, $finish);
  1202. if ($sync){
  1203. $xx->set_local_timesheet($local_ts);
  1204. $xx->save_to_xero();
  1205. $xx->refresh_remote();
  1206. }
  1207. $days=[];
  1208. $d = new \DateTime($start);
  1209. for ($i=1; $i<=14; $i++){
  1210. $days["days_$i"] = $d->format("d/M");
  1211. $d->modify("+1 day");
  1212. }
  1213. $lines=[];
  1214. foreach ($local_ts as $staff_login => $details)
  1215. {
  1216. $item = array(
  1217. 'staff_name' => $this->get_user_name_by_login ($staff_login),
  1218. 'staff_id' => $staff_login,
  1219. 'Xero_Status' => 'Empty',
  1220. 'rowspan' =>1,
  1221. 'local_total' =>0,
  1222. 'remote_total'=>0,
  1223. 'ratetype' => [],
  1224. );
  1225. $buddy = $xx->get_buddy_timesheets($staff_login, $start, $finish);
  1226. if ($buddy != NULL){
  1227. $item['Xero_Status'] = $buddy->getStatus();
  1228. }else{
  1229. $item['Xero_Status'] = "Not Exist";
  1230. }
  1231. foreach($details as $rate => $hours){
  1232. $item['rowspan'] ++;
  1233. $ratetype_item=[];
  1234. $ratetype_item['rate_name'] = $this->get_rate_name_by_id($rate);
  1235. //for local
  1236. for ($i=1; $i<=14; $i++)
  1237. {
  1238. $ratetype_item["local_$i"] = $hours[$i-1];
  1239. $ratetype_item["local_$i"] = number_format($ratetype_item["local_$i"], 2);
  1240. $item['local_total'] += $hours[$i-1];
  1241. }
  1242. $item['local_total'] = number_format($item['local_total'], 2);
  1243. //for remote
  1244. if ( $buddy != NULL )
  1245. {
  1246. $remote_lines = $buddy->getTimesheetLines();
  1247. foreach($remote_lines as $rl)
  1248. {
  1249. if ( $rl->getEarningsRateID() == $rate){
  1250. for ($i=1; $i<=14; $i++)
  1251. {
  1252. $ratetype_item["xero_$i"] = $rl->getNumberOfUnits()[$i-1];
  1253. $item['remote_total'] += $rl->getNumberOfUnits()[$i-1];
  1254. if ($ratetype_item["xero_$i"] != $ratetype_item["local_$i"]){
  1255. $ratetype_item["xero_{$i}_mismatch"] = "mismatch";
  1256. }
  1257. }
  1258. break;//we found it
  1259. }
  1260. }
  1261. }
  1262. $item['ratetype'][] = $ratetype_item;
  1263. }
  1264. $item = array_merge($item, $days);
  1265. $lines[]=$item;
  1266. }
  1267. $ts = json_decode(file_get_contents(dirname(__FILE__) . "/sample/timesheets.json"));
  1268. $response['ts'] = $ts;
  1269. $response['lines'] = $lines;
  1270. wp_send_json($response);
  1271. }
  1272. public function approve_all_timesheet()
  1273. {
  1274. check_ajax_referer('acaresydney');
  1275. //set up payroll calendar
  1276. $pc = $this->XeroOauth2->get_payroll_calendar();
  1277. $start = $pc->getStartDate()->format('Y-m-d');
  1278. $finish = new \DateTime($start);
  1279. $finish = $finish->modify("+13 days")->format('Y-m-d');
  1280. $paydate = $pc->getPaymentDate()->format('Y-m-d');
  1281. //prepare response
  1282. $response = array(
  1283. 'status' => 'success',
  1284. 'payroll_calendar' => array(
  1285. 'start' => $start,
  1286. 'finish' => $finish,
  1287. 'paydate'=> $paydate,
  1288. ),
  1289. );
  1290. $xx = new \Biukop\TimeSheet($this->XeroOauth2, $finish);
  1291. $xx->approve_all();
  1292. wp_send_json($response);
  1293. }
  1294. static public function get_user_name_by_login($login)
  1295. {
  1296. $user = get_user_by('login', $login);
  1297. if ($user->ID !=0 )
  1298. return $user->display_name;
  1299. else
  1300. return "Invalid Name";
  1301. }
  1302. //ajax
  1303. public function get_invoice_item()
  1304. {
  1305. check_ajax_referer('acaresydney');
  1306. $client = $_POST['client'];
  1307. //$client = "8cb3d205-6cdc-4187-ae39-9216923dd86d";
  1308. $start = $_POST['start']; //2019-07-01';
  1309. $finish= $_POST['finish'];//2019-07-31';
  1310. $sql = "SELECT * from $this->table_name WHERE tos != '00_000_0000_0_0' and start>='$start 00:00:00' and start<='$finish 23:59:59' and client='$client' ORDER BY start";
  1311. $rows = $this->db->get_results($sql);
  1312. $response=[
  1313. 'status' =>'success',
  1314. 'client_login' => $client,
  1315. 'client_name' => $this->get_user_name_by_login($client),
  1316. 'jobs'=>[],
  1317. 'err'=>''
  1318. ];
  1319. $price = $this->get_ndis_price();
  1320. $summary=[];// by ndis code
  1321. foreach($rows as $r){
  1322. $quantity = $this->get_job_hours($r->start, $r->finish);
  1323. $unit = $price->get_tos_unit($r->tos);
  1324. if ($unit != "Hour")
  1325. {
  1326. $quantity = 1;
  1327. }
  1328. $unitprice = $price->get_tos_price($r->tos);
  1329. $description = $this->get_job_description_for_invoice($price, $r);
  1330. $data = [
  1331. 'tos' => $price->get_tos_full_str($r->tos),
  1332. 'start' => $r->start,
  1333. 'finish' => $r->finish,
  1334. 'hours' => $quantity,
  1335. 'unitprice'=> $price->get_tos_price($r->tos),
  1336. 'staff_name' => $this->get_user_name_by_login($r->staff),
  1337. 'price' => sprintf("%.2f", $quantity * $unitprice),
  1338. ];
  1339. $response['jobs'][] = $data;
  1340. $summary[$r->tos] += $quantity;
  1341. }
  1342. if (count($response['jobs']) ==0)
  1343. {
  1344. $response['nojob'] = true;
  1345. }
  1346. $response['overall_total'] = 0;
  1347. foreach ($summary as $key => $val)
  1348. {
  1349. $unitprice = $price->get_tos_price($key);
  1350. $tos_total = $val * $price->get_tos_price($key);
  1351. $response['overall_total'] += $tos_total;
  1352. $response['summary'][] = array(
  1353. 'ndis' => $price->get_tos_ndis_code($key),
  1354. 'tos' => $price->get_tos_full_str($key),
  1355. 'unitprice'=> $unitprice,
  1356. 'Hours'=> sprintf("%0.2f", $val),
  1357. 'tos_total'=> sprintf("%0.2f", $tos_total),
  1358. );
  1359. }
  1360. if (count($summary) > 0){
  1361. $response['has_summary'] = true;
  1362. $response['overall_total'] = sprintf("%0.2f", $response['overall_total'] );
  1363. }
  1364. wp_send_json($response);
  1365. }
  1366. public function create_invoice_in_xero()
  1367. {
  1368. check_ajax_referer('acaresydney');
  1369. $client = $_POST['client'];
  1370. //$client = "8cb3d205-6cdc-4187-ae39-9216923dd86d";
  1371. $start = $_POST['start']; //2019-07-01';
  1372. $finish= $_POST['finish'];//2019-07-31';
  1373. //
  1374. $response=[
  1375. 'status'=>'success',
  1376. 'invoice_number' => '',
  1377. 'err'=> '',
  1378. ];
  1379. try{
  1380. $invoice = $this->create_invoice_by_client($client, $start, $finish);
  1381. $response['invoice_number'] = sprintf("<a href='https://go.xero.com/AccountsReceivable/Edit.aspx?InvoiceID=%s' target='_blank'>%s</a>",
  1382. $invoice->getInvoiceId(), $invoice->getInvoiceNumber());
  1383. }catch(\Exception $e){
  1384. $response['status'] = 'error';
  1385. $response['err'] = "XERO Invoice Error";
  1386. }
  1387. wp_send_json($response);
  1388. }
  1389. public function create_invoice_by_client($client_login, $start, $finish)
  1390. {
  1391. $user = get_user_by('login', $client_login);
  1392. if ( !$this->is_client($user) )
  1393. return NULL;
  1394. // $payment = get_user_meta($user->ID, "payment", true);
  1395. // if ($payment != "invoice")
  1396. // return NULL;
  1397. // tos ==1 is code 00_000_0000_0_0 = NOT NDIS
  1398. $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";
  1399. $rows = $this->db->get_results($sql);
  1400. $api = $this->XeroOauth2->get_accounting_instance();
  1401. //crate invoice
  1402. $invoice = new \XeroAPI\XeroPHP\Models\Accounting\Invoice;
  1403. $contacts = $api->getContact($this->XeroOauth2->getTenantId(), $client_login);
  1404. $now = new \DateTime();
  1405. $due = new \DateTime();
  1406. $due->modify("+14 days");
  1407. $invoice->setType("ACCREC");
  1408. $invoice->setStatus("DRAFT");
  1409. $invoice->setContact($contacts[0]);
  1410. $invoice->setDate($now);
  1411. $invoice->setDueDate($due);
  1412. $price = new NdisPrice(2019);//always 2019 until its being changed;
  1413. $lineItems=[];
  1414. foreach($rows as $r){
  1415. $quantity = $this->get_job_hours($r->start, $r->finish);
  1416. $unit = $price->get_tos_unit($r->tos);
  1417. if ($unit != "Hour")
  1418. {
  1419. $quantity = 1;
  1420. }
  1421. $unitprice = $price->get_tos_price($r->tos);
  1422. $description = $this->get_job_description_for_invoice($price, $r);
  1423. $lineItem = new \XeroAPI\XeroPHP\Models\Accounting\LineItem;
  1424. $lineItem->setDescription($description);
  1425. $lineItem->setQuantity($quantity);
  1426. $lineItem->setUnitAmount($unitprice);
  1427. $lineItem->setAccountCode(220);
  1428. $lineItem->setTaxType("EXEMPTOUTPUT");
  1429. $lineItems[]= $lineItem;
  1430. }
  1431. $invoice->setLineItems($lineItems);
  1432. //prevent zero lineitems
  1433. if ( count($invoice->getLineItems()) >0 ){
  1434. $to_save[] = $invoice;
  1435. $invoices = new \XeroAPI\XeroPHP\Models\Accounting\Invoices;
  1436. $invoices->setInvoices($to_save);
  1437. $result = $api->createInvoices($this->XeroOauth2->getTenantId(), $invoices);
  1438. $createdInvoices = $result->getInvoices();
  1439. return $createdInvoices[0];
  1440. }
  1441. return $invoice;
  1442. }
  1443. public function get_job_description_for_invoice($price, $job)
  1444. {
  1445. $description = sprintf(
  1446. '%s
  1447. [NDIS code: %s]
  1448. Time: %s - %s
  1449. By Carer : %s',
  1450. $price->get_tos_str($job->tos),
  1451. $price->get_tos_ndis_code($job->tos),
  1452. $job->start,
  1453. $job->finish,
  1454. $this->get_user_name_by_login($job->staff)
  1455. );
  1456. return $description;
  1457. }
  1458. public function create_timesheet_from_db($start, $finish){
  1459. $results = [];
  1460. $sql = "SELECT * from $this->table_name WHERE start>='$start 00:00:00' and start<='$finish 23:59:59'";
  1461. $rows = $this->db->get_results($sql);
  1462. foreach ($rows as $r){
  1463. if (!array_key_exists($r->staff, $results)){
  1464. $results[$r->staff] = [];
  1465. }
  1466. if (!array_key_exists($r->rate, $results[$r->staff])){
  1467. $results[$r->staff][$r->rate] = [];
  1468. for ($i=0; $i<14; $i++){
  1469. $results[$r->staff][$r->rate][$i] = 0; //14 days init to 0;
  1470. }
  1471. }
  1472. $idx = $this->convert_date_to_idx($r->start, $start, $finish);
  1473. if ($idx >=0 && $idx <=13){
  1474. $hours = $this->get_job_hours($r->start, $r->finish);
  1475. $results[$r->staff][$r->rate][$idx] += $hours;
  1476. //$results[$r->staff][$r->id] = $hours;
  1477. //$results[$r->staff][$r->id . '_index'] = $idx;
  1478. }else{
  1479. $msg = sprintf("ACARE_TS_ERR: found invalid job index for job id=%d, on %s, idx is %d, start=%s, finish=%s\n",
  1480. $r->id, date('Y-m-d'), $idx, $start, $finish);
  1481. $this->log($msg);
  1482. }
  1483. }
  1484. //wp_send_json($results);
  1485. return $results;
  1486. }
  1487. //convert date (no time) to index, 0 day is $start, 13day is finish, -1 is not found
  1488. public function convert_date_to_idx($date, $start, $finish)
  1489. {//start, finish format must be yyyy-mm-dd
  1490. $idx = -1;
  1491. $cur = new \DateTime($date);
  1492. $cur->setTime(0,0,0);//clear time;
  1493. $s = new \DateTime($start);
  1494. $s->setTime(0,0,0);
  1495. $f = new \DateTime($finish);
  1496. $f->setTime(0,0,0);
  1497. if ( $s <= $cur && $cur <=$f ){
  1498. $datediff = date_diff($s,$cur);
  1499. $idx = $datediff->days;
  1500. }
  1501. return $idx;
  1502. }
  1503. private function get_job_hours($start, $finish)
  1504. {
  1505. $hours = 0;
  1506. $s = strtotime($start);
  1507. $f = strtotime($finish);
  1508. $diff = $f- $s;
  1509. $hours = ($diff * 1.0 / 3600); //can be float;
  1510. return sprintf('%0.2f', $hours);
  1511. }
  1512. private function get_job_hours_hh_mm($start, $finish)
  1513. {
  1514. $hours = 0;
  1515. $s = strtotime($start);
  1516. $f = strtotime($finish);
  1517. $diff = $f- $s;
  1518. $hours = floor($diff * 1.0 / 3600); //down to integer
  1519. $minutes = round( (($diff * 1.0) % 3600) / 60) ; //round to integer;
  1520. if ($minutes <10)
  1521. $minutes = "0$minutes";
  1522. return "$hours:$minutes";
  1523. }
  1524. public function cli_feedback_url()
  1525. {
  1526. $users = get_users(array('role'=>'client'));
  1527. foreach($users as $u){
  1528. 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);
  1529. }
  1530. }
  1531. static public function log($msg){
  1532. openlog("ACARE_TS", LOG_PID | LOG_PERROR, LOG_SYSLOG);
  1533. //something wrong, we dont touch it, but give some error here
  1534. syslog(LOG_WARNING, $msg);
  1535. closelog();
  1536. }
  1537. }
  1538. $bb = new AcareOffice();
  1539. if ( defined( 'WP_CLI' ) && WP_CLI ) {
  1540. \WP_CLI::add_command( 'sync_users', array($bb, 'cli_sync_user'));
  1541. \WP_CLI::add_command( 'refresh_token', array($bb, 'cli_refresh_token'));
  1542. // \WP_CLI::add_command( 'email_jobs', array($bb, 'cli_email_jobs'));
  1543. // \WP_CLI::add_command( 'feedback_url', array($bb, 'cli_feedback_url'));
  1544. // \WP_CLI::add_command( 'produce_invoice', array($bb, 'cli_produce_invoice'));
  1545. // \WP_CLI::add_command( 'dev_change_ndis_price', array($bb, 'cli_dev_change_ndis_price'));
  1546. }
  1547. //$bb->class_loader();
  1548. //$bb->create_invoice_by_client("8cb3d205-6cdc-4187-ae39-9216923dd86d", "2019-07-01", "2019-07-31");
  1549. //$idx = $bb->convert_date_to_idx("2019-07-02 14:30:00", "2019-07-01", "2019-07-02");
  1550. //wp_send_json($idx);
  1551. //$bb->create_timesheet_from_db("2019-07-01", "2019-07-14");