mirror of
https://github.com/EGroupware/egroupware.git
synced 2024-11-21 15:33:23 +01:00
* Timesheet: allow to record/document pause times with timer and manually
This commit is contained in:
parent
eaa4a6abd0
commit
f082055134
@ -137,6 +137,7 @@ class timesheet_bo extends Api\Storage
|
||||
'ts_description' => 'Description',
|
||||
'ts_start' => 'Start',
|
||||
'ts_duration' => 'Duration',
|
||||
'ts_paused' => 'Paused',
|
||||
'ts_quantity' => 'Quantity',
|
||||
'ts_unitprice' => 'Unitprice',
|
||||
'ts_owner' => 'Owner',
|
||||
@ -506,7 +507,7 @@ class timesheet_bo extends Api\Storage
|
||||
//_debug_array($ids);
|
||||
if(empty($ids))
|
||||
{
|
||||
$this->summary = array('duration' => 0, 'price' => null, 'quantity' => 0);
|
||||
$this->summary = array('duration' => 0, 'paused' => 0, 'price' => null, 'quantity' => 0);
|
||||
return array();
|
||||
}
|
||||
unset($criteria);
|
||||
@ -520,6 +521,7 @@ class timesheet_bo extends Api\Storage
|
||||
// is not joined, as the join causes a multiplication of the sum per customfield found
|
||||
// joining of the cutomfield table is triggered by criteria being set with either a string or an array
|
||||
$cols = ['SUM(ts_duration) AS duration',
|
||||
'SUM(COALESCE(ts_paused,0)) AS paused',
|
||||
"SUM($total_sql) AS price",
|
||||
'MAX(ts_modified) AS max_modified'];
|
||||
if($this->quantity_sum)
|
||||
@ -569,7 +571,7 @@ class timesheet_bo extends Api\Storage
|
||||
parent::search($criteria,array(
|
||||
(string)$sum_ts_id[$type],"''","''","''",'MIN(ts_start)','SUM(ts_duration) AS ts_duration',
|
||||
($this->quantity_sum ? "SUM(ts_quantity) AS ts_quantity" : '0'),
|
||||
'0','NULL','0','0','0','0','0','0',"SUM($total_sql) AS ts_total"
|
||||
'0','NULL','0','0','0','0','0','0',"SUM(COALESCE(ts_paused,0)) AS ts_paused","SUM($total_sql) AS ts_total"
|
||||
),'GROUP BY '.$sum_sql[$type],$sum_extra_cols,$wildcard,$empty,$op,'UNION',$filter,$join,$need_full_no_count);
|
||||
$sum_extra_cols[$type][0] = '0';
|
||||
}
|
||||
@ -792,7 +794,7 @@ class timesheet_bo extends Api\Storage
|
||||
{
|
||||
if(!$ids)
|
||||
{
|
||||
return array('duration' => 0, 'quantity' => 0, 'price' => 0, 'max_modified' => null);
|
||||
return array('duration' => 0, 'paused' => 0, 'quantity' => 0, 'price' => 0, 'max_modified' => null);
|
||||
}
|
||||
$filter = [];
|
||||
if($ignore_acl)
|
||||
|
@ -37,6 +37,7 @@ class timesheet_merge extends Api\Storage\Merge
|
||||
*/
|
||||
protected $numeric_fields = array(
|
||||
'$$ts_duration$$',
|
||||
'$$ts_paused$$',
|
||||
'$$ts_quantity$$',
|
||||
'$$ts_unitprice$$'
|
||||
);
|
||||
@ -132,7 +133,7 @@ class timesheet_merge extends Api\Storage\Merge
|
||||
|
||||
$array = $record->get_record_array();
|
||||
$array['ts_total'] = $array['ts_quantity'] * $array['ts_unitprice'];
|
||||
foreach(array('ts_duration','ts_quantity','ts_unitprice','ts_total') as $key)
|
||||
foreach(array('ts_duration','ts_paused','ts_quantity','ts_unitprice','ts_total') as $key)
|
||||
{
|
||||
$array[$key] = self::number_format($array[$key],2,$this->mimetype);
|
||||
}
|
||||
|
@ -99,7 +99,7 @@ class timesheet_ui extends timesheet_bo
|
||||
// are we supposed to add pending events, to a new or an existing timesheet
|
||||
if (isset($_REQUEST['events']))
|
||||
{
|
||||
$pending = Events::getPending($_REQUEST['events'] === 'overall', $time);
|
||||
$pending = Events::getPending($_REQUEST['events'] === 'overall', $time, $paused);
|
||||
$this->data['events'] = array_merge($this->data['events'], array_values($pending));
|
||||
$start = $this->data['events'][0]['tse_time'];
|
||||
$this->data['ts_start'] = $start;
|
||||
@ -107,6 +107,7 @@ class timesheet_ui extends timesheet_bo
|
||||
$this->data['end_time'] = '';
|
||||
$this->data['ts_duration'] = (int)$this->data['ts_duration'] + round($time / 60); // minutes
|
||||
$this->data['ts_quantity'] = (float)$this->data['ts_quantity'] + $this->data['ts_duration'] / 60.0; // hours
|
||||
$this->data['ts_paused'] = $paused ? round($paused / 60.0) : null;
|
||||
// check if any of the events contains an app::id to link the timesheet to
|
||||
foreach($pending as $event)
|
||||
{
|
||||
|
@ -108,6 +108,8 @@ overwrite time common de Zeit überschreiben
|
||||
overwriting start or stop time timesheet de Überschreiben der Start- oder Stop-Zeit
|
||||
parent admin de Übergeordnet
|
||||
pause common de Pause
|
||||
pause time timesheet de Pausenzeit
|
||||
paused timesheet de Pausen
|
||||
permission denied!!! timesheet de Zugriff verweigert!
|
||||
permissions error - %1 could not %2 timesheet de Fehler in den Zugriffsberechtigungen - %1 nicht möglich %2
|
||||
prevent deleting admin de Verhindert Löschung
|
||||
|
@ -108,6 +108,8 @@ overwrite time common en Overwrite time
|
||||
overwriting start or stop time timesheet en Overwriting start or stop time
|
||||
parent admin en Parent
|
||||
pause common en Pause
|
||||
pause time timesheet en Pause time
|
||||
paused timesheet en Paused
|
||||
permission denied!!! timesheet en Permission denied!
|
||||
permissions error - %1 could not %2 timesheet en Permissions error - %1 could not %2
|
||||
prevent deleting admin en Prevent deleting
|
||||
|
@ -16,7 +16,7 @@ if (!defined('TIMESHEET_APP'))
|
||||
}
|
||||
|
||||
$setup_info[TIMESHEET_APP]['name'] = TIMESHEET_APP;
|
||||
$setup_info[TIMESHEET_APP]['version'] = '23.1';
|
||||
$setup_info[TIMESHEET_APP]['version'] = '23.1.001';
|
||||
$setup_info[TIMESHEET_APP]['app_order'] = 5;
|
||||
$setup_info[TIMESHEET_APP]['tables'] = array('egw_timesheet','egw_timesheet_extra','egw_timesheet_events');
|
||||
$setup_info[TIMESHEET_APP]['enable'] = 1;
|
||||
|
@ -19,7 +19,7 @@ $phpgw_baseline = array(
|
||||
'ts_title' => array('type' => 'varchar','precision' => '255','nullable' => False,'comment' => 'title of the timesheet entry'),
|
||||
'ts_description' => array('type' => 'varchar','precision' => '16384','comment' => 'description of the timesheet entry'),
|
||||
'ts_start' => array('type' => 'int','meta' => 'timestamp','precision' => '8','nullable' => False,'comment' => 'timestamp of the startdate'),
|
||||
'ts_duration' => array('type' => 'int','precision' => '8','nullable' => False,'default' => '0','comment' => 'duration of the timesheet-entry'),
|
||||
'ts_duration' => array('type' => 'int','precision' => '4','nullable' => False,'default' => '0','comment' => 'duration of the timesheet-entry'),
|
||||
'ts_quantity' => array('type' => 'float','precision' => '8','nullable' => False,'comment' => 'quantity'),
|
||||
'ts_unitprice' => array('type' => 'float','precision' => '4','comment' => 'unitprice'),
|
||||
'cat_id' => array('type' => 'int','meta' => 'category','precision' => '4','default' => '0','comment' => 'category'),
|
||||
@ -28,7 +28,8 @@ $phpgw_baseline = array(
|
||||
'ts_modifier' => array('type' => 'int','meta' => 'user','precision' => '4','nullable' => False,'comment' => 'account id of the last modifier'),
|
||||
'pl_id' => array('type' => 'int','precision' => '4','default' => '0','comment' => 'id of the linked project'),
|
||||
'ts_status' => array('type' => 'int','precision' => '4','comment' => 'status of the timesheet-entry'),
|
||||
'ts_created' => array('type' => 'int','meta' => 'timestamp','precision' => '8','nullable' => False,'comment' => 'Creation date of the timesheet')
|
||||
'ts_created' => array('type' => 'int','meta' => 'timestamp','precision' => '8','nullable' => False,'comment' => 'Creation date of the timesheet'),
|
||||
'ts_paused' => array('type' => 'int','precision' => '4','default' => '0','comment' => 'pause time(s) of the timesheet-entry')
|
||||
),
|
||||
'pk' => array('ts_id'),
|
||||
'fk' => array(),
|
||||
|
@ -234,3 +234,14 @@ function timesheet_upgrade22_1()
|
||||
{
|
||||
return $GLOBALS['setup_info']['timesheet']['currentver'] = '23.1';
|
||||
}
|
||||
function timesheet_upgrade23_1()
|
||||
{
|
||||
$GLOBALS['egw_setup']->oProc->AddColumn('egw_timesheet','ts_paused',array(
|
||||
'type' => 'int',
|
||||
'precision' => '4',
|
||||
'default' => '0',
|
||||
'comment' => 'pause time(s) of the timesheet-entry'
|
||||
));
|
||||
|
||||
return $GLOBALS['setup_info']['timesheet']['currentver'] = '23.1.001';
|
||||
}
|
@ -218,17 +218,18 @@ class Events extends Api\Storage\Base
|
||||
*/
|
||||
public function storeWorkingTime()
|
||||
{
|
||||
if (!($events = self::getPending(true, $time)) || !$time)
|
||||
if (!($events = self::getPending(true, $time, $paused)) || !$time)
|
||||
{
|
||||
throw new Api\Exception\AssertionFailed("No pending overall events!");
|
||||
}
|
||||
$ids = array_keys($events);
|
||||
$bo = new \timesheet_bo();
|
||||
// check if we already have a timesheet for the current periode
|
||||
// check if we already have a timesheet for the current period
|
||||
if (($period_ts = $bo->periodeWorkingTimesheet(reset($events)['tse_time'])))
|
||||
{
|
||||
$events = array_merge(self::get(['ts_id' => $period_ts['ts_id']], $period_total), $events);
|
||||
$events = array_merge(self::get(['ts_id' => $period_ts['ts_id']], $period_total, $period_paused), $events);
|
||||
$time += $period_total;
|
||||
$paused += $period_paused;
|
||||
}
|
||||
$title = self::workingTimeTitle($events, $start);
|
||||
$bo->init($period_ts);
|
||||
@ -240,6 +241,7 @@ class Events extends Api\Storage\Base
|
||||
'end_time' => '',
|
||||
'ts_duration' => $minutes = round($time / 60),
|
||||
'ts_quantity' => $minutes / 60.0,
|
||||
'ts_paused' => round($paused / 60),
|
||||
'ts_owner' => $this->user,
|
||||
]);
|
||||
self::addToTimesheet($bo->data['ts_id'], $ids);
|
||||
@ -351,6 +353,15 @@ class Events extends Api\Storage\Base
|
||||
*/
|
||||
protected static function evaluate(array &$timer, array $row)
|
||||
{
|
||||
// paused timer is started or stopped
|
||||
if ($timer['paused'] && !($row['tse_type'] & self::PAUSE))
|
||||
{
|
||||
$timer['was_paused'] = 60000 * round(($row['tse_time']->getTimestamp() - $timer['pause_started']->getTimestamp())/60);
|
||||
}
|
||||
else
|
||||
{
|
||||
unset($timer['was_paused']);
|
||||
}
|
||||
if ($row['tse_type'] & self::START)
|
||||
{
|
||||
$timer['start'] = $timer['started'] = $row['tse_time'];
|
||||
@ -367,6 +378,10 @@ class Events extends Api\Storage\Base
|
||||
{
|
||||
$timer['paused'] = ($row['tse_type'] & self::PAUSE) === self::PAUSE;
|
||||
}
|
||||
if ($timer['paused'])
|
||||
{
|
||||
$timer['pause_started'] = $row['tse_time'];
|
||||
}
|
||||
$timer['last'] = $row['tse_time'];
|
||||
$timer['id'] = $row['tse_id'];
|
||||
return $time ?? null;
|
||||
@ -378,10 +393,11 @@ class Events extends Api\Storage\Base
|
||||
* Not stopped events-sequences are NOT returned (stopped sequences end with a stop event).
|
||||
*
|
||||
* @param int|array $filter
|
||||
* @param int &$total=null on return time in seconds
|
||||
* @param ?int &$total=null on return time in seconds
|
||||
* @param ?int &$paused on return paused time in seconds
|
||||
* @return array[] tse_id => array pairs plus extra key sum (time-sum in seconds)
|
||||
*/
|
||||
public static function get($filter, int &$total=null)
|
||||
public static function get($filter, ?int &$total=null, ?int &$paused=null)
|
||||
{
|
||||
if (!is_array($filter))
|
||||
{
|
||||
@ -392,7 +408,7 @@ class Events extends Api\Storage\Base
|
||||
'offset' => 0,
|
||||
'paused' => false,
|
||||
];
|
||||
$total = $open = 0;
|
||||
$total = $open = $paused = 0;
|
||||
$events = [];
|
||||
foreach(self::getInstance()->search('', false, 'tse_id', '', '',
|
||||
false, 'AND', false, $filter) as $row)
|
||||
@ -411,6 +427,7 @@ class Events extends Api\Storage\Base
|
||||
$row['total'] = $total + $timer['offset'] / 1000;
|
||||
}
|
||||
$row['time'] = $time / 1000;
|
||||
$row['paused'] = !empty($timer['was_paused']) ? $paused += $timer['was_paused']/1000 : null;
|
||||
$events[$row['tse_id']] = $row;
|
||||
}
|
||||
// remove open / unstopped timer events
|
||||
@ -427,16 +444,17 @@ class Events extends Api\Storage\Base
|
||||
* Not stopped events-sequences are NOT returned (stopped sequences end with a stop event).
|
||||
*
|
||||
* @param bool $overall
|
||||
* @param int &$time=null on return total time in seconds
|
||||
* @param ?int &$time=null on return total time in seconds
|
||||
* @param ?int &$paused=null on return total paused time in seconds
|
||||
* @return array[] tse_id => array pairs
|
||||
*/
|
||||
public static function getPending($overall=false, int &$time=null)
|
||||
public static function getPending($overall=false, ?int &$time=null, ?int &$paused=null)
|
||||
{
|
||||
return self::get([
|
||||
'ts_id' => null,
|
||||
'account_id' => self::getInstance()->user,
|
||||
($overall ? '' : 'NOT ').'(tse_type & '.self::OVERALL.')',
|
||||
], $time);
|
||||
], $time, $paused);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -52,6 +52,7 @@ class JsTimesheet extends Api\CalDAV\JsBase
|
||||
'description' => $timesheet['description'],
|
||||
'start' => self::UTCDateTime($timesheet['start'], true),
|
||||
'duration' => (int)$timesheet['duration'],
|
||||
'paused' => (int)$timesheet['paused'],
|
||||
'project' => $timesheet['project_blur'] ?? null,
|
||||
'pm_id' => !empty($timesheet['pm_id']) ? (int)$timesheet['pm_id'] : null,
|
||||
'quantity' => (double)$timesheet['quantity'],
|
||||
@ -132,6 +133,10 @@ class JsTimesheet extends Api\CalDAV\JsBase
|
||||
}
|
||||
break;
|
||||
|
||||
case 'paused':
|
||||
$timesheet['ts_paused'] = self::parseInt($value);
|
||||
break;
|
||||
|
||||
case 'pricelist':
|
||||
$timesheet['pl_id'] = self::parseInt($value);
|
||||
break;
|
||||
|
@ -38,15 +38,13 @@
|
||||
<row class="row" disabled="!@ts_viewtype">
|
||||
<et2-description value="comment"></et2-description>
|
||||
<et2-textarea id="ts_description_short" rows="5" cols="50"></et2-textarea>
|
||||
<et2-description></et2-description>
|
||||
<et2-description></et2-description>
|
||||
<et2-description></et2-description>
|
||||
</row>
|
||||
</row>
|
||||
<row class="row" disabled="@ts_viewtype">
|
||||
<et2-description value="Quantity" for="ts_quantity"></et2-description>
|
||||
<et2-number statustext="empty if identical to duration" id="ts_quantity" precision="3" placeholder="@ts_quantity_blur"></et2-number>
|
||||
<et2-description></et2-description>
|
||||
</row>
|
||||
<et2-description></et2-description>
|
||||
<et2-date-duration label="Pause time" id="ts_paused" displayFormat="hm" span="all"></et2-date-duration>
|
||||
</row>
|
||||
<row class="row" disabled="@ts_viewtype">
|
||||
<et2-description value="Category" for="cat_id"></et2-description>
|
||||
<et2-select-cat span="all" id="cat_id" application="timesheet" emptyLabel="None"></et2-select-cat>
|
||||
@ -86,6 +84,7 @@
|
||||
<column/>
|
||||
<column/>
|
||||
<column/>
|
||||
<column/>
|
||||
</columns>
|
||||
<rows>
|
||||
<row class="th">
|
||||
@ -93,6 +92,7 @@
|
||||
<et2-description value="Recorded"></et2-description>
|
||||
<et2-description value="Type"></et2-description>
|
||||
<et2-description value="Duration"></et2-description>
|
||||
<et2-description value="Paused"></et2-description>
|
||||
<et2-description value="Sum"></et2-description>
|
||||
</row>
|
||||
<row id="timesheet-events::$row_cont[tse_id]">
|
||||
@ -100,6 +100,7 @@
|
||||
<et2-date-time id="${row}[tse_timestamp]" readonly="true"></et2-date-time>
|
||||
<et2-select id="${row}[tse_type]" readonly="true"></et2-select>
|
||||
<et2-date-duration id="${row}[time]" readonly="true" displayFormat="h:m" dataFormat="s"></et2-date-duration>
|
||||
<et2-date-duration id="${row}[paused]" readonly="true" displayFormat="h:m" dataFormat="s"></et2-date-duration>
|
||||
<et2-date-duration id="${row}[total]" readonly="true" displayFormat="h:m" dataFormat="s"></et2-date-duration>
|
||||
</row>
|
||||
</rows>
|
||||
|
@ -19,6 +19,7 @@
|
||||
<column width="70%"/>
|
||||
<column width="15%"/>
|
||||
<column width="60"/>
|
||||
<column width="60"/>
|
||||
<column width="60" disabled="@no_ts_quantity"/>
|
||||
<column width="60" disabled="@no_ts_unitprice"/>
|
||||
<column width="60" disabled="@no_ts_total"/>
|
||||
@ -54,16 +55,20 @@
|
||||
<nextmatch-sortheader label="Category" id="cat_id"/>
|
||||
<et2-vbox>
|
||||
<nextmatch-sortheader label="Duration" id="ts_duration"/>
|
||||
<et2-date-duration id="duration" readonly="true" displayFormat="hm"></et2-date-duration>
|
||||
<et2-date-duration id="duration" readonly="true" displayFormat="hm" align="right"></et2-date-duration>
|
||||
</et2-vbox>
|
||||
<et2-vbox>
|
||||
<nextmatch-sortheader label="Paused" id="ts_paused"/>
|
||||
<et2-date-duration id="paused" readonly="true" displayFormat="hm" align="right"></et2-date-duration>
|
||||
</et2-vbox>
|
||||
<et2-vbox>
|
||||
<nextmatch-sortheader label="Quantity" id="ts_quantity"/>
|
||||
<et2-number id="quantity" readonly="true" precision="3"></et2-number>
|
||||
<et2-number id="quantity" readonly="true" precision="3" align="right"></et2-number>
|
||||
</et2-vbox>
|
||||
<nextmatch-sortheader label="Price" id="ts_unitprice"/>
|
||||
<et2-vbox>
|
||||
<nextmatch-sortheader label="Total" id="ts_total"/>
|
||||
<et2-number id="price" readonly="true" precision="2"></et2-number>
|
||||
<et2-number id="price" readonly="true" precision="2" align="right"></et2-number>
|
||||
</et2-vbox>
|
||||
<et2-nextmatch-header-filter id="ts_owner" class="$cont[ownerClass]" noLang="1" emptyLabel="User"/>
|
||||
<nextmatch-sortheader label="Created" id="ts_created"/>
|
||||
@ -80,8 +85,8 @@
|
||||
<et2-description id="${row}[ts_description]" class="ts_description" noLang="1"></et2-description>
|
||||
</et2-vbox>
|
||||
<et2-select-cat class="noWrap" id="${row}[cat_id]" readonly="true"></et2-select-cat>
|
||||
<et2-date-duration id="${row}[ts_duration]" readonly="true" align="right" displayFormat="hm"
|
||||
selectUnit="false"></et2-date-duration>
|
||||
<et2-date-duration id="${row}[ts_duration]" readonly="true" align="right" displayFormat="hm" selectUnit="false"></et2-date-duration>
|
||||
<et2-date-duration id="${row}[ts_paused]" readonly="true" align="right" displayFormat="hm" selectUnit="false"></et2-date-duration>
|
||||
<et2-number id="${row}[ts_quantity]" readonly="true" precision="3" noLang="1"></et2-number>
|
||||
<et2-description id="${row}[ts_unitprice]" noLang="1"></et2-description>
|
||||
<et2-number id="${row}[ts_total]" readonly="true" precision="2" noLang="1"></et2-number>
|
||||
|
Loading…
Reference in New Issue
Block a user