| @@ -61,4 +61,22 @@ class NdisPrice{ | |||
| } | |||
| return ""; | |||
| } | |||
| public function get_tos_price($ndis_code) | |||
| { | |||
| foreach ($this->tos as $r){ | |||
| if ($ndis_code == $r->code) | |||
| return (float) $r->price; | |||
| } | |||
| return 0; | |||
| } | |||
| public function get_tos_unit($ndis_code) | |||
| { | |||
| foreach ($this->tos as $r){ | |||
| if ($ndis_code == $r->code) | |||
| return $r->unit; | |||
| } | |||
| return ""; | |||
| } | |||
| } | |||
| @@ -28,6 +28,10 @@ class TimeSheet{ | |||
| // )); | |||
| } | |||
| public function refresh_remote(){ | |||
| $this->get_remote_timesheets(); | |||
| } | |||
| private function get_remote_timesheets() | |||
| { | |||
| $this->remote_timesheets = $this->xero->load('PayrollAU\\Timesheet') | |||
| @@ -80,6 +84,10 @@ class TimeSheet{ | |||
| { | |||
| $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'); | |||
| @@ -107,6 +115,12 @@ class TimeSheet{ | |||
| ->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{ | |||
| @@ -121,6 +135,15 @@ class TimeSheet{ | |||
| $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) | |||
| @@ -138,9 +161,10 @@ class TimeSheet{ | |||
| return $this->warning_timesheets; | |||
| } | |||
| private function approve_all(){ | |||
| public function approve_all(){ | |||
| $to_save=[]; | |||
| foreach ( $this->local_timesheets as $t){ | |||
| foreach ( $this->remote_timesheets as $t){ | |||
| if($t->getStatus() == 'DRAFT'){ | |||
| $t->setDirty('EmployeeID'); | |||
| $t->setDirty('StartDate'); | |||
| $t->setDirty('EndDate'); | |||
| @@ -150,6 +174,7 @@ class TimeSheet{ | |||
| $t->setDirty('TimesheetID'); | |||
| $t->setStatus('APPROVED'); | |||
| $to_save[]=$t; | |||
| } | |||
| } | |||
| $this->xero->saveAll($to_save, false); | |||
| } | |||
| @@ -1,17 +1,32 @@ | |||
| @CHARSET "UTF-8"; | |||
| .blink_me { | |||
| animation: blinker 0.3s linear infinite; | |||
| } | |||
| @keyframes blinker { | |||
| 50% { | |||
| opacity: 0; | |||
| } | |||
| } | |||
| .container{ | |||
| width:100%; | |||
| } | |||
| #cstart, #cfinish, #paydate{ | |||
| color: black; | |||
| font-weight:900; | |||
| #cstart, #cfinish, #paydate, | |||
| #invoice_start, #invoice_finish{ | |||
| width:100%; | |||
| background-color: #EDEDED; | |||
| border:none; | |||
| text-align: center; | |||
| color: green; | |||
| font-weight: 900; | |||
| background: #f0fbff; | |||
| font-size: 1.5em; | |||
| } | |||
| .hidden{ | |||
| display:none; | |||
| } | |||
| @@ -20,6 +35,12 @@ td.sync_detail{ | |||
| padding:0px; | |||
| } | |||
| table.staffhours, | |||
| table.clientinvoice{ | |||
| background-color:white; | |||
| } | |||
| div.invoice_button, | |||
| table.hours{ | |||
| margin-bottom:0px; | |||
| } | |||
| @@ -83,7 +104,7 @@ table.blueTable tfoot .links a{ | |||
| } | |||
| .week1color { | |||
| tr.DRAFT .week1color { | |||
| color: black; | |||
| /* Permalink - use to edit and share this gradient: https://colorzilla.com/gradient-editor/#ebe9f9+0,d8d0ef+50,cec7ec+51,c1bfea+100;Purple+3D+%231 */ | |||
| @@ -95,7 +116,7 @@ table.blueTable tfoot .links a{ | |||
| } | |||
| .week2color { | |||
| tr.DRAFT .week2color { | |||
| color: white; | |||
| /* Permalink - use to edit and share this gradient: https://colorzilla.com/gradient-editor/#627d4d+0,1f3b08+100;Olive+3D */ | |||
| @@ -104,4 +125,57 @@ table.blueTable tfoot .links a{ | |||
| background: -webkit-linear-gradient(top, #627d4d 0%,#1f3b08 100%); /* Chrome10-25,Safari5.1-6 */ | |||
| background: linear-gradient(to bottom, #627d4d 0%,#1f3b08 100%); /* W3C, IE10+, FF16+, Chrome26+, Opera12+, Safari7+ */ | |||
| filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#627d4d', endColorstr='#1f3b08',GradientType=0 ); /* IE6-9 */ | |||
| } | |||
| } | |||
| #staff tr.loading, | |||
| #staff td.loading{ | |||
| text-align:center; | |||
| height:500px; | |||
| } | |||
| td.loading img{ | |||
| margin-top:200px; | |||
| } | |||
| table.hours th, | |||
| table.hours td{ | |||
| text-align: center; | |||
| width:7.14% ; | |||
| } | |||
| .mismatch { | |||
| background-color:yellow; | |||
| color:red; | |||
| font-size:1.3em; | |||
| font-weight:900; | |||
| } | |||
| td.invoice_nubmer{ | |||
| text-align:center; | |||
| } | |||
| img.waiting_invoice_number{ | |||
| height:32px; | |||
| display:none; | |||
| } | |||
| .invoice_nameonly_row{ | |||
| cursor:pointer; | |||
| } | |||
| .invoice_nameonly_row:hover{ | |||
| background-color:yellow; | |||
| } | |||
| .invoice_detail_row th{ | |||
| cursor:pointer; | |||
| } | |||
| .invoice_detail_row th:hover{ | |||
| color:green; | |||
| } | |||
| .invoice_nameonly_row_dummy{ | |||
| color:white; | |||
| background-color: black; | |||
| display:none; | |||
| animation: blinker 0.3s linear infinite; | |||
| } | |||
| @@ -0,0 +1,55 @@ | |||
| <tr id="invoice_{{client_login}}" class="invoice_detail_row"> | |||
| <td class='sync_detail client_invoice' data-client-id='{{client_login}}'> | |||
| <table id="p{{client_login}" data-client-login="{{client_login}}" class="clientinvoice"> | |||
| <thead> | |||
| <tr> | |||
| <th class="client_invoice"><i class="vc_tta-icon fa fa-minus-square"></i> {{client_name}}</th> | |||
| <th class="client_invoice">Service</th> | |||
| <th class="client_invoice">Start</th> | |||
| <th class="client_invoice">Finish</th> | |||
| <th class="client_invoice">Hours</th> | |||
| <th class="client_invoice">Unit Price</th> | |||
| <th class="client_invoice">Staff</th> | |||
| <th class="client_invoice">Price</th> | |||
| <th class="client_invoice">Invoice Number</th> | |||
| </tr> | |||
| </thead> | |||
| <tfoot> | |||
| <tr> | |||
| <td > | |||
| </td> | |||
| <td colspan="7"></td> | |||
| <td class='invoice_button' rowspan="rowspan"> | |||
| <div class="wpb_wrapper" {{#nojob}}style="visibility:hidden;" {{/nojob}}> | |||
| <div class="vc_btn3-container vc_btn3-right invoice_button"> | |||
| <a data-client-login="{{client_login}}" | |||
| class="invoice_button vc_general vc_btn3 vc_btn3-size-md vc_btn3-shape-rounded vc_btn3-style-modern disable_vc_btn3-block vc_btn3-icon-left vc_btn3-color-success" | |||
| href="" title="" target="_blank"><i | |||
| class="vc_btn3-icon fa fa-refresh"></i> Create Invoice {{client_name}} | |||
| </a> | |||
| </div> | |||
| </div> | |||
| </td> | |||
| </tr> | |||
| </tfoot> | |||
| <tbody class="invoiceitems"> | |||
| {{#jobs}} | |||
| <tr> | |||
| <td></td> | |||
| <td>{{{tos}}}</td> | |||
| <td>{{start}}</td> | |||
| <td>{{finish}}</td> | |||
| <td>{{hours}}</td> | |||
| <td>{{unitprice}}</td> | |||
| <td>{{staff_name}}</td> | |||
| <td>{{price}}</td> | |||
| <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"> | |||
| </td> | |||
| </tr> | |||
| {{/jobs}} | |||
| </tbody> | |||
| </table> | |||
| </td> | |||
| </tr> | |||
| @@ -1,68 +1,76 @@ | |||
| {{#lines}} | |||
| <tr> | |||
| <td>{{staff_name}}</td> | |||
| <td>{{rate_name}}</td> | |||
| <td class=sync_detail> | |||
| <table class='hours'> | |||
| <thead id="p{{staff_id}}"> | |||
| <th>{{days_1}}</th> | |||
| <th>{{days_2}}</th> | |||
| <th>{{days_3}}</th> | |||
| <th>{{days_4}}</th> | |||
| <th>{{days_5}}</th> | |||
| <th>{{days_6}}</th> | |||
| <th>{{days_7}}</th> | |||
| <th>{{days_8}}</th> | |||
| <th>{{days_9}}</th> | |||
| <th>{{days_10}}</th> | |||
| <th>{{days_11}}</th> | |||
| <th>{{days_12}}</th> | |||
| <th>{{days_13}}</th> | |||
| <th>{{days_14}}</th> | |||
| </thead> | |||
| <tbody> | |||
| <tr> | |||
| <td class='local week1color' id='a_0'>{{local_1}}</td> | |||
| <td class='local week1color' id='a_1'>{{local_2}}</td> | |||
| <td class='local week1color' id='a_2'>{{local_3}}</td> | |||
| <td class='local week1color' id='a_3'>{{local_4}}</td> | |||
| <td class='local week1color' id='a_4'>{{local_5}}</td> | |||
| <td class='local week1color' id='a_5'>{{local_6}}</td> | |||
| <td class='local week1color' id='a_6'>{{local_7}}</td> | |||
| <td class='local week2color' id='a_7'>{{local_8}}</td> | |||
| <td class='local week2color' id='a_8'>{{local_9}}</td> | |||
| <td class='local week2color' id='a_9'>{{local_10}}</td> | |||
| <td class='local week2color' id='a_10'>{{local_11}}</td> | |||
| <td class='local week2color' id='a_11'>{{local_12}}</td> | |||
| <td class='local week2color' id='a_12'>{{local_13}}</td> | |||
| <td class='local week2color' id='a_13'>{{local_14}}</td> | |||
| </tr> | |||
| <tr> | |||
| <td colspan="14"> | |||
| <div style="text-align: center">To Xero | |||
| <span class="ticon ticon-arrow-down"></span> | |||
| </div> | |||
| </td> | |||
| </tr> | |||
| <tr> | |||
| <td class='xero week1color' id='b_0'>{{xero_1}}</td> | |||
| <td class='xero week1color' id='b_1'>{{xero_2}}</td> | |||
| <td class='xero week1color' id='b_2'>{{xero_3}}</td> | |||
| <td class='xero week1color' id='b_3'>{{xero_4}}</td> | |||
| <td class='xero week1color' id='b_4'>{{xero_5}}</td> | |||
| <td class='xero week1color' id='b_5'>{{xero_6}}</td> | |||
| <td class='xero week1color' id='b_6'>{{xero_7}}</td> | |||
| <td class='xero week2color' id='b_7'>{{xero_8}}</td> | |||
| <td class='xero week2color' id='b_8'>{{xero_9}}</td> | |||
| <td class='xero week2color' id='b_9'>{{xero_10}}</td> | |||
| <td class='xero week2color' id='b_10'>{{xero_11}}</td> | |||
| <td class='xero week2color' id='b_11'>{{xero_12}}</td> | |||
| <td class='xero week2color' id='b_12'>{{xero_13}}</td> | |||
| <td class='xero week2color' id='b_13'>{{xero_14}}</td> | |||
| </tr> | |||
| </tbody> | |||
| </table> | |||
| <tr class="{{Xero_Status}}"> | |||
| <td rowspan='{{rowspan}}'><strong>{{staff_name}}</strong> <br>Total: {{local_total}} hours</td> | |||
| <td>Earnings</td> | |||
| <td class=sync_detail> <table class='hours'> | |||
| <thead id="p{{staff_id}}"> | |||
| <th>{{days_1}}</th> | |||
| <th>{{days_2}}</th> | |||
| <th>{{days_3}}</th> | |||
| <th>{{days_4}}</th> | |||
| <th>{{days_5}}</th> | |||
| <th>{{days_6}}</th> | |||
| <th>{{days_7}}</th> | |||
| <th>{{days_8}}</th> | |||
| <th>{{days_9}}</th> | |||
| <th>{{days_10}}</th> | |||
| <th>{{days_11}}</th> | |||
| <th>{{days_12}}</th> | |||
| <th>{{days_13}}</th> | |||
| <th>{{days_14}}</th> | |||
| </thead> | |||
| </table> | |||
| </td> | |||
| <td style="vertical-align:bottom;">{{Xero_Status}}</td> | |||
| <td rowspan='{{rowspan}}' style="vertical-align:bottom;">{{remote_total}} hours<br><strong>{{Xero_Status}}</strong></td> | |||
| </tr> | |||
| {{#ratetype}} | |||
| <tr> | |||
| <td>{{rate_name}}</td> | |||
| <td class=sync_detail> | |||
| <table class='hours'> | |||
| <tbody> | |||
| <tr> | |||
| <td class='local week1color' id='a_0'>{{local_1}}</td> | |||
| <td class='local week1color' id='a_1'>{{local_2}}</td> | |||
| <td class='local week1color' id='a_2'>{{local_3}}</td> | |||
| <td class='local week1color' id='a_3'>{{local_4}}</td> | |||
| <td class='local week1color' id='a_4'>{{local_5}}</td> | |||
| <td class='local week1color' id='a_5'>{{local_6}}</td> | |||
| <td class='local week1color' id='a_6'>{{local_7}}</td> | |||
| <td class='local week2color' id='a_7'>{{local_8}}</td> | |||
| <td class='local week2color' id='a_8'>{{local_9}}</td> | |||
| <td class='local week2color' id='a_9'>{{local_10}}</td> | |||
| <td class='local week2color' id='a_10'>{{local_11}}</td> | |||
| <td class='local week2color' id='a_11'>{{local_12}}</td> | |||
| <td class='local week2color' id='a_12'>{{local_13}}</td> | |||
| <td class='local week2color' id='a_13'>{{local_14}}</td> | |||
| </tr> | |||
| <tr> | |||
| <td colspan="14"> | |||
| <div style="text-align: center">To Xero | |||
| <span class="ticon ticon-arrow-down"></span> | |||
| </div> | |||
| </td> | |||
| </tr> | |||
| <tr> | |||
| <td class='xero week1color {{xero_1_mismatch}}' id='b_0'>{{xero_1}}</td> | |||
| <td class='xero week1color {{xero_2_mismatch}}' id='b_1'>{{xero_2}}</td> | |||
| <td class='xero week1color {{xero_3_mismatch}}' id='b_2'>{{xero_3}}</td> | |||
| <td class='xero week1color {{xero_4_mismatch}}' id='b_3'>{{xero_4}}</td> | |||
| <td class='xero week1color {{xero_5_mismatch}}' id='b_4'>{{xero_5}}</td> | |||
| <td class='xero week1color {{xero_6_mismatch}}' id='b_5'>{{xero_6}}</td> | |||
| <td class='xero week1color {{xero_7_mismatch}}' id='b_6'>{{xero_7}}</td> | |||
| <td class='xero week2color {{xero_8_mismatch}}' id='b_7'>{{xero_8}}</td> | |||
| <td class='xero week2color {{xero_9_mismatch}}' id='b_8'>{{xero_9}}</td> | |||
| <td class='xero week2color {{xero_10_mismatch}}' id='b_9'>{{xero_10}}</td> | |||
| <td class='xero week2color {{xero_11_mismatch}}' id='b_10'>{{xero_11}}</td> | |||
| <td class='xero week2color {{xero_12_mismatch}}' id='b_11'>{{xero_12}}</td> | |||
| <td class='xero week2color {{xero_13_mismatch}}' id='b_12'>{{xero_13}}</td> | |||
| <td class='xero week2color {{xero_14_mismatch}}' id='b_13'>{{xero_14}}</td> | |||
| </tr> | |||
| </tbody> | |||
| </table> | |||
| </td> | |||
| </tr> | |||
| {{/ratetype}} | |||
| {{/lines}} | |||
| @@ -169,15 +169,19 @@ | |||
| <img src="#"> | |||
| </div> | |||
| <div class='statusbar titlebar_gradient'> | |||
| <a id='xeroc' href="/xeroc/" target="_blank"> | |||
| <div class='xero'> | |||
| <i class="ticon ticon-times"></i> | |||
| </div> | |||
| </a> | |||
| <div class='wifi'> | |||
| <i class="ticon ticon-times"></i> | |||
| </div> | |||
| <a id='ndiscsv' href="/ndiscsv/" target="_blank"> | |||
| <div class='csv'> | |||
| <i class="ticon ticon-times"></i> | |||
| </div> | |||
| </a> | |||
| <div class='wages'> | |||
| $ | |||
| <div name='number'> | |||
| @@ -0,0 +1,240 @@ | |||
| /*! | |||
| * jQuery scrollintoview() plugin and :scrollable selector filter | |||
| * | |||
| * Version 1.9.4 (06 April 2016) | |||
| * Requires jQuery 1.4 or newer | |||
| * | |||
| * Copyright (c) 2011 Robert Koritnik | |||
| * Licensed under the terms of the MIT license | |||
| * http://www.opensource.org/licenses/mit-license.php | |||
| */ | |||
| !function(root, factory) { | |||
| if (typeof define === 'function' && define.amd) { | |||
| define(['jquery'], factory); | |||
| } else if (typeof exports === 'object') { | |||
| factory(require('jquery')); | |||
| } else { | |||
| factory(root.jQuery); | |||
| } | |||
| } | |||
| (this, function($) { | |||
| var converter = { | |||
| vertical: { x: false, y: true }, | |||
| horizontal: { x: true, y: false }, | |||
| both: { x: true, y: true }, | |||
| x: { x: true, y: false }, | |||
| y: { x: false, y: true } | |||
| }; | |||
| var settings = { | |||
| duration: "fast", | |||
| direction: "both", | |||
| viewPadding: 0 | |||
| }; | |||
| var rootrx = /^(?:html)$/i; | |||
| // gets border dimensions | |||
| var borders = function(domElement, styles) { | |||
| styles = styles || (document.defaultView && document.defaultView.getComputedStyle ? document.defaultView.getComputedStyle(domElement, null) : domElement.currentStyle); | |||
| var px = document.defaultView && document.defaultView.getComputedStyle ? true : false; | |||
| var b = { | |||
| top: (parseFloat(px ? styles.borderTopWidth : $.css(domElement, "borderTopWidth")) || 0), | |||
| left: (parseFloat(px ? styles.borderLeftWidth : $.css(domElement, "borderLeftWidth")) || 0), | |||
| bottom: (parseFloat(px ? styles.borderBottomWidth : $.css(domElement, "borderBottomWidth")) || 0), | |||
| right: (parseFloat(px ? styles.borderRightWidth : $.css(domElement, "borderRightWidth")) || 0) | |||
| }; | |||
| return { | |||
| top: b.top, | |||
| left: b.left, | |||
| bottom: b.bottom, | |||
| right: b.right, | |||
| vertical: b.top + b.bottom, | |||
| horizontal: b.left + b.right | |||
| }; | |||
| }; | |||
| var dimensions = function($element) { | |||
| var elem = $element[0], | |||
| isRoot = rootrx.test(elem.nodeName), | |||
| $elem = isRoot ? $(window) : $element; | |||
| return { | |||
| border: isRoot ? { top: 0, left: 0, bottom: 0, right: 0 } : borders(elem), | |||
| scroll: { | |||
| top: $elem.scrollTop(), | |||
| left: $elem.scrollLeft(), | |||
| maxtop: elem.scrollHeight - elem.clientHeight, | |||
| maxleft: elem.scrollWidth - elem.clientWidth | |||
| }, | |||
| scrollbar: isRoot | |||
| ? { right: 0, bottom: 0 } | |||
| : { | |||
| right: $elem.innerWidth() - elem.clientWidth, | |||
| bottom: $elem.innerHeight() - elem.clientHeight | |||
| }, | |||
| rect: isRoot ? { top: 0, left: 0, bottom: elem.clientHeight, right: elem.clientWidth } : elem.getBoundingClientRect() | |||
| }; | |||
| }; | |||
| $.fn.extend({ | |||
| scrollintoview: function(options) { | |||
| /// <summary>Scrolls the first element in the set into view by scrolling its closest scrollable parent.</summary> | |||
| /// <param name="options" type="Object">Additional options that can configure scrolling: | |||
| /// duration (default: "fast") - jQuery animation speed (can be a duration string or number of milliseconds) | |||
| /// direction (default: "both") - select possible scrollings ("vertical" or "y", "horizontal" or "x", "both") | |||
| /// complete (default: none) - a function to call when scrolling completes (called in context of the DOM element being scrolled) | |||
| /// </param> | |||
| /// <return type="jQuery">Returns the same jQuery set that this function was run on.</return> | |||
| options = $.extend({}, settings, options); | |||
| options.direction = converter[typeof (options.direction) === "string" && options.direction.toLowerCase()] || converter.both; | |||
| if (typeof options.viewPadding == "number") { | |||
| options.viewPadding = { x: options.viewPadding, y: options.viewPadding }; | |||
| } else if (typeof options.viewPadding == "object") { | |||
| if (options.viewPadding.x == undefined) { | |||
| options.viewPadding.x = 0; | |||
| } | |||
| if (options.viewPadding.y == undefined) { | |||
| options.viewPadding.y = 0; | |||
| } | |||
| } | |||
| var dirStr = ""; | |||
| if (options.direction.x === true) dirStr = "horizontal"; | |||
| if (options.direction.y === true) dirStr = dirStr ? "both" : "vertical"; | |||
| var el = this.eq(0); | |||
| var scroller = el.parent().closest(":scrollable(" + dirStr + ")"); | |||
| // check if there's anything to scroll in the first place | |||
| if (scroller.length > 0) { | |||
| scroller = scroller.eq(0); | |||
| var dim = { | |||
| e: dimensions(el), | |||
| s: dimensions(scroller) | |||
| }; | |||
| var rel = { | |||
| top: dim.e.rect.top - (dim.s.rect.top + dim.s.border.top), | |||
| bottom: dim.s.rect.bottom - dim.s.border.bottom - dim.s.scrollbar.bottom - dim.e.rect.bottom, | |||
| left: dim.e.rect.left - (dim.s.rect.left + dim.s.border.left), | |||
| right: dim.s.rect.right - dim.s.border.right - dim.s.scrollbar.right - dim.e.rect.right | |||
| }; | |||
| var animProperties = {}; | |||
| // vertical scroll | |||
| if (options.direction.y === true) { | |||
| if (rel.top < 0) { | |||
| animProperties.scrollTop = Math.max(0, dim.s.scroll.top + rel.top - options.viewPadding.y); | |||
| } else if (rel.top > 0 && rel.bottom < 0) { | |||
| animProperties.scrollTop = Math.min(dim.s.scroll.top + Math.min(rel.top, -rel.bottom) + options.viewPadding.y, dim.s.scroll.maxtop); | |||
| } | |||
| } | |||
| // horizontal scroll | |||
| if (options.direction.x === true) { | |||
| if (rel.left < 0) { | |||
| animProperties.scrollLeft = Math.max(0, dim.s.scroll.left + rel.left - options.viewPadding.x); | |||
| } else if (rel.left > 0 && rel.right < 0) { | |||
| animProperties.scrollLeft = Math.min(dim.s.scroll.left + Math.min(rel.left, -rel.right) + options.viewPadding.x, dim.s.scroll.maxleft); | |||
| } | |||
| } | |||
| // scroll if needed | |||
| if (!$.isEmptyObject(animProperties)) { | |||
| var scrollExpect = {}, | |||
| scrollListener = scroller; | |||
| if (rootrx.test(scroller[0].nodeName)) { | |||
| scroller = $("html,body"); | |||
| scrollListener = $(window); | |||
| } | |||
| function animateStep(now, tween) { | |||
| scrollExpect[tween.prop] = Math.floor(now); | |||
| }; | |||
| function onscroll(event) { | |||
| $.each(scrollExpect, function(key, value) { | |||
| if (Math.floor(scrollListener[key]()) != Math.floor(value)) { | |||
| options.complete = null; // don't run complete function if the scrolling was interrupted | |||
| scroller.stop('scrollintoview'); | |||
| } | |||
| }); | |||
| } | |||
| scrollListener.on('scroll', onscroll); | |||
| scroller | |||
| .stop('scrollintoview') | |||
| .animate(animProperties, { duration: options.duration, step: animateStep, queue: 'scrollintoview' }) | |||
| .eq(0) // we want function to be called just once (ref. "html,body") | |||
| .queue('scrollintoview', function(next) { | |||
| scrollListener.off('scroll', onscroll); | |||
| $.isFunction(options.complete) && options.complete.call(scroller[0]); | |||
| next(); | |||
| }) | |||
| scroller.dequeue('scrollintoview'); | |||
| } else { | |||
| // when there's nothing to scroll, just call the "complete" function | |||
| $.isFunction(options.complete) && options.complete.call(scroller[0]); | |||
| } | |||
| } | |||
| // return set back | |||
| return this; | |||
| } | |||
| }); | |||
| var scrollValue = { | |||
| auto: true, | |||
| scroll: true, | |||
| visible: false, | |||
| hidden: false | |||
| }; | |||
| var scroll = function(element, direction) { | |||
| direction = converter[typeof (direction) === "string" && direction.toLowerCase()] || converter.both; | |||
| var styles = (document.defaultView && document.defaultView.getComputedStyle ? document.defaultView.getComputedStyle(element, null) : element.currentStyle); | |||
| var overflow = { | |||
| x: scrollValue[styles.overflowX.toLowerCase()] || false, | |||
| y: scrollValue[styles.overflowY.toLowerCase()] || false, | |||
| isRoot: rootrx.test(element.nodeName) | |||
| }; | |||
| // check if completely unscrollable (exclude HTML element because it's special) | |||
| if (!overflow.x && !overflow.y && !overflow.isRoot) { | |||
| return false; | |||
| } | |||
| var size = { | |||
| height: { | |||
| scroll: element.scrollHeight, | |||
| client: element.clientHeight | |||
| }, | |||
| width: { | |||
| scroll: element.scrollWidth, | |||
| client: element.clientWidth | |||
| }, | |||
| // check overflow.x/y because iPad (and possibly other tablets) don't dislay scrollbars | |||
| scrollableX: function() { | |||
| return (overflow.x || overflow.isRoot) && this.width.scroll > this.width.client; | |||
| }, | |||
| scrollableY: function() { | |||
| return (overflow.y || overflow.isRoot) && this.height.scroll > this.height.client; | |||
| } | |||
| }; | |||
| return direction.y && size.scrollableY() || direction.x && size.scrollableX(); | |||
| }; | |||
| $.expr[":"].scrollable = $.expr.createPseudo(function(direction) { | |||
| return function(element) { | |||
| return scroll(element, direction); | |||
| }; | |||
| }); | |||
| }); | |||
| @@ -25,9 +25,15 @@ | |||
| $( ".boundary_datepicker" ).datepicker("option", "dateFormat", "yy-mm-dd"); | |||
| } | |||
| function loading() | |||
| { | |||
| return "<tr class='loading' ><td colspan=4 class='loading'><img src='"+bts().load_job_img +"'><br><h1>Sync to Xero</h1></td></tr>"; | |||
| } | |||
| function display_hour_lines(response) | |||
| { | |||
| $('#staff').html(); | |||
| $('#staff').html(''); | |||
| var temp = $('#bts_staff_hours_template').html(); | |||
| var html = Mustache.render(temp, response); | |||
| $('#staff').append(html); | |||
| @@ -41,6 +47,7 @@ | |||
| } | |||
| function get_timesheet_from_xero(){ | |||
| $('#staff').html(loading()); | |||
| $.post(bts().ajax_url, { // POST request | |||
| _ajax_nonce: bts().nonce, // nonce | |||
| action: "get_timesheet_from_xero", // action | |||
| @@ -57,13 +64,13 @@ | |||
| } | |||
| function sync_timesheet_from_xero(){ | |||
| $('#staff').html(loading()); | |||
| $.post(bts().ajax_url, { // POST request | |||
| _ajax_nonce: bts().nonce, // nonce | |||
| action: "get_timesheet_from_xero", // action | |||
| sync: true, | |||
| }).done(function(response){ | |||
| set_payroll_calendar(response.payroll_calendar); | |||
| console.log("%o", response); | |||
| display_hour_lines(response); | |||
| }).fail(function(){ | |||
| console.warn('failed'); | |||
| @@ -71,9 +78,243 @@ | |||
| console.log('completed'); | |||
| }); | |||
| } | |||
| function approve_all_timesheet() | |||
| { | |||
| $.post(bts().ajax_url, { // POST request | |||
| _ajax_nonce: bts().nonce, // nonce | |||
| action: "approve_all_timesheet", // action | |||
| }).done(function(response){ | |||
| if (response.status == 'success') | |||
| alert("approve all succeed"); | |||
| else | |||
| alert(response.err); | |||
| }).fail(function(){ | |||
| alert("Network Error, cannot approve all"); | |||
| }).always(function(){ | |||
| console.log('completed'); | |||
| }); | |||
| } | |||
| $('#sync_timesheet').click(function(){ | |||
| sync_timesheet_from_xero(); | |||
| }); | |||
| $('#approve_all').click(function(){ | |||
| approve_all_timesheet(); | |||
| }); | |||
| function display_invoice_items_test(response) | |||
| { | |||
| var template = $('#bts_client_invoice_template').html(); | |||
| for (var i=1; i<10; i++){ | |||
| data = { | |||
| client_name: "Martin", | |||
| jobs:[ | |||
| { | |||
| tos: "service a " + i, | |||
| staff_name: "joe", | |||
| start: "2019-07-01", | |||
| finish: "2019-07-14", | |||
| hours: i, | |||
| price: 336, | |||
| }, | |||
| { | |||
| tos: "service b " + i, | |||
| staff_name: "joe dne", | |||
| start: "2019-07-01", | |||
| finish: "2019-07-14", | |||
| hours: i, | |||
| price: 16, | |||
| } | |||
| ] | |||
| } | |||
| html = Mustache.render(template, data); | |||
| $('#clientinvoice').append(html); | |||
| } | |||
| } | |||
| function display_invoice_items(selector, response) | |||
| { | |||
| var template = $('#bts_client_invoice_template').html(); | |||
| html = Mustache.render(template, response); | |||
| el = $(html); | |||
| $(selector).after(el); | |||
| //el.SlideDown(); | |||
| el.show(); | |||
| } | |||
| function get_invoice_item(selector, client_id) | |||
| { | |||
| $.post(bts().ajax_url, { // POST request | |||
| _ajax_nonce: bts().nonce, // nonce | |||
| action: "get_invoice_item", // action | |||
| client: client_id, | |||
| start: get_invoice_start(), | |||
| finish: get_invoice_finish(), | |||
| }).done(function(response){ | |||
| if (response.status == 'success'){ | |||
| display_invoice_items(selector, response); | |||
| $(selector).hide(); | |||
| }else{ | |||
| alert(response.err); | |||
| } | |||
| }).fail(function(){ | |||
| alert("Network Error, cannot approve all"); | |||
| }).always(function(){ | |||
| console.log('completed'); | |||
| }); | |||
| } | |||
| function create_invoice_number(client_id) | |||
| { | |||
| start_showing_invoice_request(client_id); | |||
| $.post(bts().ajax_url, { // POST request | |||
| _ajax_nonce: bts().nonce, // nonce | |||
| action: "create_invoice_in_xero", // action | |||
| client: client_id, | |||
| start: get_invoice_start(), | |||
| finish: get_invoice_finish(), | |||
| }).done(function(response){ | |||
| if (response.status == 'success'){ | |||
| show_invoice_number(response); | |||
| }else{ | |||
| alert(response.err); | |||
| } | |||
| }).fail(function(){ | |||
| alert("Network Error, cannot approve all"); | |||
| }).always(function(){ | |||
| console.log('completed'); | |||
| }); | |||
| } | |||
| function start_showing_invoice_request(client_id) | |||
| { | |||
| $('td.invoice_nubmer img').show(); | |||
| animate_into_top('#invoice_' + client_id); | |||
| return; | |||
| $('#invoice_' + client_id).scrollintoview({ | |||
| duration: 2500, | |||
| direction: "vertical", | |||
| viewPadding: { y: 10 }, | |||
| complete: function() { | |||
| // highlight the element so user's focus gets where it needs to be | |||
| } | |||
| }); | |||
| } | |||
| function animate_into_top(el) | |||
| { | |||
| var offset = $(el).offset(); // Contains .top and .left | |||
| offset.left -= 20; | |||
| offset.top -= 20; | |||
| $('html, body').animate({ | |||
| scrollTop: offset.top, | |||
| scrollLeft: offset.left | |||
| },1000); | |||
| } | |||
| function show_invoice_number(response) | |||
| { | |||
| $('td.invoice_nubmer').html(response.invoice_number); | |||
| $('td.invoice_button div').hide(); | |||
| } | |||
| function get_invoice_start() | |||
| { | |||
| return $('#invoice_start').attr('value'); | |||
| } | |||
| function get_invoice_finish() | |||
| { | |||
| return $('#invoice_finish').attr('value'); | |||
| } | |||
| $(document).on('click', 'td.client_nameonly', function(){ | |||
| var id = $(this).attr('data-client-id'); | |||
| $('#nameonly_' + id).hide(); | |||
| $('#dummyui_' + id).show(); | |||
| if( $('#invoice_' + id).length == 0 ){ | |||
| get_invoice_item('#dummyui_' + id, id); | |||
| }else{ | |||
| $('#dummyui_' + id).hide(); | |||
| $('#invoice_' + id).show(); | |||
| } | |||
| }); | |||
| $(document).on('click', 'th.client_invoice', function(){ | |||
| var id = $(this).closest('td.client_invoice').attr('data-client-id'); | |||
| $('#nameonly_' + id).show(); | |||
| $('#invoice_' + id).hide(); | |||
| }); | |||
| $(document).on('hide','#maintabs',function(){ | |||
| alert('abc'); | |||
| }); | |||
| $(document).on("afterShow.vc.accordion", function(e, opt) { | |||
| console.log("%o, %o", e, opt); | |||
| if (e.target.hash =="#1565353205981-c3582e44-83d2"){ | |||
| get_timesheet_from_xero(); | |||
| } | |||
| }) | |||
| function format_date(date){ | |||
| var dd = date.getDate(); | |||
| var mm = date.getMonth() + 1; //January is 0! | |||
| var yyyy = date.getFullYear(); | |||
| if (dd < 10) { | |||
| dd = '0' + dd; | |||
| } | |||
| if (mm < 10) { | |||
| mm = '0' + mm; | |||
| } | |||
| return yyyy + '-' + mm + '-' +dd ; | |||
| } | |||
| function daysInMonth (month, year) { | |||
| return new Date(year, month, 0).getDate(); | |||
| } | |||
| function setup_invoice_start_finish() | |||
| { | |||
| var date = new Date(); | |||
| var firstDay = new Date(date.getFullYear(), | |||
| date.getMonth(), 1); | |||
| var lastDay = new Date(date.getFullYear(), | |||
| date.getMonth(), daysInMonth(date.getMonth()+1, | |||
| date.getFullYear())); | |||
| $('#invoice_start').attr('value', format_date(firstDay)); | |||
| $('#invoice_finish').attr('value', format_date(lastDay)); | |||
| } | |||
| $('#invoice_start').change(function(){ | |||
| clear_all_invoice(); | |||
| }); | |||
| $('#invoice_finish').change(function(){ | |||
| clear_all_invoice(); | |||
| }); | |||
| $(document).on('click', 'a.invoice_button', function(e){ | |||
| e.stopPropagation(); | |||
| var id = $(this).attr('data-client-login'); | |||
| create_invoice_number(id); | |||
| return false; | |||
| }); | |||
| function clear_all_invoice() | |||
| { | |||
| if ( $(".invoice_detail_row").length >0 ){ | |||
| if (!confirm("Change Date will clear all invoice details")) | |||
| return; | |||
| } | |||
| $(".invoice_detail_row").remove(); | |||
| $(".invoice_nameonly_row").show(); | |||
| } | |||
| datebox(); | |||
| setup_invoice_start_finish(); | |||
| /*_____________________________________________*/ | |||
| }); | |||
| })(jQuery); | |||
| })(jQuery); | |||
| @@ -49,7 +49,9 @@ class AcareOffice{ | |||
| add_shortcode( 'bts_feedback_card', array($this, 'bts_feedback_card')); | |||
| add_shortcode( 'bb_timesheet_canvas', array($this, 'bb_timesheet_canvas')); | |||
| add_shortcode( 'bts_staff_hours_template', array($this, 'bts_staff_hours_template')); | |||
| add_shortcode( 'bts_client_invoice_template', array($this, 'bts_client_invoice_template')); | |||
| add_shortcode( 'bts_csv_template', array($this, 'bts_csv_template')); | |||
| add_shortcode( 'bts_invoiced_client', array($this, 'bts_invoiced_client')); | |||
| //user profile page | |||
| add_shortcode( 'bts_user_name', array($this,'bts_user_name')); | |||
| @@ -78,6 +80,10 @@ class AcareOffice{ | |||
| add_action('wp_ajax_nopriv_client_ack_job', array($this,'client_ack_job' )); | |||
| add_action('wp_ajax_get_timesheet_from_xero', array($this,'get_timesheet_from_xero' )); | |||
| add_action('wp_ajax_approve_all_timesheet', array($this,'approve_all_timesheet' )); | |||
| add_action('wp_ajax_get_invoice_item', array($this,'get_invoice_item' )); | |||
| add_action('wp_ajax_create_invoice_in_xero', array($this,'create_invoice_in_xero' )); | |||
| // hook add_rewrite_rules function into rewrite_rules_array | |||
| add_filter('rewrite_rules_array', array($this,'my_add_rewrite_rules')); | |||
| @@ -112,7 +118,32 @@ class AcareOffice{ | |||
| $this->xero->init_wp(); | |||
| //$abc = new AddrMap("01515b52-6936-46b2-a000-9ad4cd7a5b50", "0768db6d-e5f4-4b45-89a2-29f7e8d2953c"); | |||
| $abc = new AddrMap("122eb1d0-d8c4-4fc3-8bf8-b7825bee1a01", "0768db6d-e5f4-4b45-89a2-29f7e8d2953c"); | |||
| //$abc = new AddrMap("122eb1d0-d8c4-4fc3-8bf8-b7825bee1a01", "0768db6d-e5f4-4b45-89a2-29f7e8d2953c"); | |||
| $this->check_csv_download(); | |||
| } | |||
| private function check_csv_download() | |||
| { | |||
| $url = $_SERVER['REQUEST_URI']; | |||
| $matches=[]; | |||
| preg_match("/\/ndiscsv\/start-([^\/]+)\/finish-([^\/]+)\/?$/", $url, $matches); | |||
| if ( $matches !=3 || $_SERVER['REQUEST_URI'] != $matches[0] ) | |||
| return; | |||
| $start = $matches[1]; | |||
| $finish = $matches[2]; | |||
| $filename="{$start}___{$finish}.csv"; | |||
| header("Expires: 0"); | |||
| header("Cache-Control: no-cache, no-store, must-revalidate"); | |||
| header('Cache-Control: pre-check=0, post-check=0, max-age=0', false); | |||
| header("Pragma: no-cache"); | |||
| header("Content-type: text/csv"); | |||
| header("Content-Disposition:attachment; filename=$filename"); | |||
| header("Content-Type: application/force-download"); | |||
| //readfile(dirname(__FILE__) . "/img/circle.png"); | |||
| echo "fuck this file"; | |||
| exit(); | |||
| } | |||
| @@ -191,6 +222,8 @@ class AcareOffice{ | |||
| 'feedback_card/([^/]+)/week-([^/]+)/?$' => 'index.php?pagename=feedback_card&bts_user_id=$matches[1]&bts_week_id=$matches[2]', | |||
| 'feedback_card/([^/]+)/start-([^/]+)/finish-([^/]+)/?$' => 'index.php?pagename=feedback_card&bts_user_id=$matches[1]&bts_job_start=$matches[2]&bts_job_finish=$matches[3]', | |||
| 'ndiscsv/start-([^/]+)/finish-([^/]+)/?$' => 'index.php?pagename=ndiscsv&bts_job_start=$matches[1]&bts_job_finish=$matches[2]', | |||
| ); | |||
| $aRules = $aNewRules + $aRules; | |||
| return $aRules; | |||
| @@ -460,6 +493,7 @@ class AcareOffice{ | |||
| wp_enqueue_style( 'bts_xeroc', plugins_url('css/xeroc.css', __FILE__)); | |||
| wp_enqueue_script( 'bts_xeroc', plugins_url('js/xeroc.js', __FILE__), array( 'jquery' , 'bts' )); | |||
| wp_enqueue_script('mustache', plugins_url('js/mustache.min.js', __FILE__), array('jquery')); | |||
| wp_enqueue_script('scrollintoview', plugins_url('js/scrollintoview.js', __FILE__), array('jquery')); | |||
| global $wp_scripts; | |||
| wp_enqueue_script('jquery-ui-datepicker'); | |||
| @@ -610,6 +644,41 @@ class AcareOffice{ | |||
| public function bts_staff_hours_template($attr){ | |||
| return $this->template('bts_staff_hours_template', 'bts_staff_hours_template.html'); | |||
| } | |||
| public function bts_client_invoice_template($attr){ | |||
| return $this->template('bts_client_invoice_template', 'bts_client_invoice_template.html'); | |||
| } | |||
| public function bts_csv_template($attr){ | |||
| return $this->template('bts_csv_template', 'bts_csv_template.html'); | |||
| } | |||
| public function bts_invoiced_client($attr) | |||
| { | |||
| $result = ""; | |||
| $users = $users = get_users(array('role' => 'client')); | |||
| $row = <<<ZOT | |||
| <tr id="nameonly_%s" data-client-id='%s' class="invoice_nameonly_row"> | |||
| <td class="client_nameonly" data-client-id='%s'> | |||
| <i class="vc_tta-icon fa fa-plus-square"></i> %s | |||
| </td> | |||
| </tr> | |||
| <tr id="dummyui_%s" data-client-id='%s' class="invoice_nameonly_row_dummy"> | |||
| <td class="client_nameonly" data-client-id='%s'> | |||
| <i class="vc_tta-icon fa fa-plus-square"></i> %s is loading .... please wait... | |||
| </td> | |||
| </tr> | |||
| ZOT; | |||
| foreach ($users as $u) | |||
| { | |||
| $payment = get_user_meta($u->ID, 'payment', true); | |||
| if( $payment != 'invoice' ) | |||
| continue; //bypass | |||
| $result .= sprintf($row, $u->user_login, $u->user_login, $u->user_login, $u->display_name, | |||
| $u->user_login, $u->user_login, $u->user_login, $u->display_name); | |||
| } | |||
| return $result; | |||
| } | |||
| //generate template based on html file | |||
| private function template($id, $file) | |||
| { | |||
| @@ -1029,6 +1098,7 @@ class AcareOffice{ | |||
| if ($sync){ | |||
| $xx->set_local_timesheet($local_ts); | |||
| $xx->save_to_xero(); | |||
| $xx->refresh_remote(); | |||
| } | |||
| @@ -1047,33 +1117,52 @@ class AcareOffice{ | |||
| 'staff_name' => $this->get_user_name_by_login ($staff_login), | |||
| 'staff_id' => $staff_login, | |||
| 'Xero_Status' => 'Empty', | |||
| 'rowspan' =>1, | |||
| 'local_total' =>0, | |||
| 'remote_total'=>0, | |||
| 'ratetype' => [], | |||
| ); | |||
| //for local | |||
| $buddy = $xx->get_buddy_timesheets($staff_login, new \DateTime($start), new \DateTime($finish)); | |||
| if ($buddy != NULL){ | |||
| $item['Xero_Status'] = $buddy->getStatus(); | |||
| }else{ | |||
| $item['Xero_Status'] = "Not Exist"; | |||
| } | |||
| foreach($details as $rate => $hours){ | |||
| $item['rate_name'] = $this->get_rate_name_by_id($rate); | |||
| $item['rowspan'] ++; | |||
| $ratetype_item=[]; | |||
| $ratetype_item['rate_name'] = $this->get_rate_name_by_id($rate); | |||
| //for local | |||
| for ($i=1; $i<=14; $i++) | |||
| { | |||
| $item["local_$i"] = $hours[$i-1]; | |||
| $ratetype_item["local_$i"] = $hours[$i-1]; | |||
| $item['local_total'] += $hours[$i-1]; | |||
| } | |||
| } | |||
| //for remote | |||
| $buddy = $xx->get_buddy_timesheets($staff_login, new \DateTime($start), new \DateTime($finish)); | |||
| if ( $buddy != NULL ) | |||
| { | |||
| $item['Xero_Status'] = $buddy->getStatus(); | |||
| $remote_lines = $buddy->getTimesheetLines(); | |||
| foreach($remote_lines as $rl) | |||
| //for remote | |||
| if ( $buddy != NULL ) | |||
| { | |||
| if ( $rl->getEarningsRateID() == $rate){ | |||
| for ($i=1; $i<=14; $i++) | |||
| { | |||
| $item["xero_$i"] = $rl->getNumberOfUnits()[$i-1]; | |||
| } | |||
| break;//we found it | |||
| } | |||
| $remote_lines = $buddy->getTimesheetLines(); | |||
| foreach($remote_lines as $rl) | |||
| { | |||
| if ( $rl->getEarningsRateID() == $rate){ | |||
| for ($i=1; $i<=14; $i++) | |||
| { | |||
| $ratetype_item["xero_$i"] = $rl->getNumberOfUnits()[$i-1]; | |||
| $item['remote_total'] += $rl->getNumberOfUnits()[$i-1]; | |||
| if ($ratetype_item["xero_$i"] != $ratetype_item["local_$i"]){ | |||
| $ratetype_item["xero_{$i}_mismatch"] = "mismatch"; | |||
| } | |||
| } | |||
| break;//we found it | |||
| } | |||
| } | |||
| } | |||
| $item['ratetype'][] = $ratetype_item; | |||
| } | |||
| $item = array_merge($item, $days); | |||
| @@ -1086,6 +1175,31 @@ class AcareOffice{ | |||
| wp_send_json($response); | |||
| } | |||
| public function approve_all_timesheet() | |||
| { | |||
| check_ajax_referer('acaresydney'); | |||
| //set up payroll calendar | |||
| $pc = $this->xero->get_payroll_calendar(); | |||
| $start = $pc->getStartDate()->format('Y-m-d'); | |||
| $finish = new \DateTime($start); | |||
| $finish = $finish->modify("+13 days")->format('Y-m-d'); | |||
| $paydate = $pc->getPaymentDate()->format('Y-m-d'); | |||
| //prepare response | |||
| $response = array( | |||
| 'status' => 'success', | |||
| 'payroll_calendar' => array( | |||
| 'start' => $start, | |||
| 'finish' => $finish, | |||
| 'paydate'=> $paydate, | |||
| ), | |||
| ); | |||
| $xx = new \Biukop\TimeSheet($this->xero->get_xero_handle(), $finish); | |||
| $xx->approve_all(); | |||
| wp_send_json($response); | |||
| } | |||
| static public function get_user_name_by_login($login) | |||
| { | |||
| $user = get_user_by('login', $login); | |||
| @@ -1095,6 +1209,167 @@ class AcareOffice{ | |||
| return "Invalid Name"; | |||
| } | |||
| //ajax | |||
| public function get_invoice_item() | |||
| { | |||
| check_ajax_referer('acaresydney'); | |||
| $client = $_POST['client']; | |||
| //$client = "8cb3d205-6cdc-4187-ae39-9216923dd86d"; | |||
| $start = $_POST['start']; //2019-07-01'; | |||
| $finish= $_POST['finish'];//2019-07-31'; | |||
| $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"; | |||
| $rows = $this->db->get_results($sql); | |||
| $response=[ | |||
| 'status' =>'success', | |||
| 'client_login' => $client, | |||
| 'client_name' => $this->get_user_name_by_login($client), | |||
| 'jobs'=>[], | |||
| 'err'=>'' | |||
| ]; | |||
| $now = new \DateTime(); | |||
| $price = new NdisPrice($now->format("Y"));//current year; | |||
| $summary=[];// by ndis code | |||
| foreach($rows as $r){ | |||
| $quantity = $this->get_job_hours($r->start, $r->finish); | |||
| $unit = $price->get_tos_unit($r->tos); | |||
| if ($unit != "Hour") | |||
| { | |||
| $quantity = 1; | |||
| } | |||
| $unitprice = $price->get_tos_price($r->tos); | |||
| $description = $this->get_job_description_for_invoice($price, $r); | |||
| $data = [ | |||
| 'tos' => $price->get_tos_str($r->tos), | |||
| 'start' => $r->start, | |||
| 'finish' => $r->finish, | |||
| 'hours' => $quantity, | |||
| 'unitprice'=> $price->get_tos_price($r->tos), | |||
| 'staff_name' => $this->get_user_name_by_login($r->staff), | |||
| 'price' => sprintf("%.2f", $quantity * $unitprice), | |||
| ]; | |||
| $response['jobs'][] = $data; | |||
| $summary[$r->tos] += $quantity; | |||
| } | |||
| if (count($response['jobs']) ==0) | |||
| { | |||
| $response['nojob'] = true; | |||
| } | |||
| foreach ($summary as $key => $val) | |||
| { | |||
| $response['summary'][] = array( | |||
| 'ndis' => $key, | |||
| 'tos' => $price->get_tos_str($key), | |||
| 'Hours'=> $val, | |||
| ); | |||
| } | |||
| wp_send_json($response); | |||
| } | |||
| public function create_invoice_in_xero() | |||
| { | |||
| check_ajax_referer('acaresydney'); | |||
| $client = $_POST['client']; | |||
| //$client = "8cb3d205-6cdc-4187-ae39-9216923dd86d"; | |||
| $start = $_POST['start']; //2019-07-01'; | |||
| $finish= $_POST['finish'];//2019-07-31'; | |||
| // | |||
| $response=[ | |||
| 'status'=>success, | |||
| 'invoice_number' => '', | |||
| 'err'=> '', | |||
| ]; | |||
| try{ | |||
| $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>", | |||
| $invoice->getInvoiceID(), $invoice->getInvoiceNumber()); | |||
| }catch(\Exception $e){ | |||
| $response['status'] = 'error'; | |||
| $response['err'] = "XERO Invoice Error"; | |||
| } | |||
| wp_send_json($response); | |||
| } | |||
| public function create_invoice_by_client($client_login, $start, $finish) | |||
| { | |||
| $user = get_user_by('login', $client_login); | |||
| if ( !$this->is_client($user) ) | |||
| return NULL; | |||
| $payment = get_user_meta($user->ID, "payment", true); | |||
| if ($payment != "invoice") | |||
| return NULL; | |||
| $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_login' ORDER BY start"; | |||
| $rows = $this->db->get_results($sql); | |||
| $xero = $this->xero->get_xero_handle(); | |||
| //crate invoice | |||
| $invoice = new \XeroPHP\Models\Accounting\Invoice($xero); | |||
| $contact= $xero->loadByGUID('Accounting\\Contact', $client_login); | |||
| $now = new \DateTime(); | |||
| $due = new \DateTime(); | |||
| $due->modify("+14 days"); | |||
| $invoice->setType("ACCREC") | |||
| ->setStatus("DRAFT") | |||
| ->setContact($contact) | |||
| ->setDate($now) | |||
| ->setDueDate($due); | |||
| $to_save=[];//all invoices to save; | |||
| $price = new NdisPrice($now->format("Y"));//current year; | |||
| foreach($rows as $r){ | |||
| $quantity = $this->get_job_hours($r->start, $r->finish); | |||
| $unit = $price->get_tos_unit($r->tos); | |||
| if ($unit != "Hour") | |||
| { | |||
| $quantity = 1; | |||
| } | |||
| $unitprice = $price->get_tos_price($r->tos); | |||
| $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); | |||
| } | |||
| //prevent zero lineitems | |||
| if ( count($invoice->getLineItems()) >0 ) | |||
| $invoice->save(); | |||
| return $invoice; | |||
| } | |||
| public function get_job_description_for_invoice($price, $job) | |||
| { | |||
| $description = sprintf( | |||
| '%s | |||
| [NDIS code: %s] | |||
| Time: %s - %s | |||
| By Carer : %s', | |||
| $price->get_tos_str($job->tos), | |||
| $job->tos, | |||
| $job->start, | |||
| $job->finish, | |||
| $this->get_user_name_by_login($job->staff) | |||
| ); | |||
| return $description; | |||
| } | |||
| public function create_timesheet_from_db($start, $finish){ | |||
| $results = []; | |||
| @@ -1177,6 +1452,9 @@ if ( defined( 'WP_CLI' ) && WP_CLI ) { | |||
| \WP_CLI::add_command( 'feedback_url', array($bb, 'feedback_url')); | |||
| \WP_CLI::add_command( 'produce_invoice', array($bb, 'produce_invoice')); | |||
| } | |||
| //$bb->class_loader(); | |||
| //$bb->create_invoice_by_client("8cb3d205-6cdc-4187-ae39-9216923dd86d", "2019-07-01", "2019-07-31"); | |||
| //$idx = $bb->convert_date_to_idx("2019-07-02 14:30:00", "2019-07-01", "2019-07-02"); | |||
| //wp_send_json($idx); | |||
| //$bb->create_timesheet_from_db("2019-07-01", "2019-07-14"); | |||