WIP timesheet timers: allow to add more time on an existing timesheet with right click "Start timer"

This commit is contained in:
ralf 2022-10-07 19:36:57 +02:00
parent 03fdceb847
commit d8e993dc75
5 changed files with 174 additions and 18 deletions

View File

@ -59,7 +59,8 @@ egw.extend('timer', egw.MODULE_GLOBAL, function()
// initiate specific timer, only if running or paused // initiate specific timer, only if running or paused
if (_state.specific?.start || _state.specific?.paused) if (_state.specific?.start || _state.specific?.paused)
{ {
startTimer(specific, _state.specific?.start, _state.specific?.offset); // to show offset / paused time startTimer(specific, _state.specific?.start, _state.specific?.offset, _state.specific.app_id); // to show offset / paused time
specific.id = _state.specific.id;
if (_state.specific?.paused) if (_state.specific?.paused)
{ {
stopTimer(specific, true); stopTimer(specific, true);
@ -126,10 +127,25 @@ egw.extend('timer', egw.MODULE_GLOBAL, function()
break; break;
} }
// persist state // persist state
egw.request('timesheet.EGroupware\\Timesheet\\Events.ajax_event', [getState(_action, _time)]).then(() => { egw.request('timesheet.EGroupware\\Timesheet\\Events.ajax_event', [getState(_action, _time)]).then((tse_id) => {
if (_action.substring(8) === 'specific')
{
specific.id = tse_id;
}
if (_action === 'specific-stop') if (_action === 'specific-stop')
{ {
egw.open(null, 'timesheet', 'add', {events: 'specific'}); let type = 'add';
let extra = {events: 'specific'};
if (specific.app_id && specific.app_id.substring(0, 11) === 'timesheet::')
{
extra.ts_id = specific.app_id.substring(11);
type = 'edit';
}
egw.open(null, 'timesheet', type, extra);
// unset the app_id and the tse_id to not associate the next start with it
specific.app_id = undefined;
specific.id = undefined;
} }
}); });
} }
@ -231,8 +247,9 @@ egw.extend('timer', egw.MODULE_GLOBAL, function()
* @param object _timer * @param object _timer
* @param string|Date|undefined _start to initialise with time different from current time * @param string|Date|undefined _start to initialise with time different from current time
* @param number|undefined _offset to set an offset * @param number|undefined _offset to set an offset
* @param string|undefined _app_id
*/ */
function startTimer(_timer, _start, _offset) function startTimer(_timer, _start, _offset, _app_id)
{ {
// update _timer state object // update _timer state object
if (_start) if (_start)
@ -249,6 +266,7 @@ egw.extend('timer', egw.MODULE_GLOBAL, function()
} }
_timer.offset = 0; // it's now set in start-time _timer.offset = 0; // it's now set in start-time
_timer.paused = false; _timer.paused = false;
_timer.app_id = _app_id;
// update now // update now
update(); update();
@ -311,6 +329,44 @@ egw.extend('timer', egw.MODULE_GLOBAL, function()
} }
return { return {
/**
* Start timer for given app and id
*
* @param {Object} _action
* @param {Array} _senders
*/
start_timer(_action, _senders)
{
if (_action.parent.data.nextmatch?.getSelection().all || _senders.length !== 1)
{
egw.message(egw.lang('You must select a single entry!'), 'error');
return;
}
// timer already running, ask user if he wants to associate it with the entry, or cancel
if (specific.start || specific.paused)
{
Et2Dialog.show_dialog((_button) => {
if (_button === Et2Dialog.OK_BUTTON)
{
if (specific.paused)
{
startTimer(specific, undefined, undefined, _senders[0].id);
}
else
{
specific.app_id = _senders[0].id;
egw.request('timesheet.EGroupware\\Timesheet\\Events.ajax_updateAppId', [specific.id, specific.app_id]);
}
}
},
egw.lang('Do you want to associate it with the selected %1 entry?', egw.lang(_senders[0].id.split('::')[0])),
egw.lang('Timer already running or paused'), {},
Et2Dialog.BUTTONS_OK_CANCEL, Et2Dialog.QUESTION_MESSAGE, undefined, egw);
return;
}
startTimer(specific, undefined, undefined, _senders[0].id);
},
/** /**
* Create timer in top-menu * Create timer in top-menu
* *

View File

@ -82,27 +82,57 @@ class timesheet_ui extends timesheet_bo
{ {
$this->data = array( $this->data = array(
'ts_start' => $this->today, 'ts_start' => $this->today,
'start_time' => '', // force empty start-time 'start_time' => '', // force empty start-time
'end_time' => Api\DateTime::to($this->now,'H:i'), 'end_time' => Api\DateTime::to($this->now, 'H:i'),
'ts_owner' => $GLOBALS['egw_info']['user']['account_id'], 'ts_owner' => $GLOBALS['egw_info']['user']['account_id'],
'cat_id' => (int) $_REQUEST['cat_id'], 'cat_id' => (int)$_REQUEST['cat_id'],
'ts_status'=> $GLOBALS['egw_info']['user']['preferences']['timesheet']['predefined_status'], 'ts_status' => $GLOBALS['egw_info']['user']['preferences']['timesheet']['predefined_status'],
'ts_project' => $_REQUEST['ts_project'], 'ts_project' => $_REQUEST['ts_project'],
'ts_title_blur' => $_REQUEST['ts_project'], 'ts_title_blur' => $_REQUEST['ts_project'],
'events' => [],
); );
if(!is_numeric($_REQUEST['ts_project'])) if (!is_numeric($_REQUEST['ts_project']))
{ {
$this->data['pm_id'] = $this->find_pm_id($_REQUEST['ts_project']); $this->data['pm_id'] = $this->find_pm_id($_REQUEST['ts_project']);
} }
if (isset($_REQUEST['events'])) }
// 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);
$this->data['events'] = array_merge($this->data['events'], array_values($pending));
$start = $this->data['events'][0]['tse_time'];
$this->data['ts_start'] = $start;
$this->data['start_time'] = Api\DateTime::server2user($start, 'H:s');
$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
// check if any of the events contains an app::id to link the timesheet to
foreach($pending as $event)
{ {
$this->data['events'] = array_values(Events::getPending($_REQUEST['events'] === 'overall', $time)); if (!empty($event['tse_app']) && $event['tse_app'] !== TIMESHEET_APP && !empty($event['tse_app_id']))
$start = $this->data['events'][0]['tse_time']; {
$this->data['ts_start'] = $start; // existing timesheets can be directly linked (takes care to not link multiple times)
$this->data['start_time'] = Api\DateTime::server2user($start, 'H:s'); if ($this->data['ts_id'])
$this->data['end_time'] = ''; {
$this->data['ts_duration'] = round($time / 60); // minutes Api\Link::link(TIMESHEET_APP, $this->data['ts_id'], $event['tse_app'], $event['tse_app_id']);
$this->data['ts_quantity'] = $this->data['ts_duration'] / 60.0; // hours }
// new timesheets will be linked on saving (need to check to not add multiple times)
else
{
if (!isset($this->data['link_to']['to_id']))
{
$this->data['link_to']['to_id'] = [];
}
if (!in_array($app_id = [
'to_app' => $event['tse_app'],
'to_id' => $event['tse_app_id'],
], $this->data['link_to']['to_id']))
{
$this->data['link_to']['to_id'][] = $app_id;
}
}
}
} }
} }
if (!empty($this->data['events'])) if (!empty($this->data['events']))
@ -288,7 +318,8 @@ class timesheet_ui extends timesheet_bo
{ {
Link::link(TIMESHEET_APP,$this->data['ts_id'],$content['link_to']['to_id']); Link::link(TIMESHEET_APP,$this->data['ts_id'],$content['link_to']['to_id']);
} }
if (empty($content['ts_id']) && !empty($content['events'])) // associate events with the now stored timesheet (need to run for existing timesheets too, if new events are added!)
if (!empty($content['events']))
{ {
Events::addToTimesheet($this->data['ts_id'], array_map(static function($event) Events::addToTimesheet($this->data['ts_id'], array_map(static function($event)
{ {
@ -1117,6 +1148,19 @@ class timesheet_ui extends timesheet_bo
'disableClass' => 'th', 'disableClass' => 'th',
); );
} }
// if specific timer is NOT disabled, allow to book further time on existing sheets
$config = Api\Config::read(TIMESHEET_APP);
if (!in_array('specific', $config['disable_timer'] ?? []))
{
$actions['timer'] = array(
'icon' => 'timesheet/navbar',
'caption' => 'Start timer',
'onExecute' => 'javaScript:app.timesheet.egw.start_timer',
'allowOnMultiple' => false,
'group' => $group,
'disableClass' => 'th',
);
}
$group++; $group++;
$actions += array( $actions += array(
'documents' => timesheet_merge::document_action( 'documents' => timesheet_merge::document_action(

View File

@ -29,6 +29,7 @@ deletes this field timesheet de Dieses Feld löschen
determines the order the fields are displayed timesheet de verändert die Reihenfolge der angezeigten Felder determines the order the fields are displayed timesheet de verändert die Reihenfolge der angezeigten Felder
directory with documents to insert entries timesheet de Ordner mit Dokumenten zum Einfügen. directory with documents to insert entries timesheet de Ordner mit Dokumenten zum Einfügen.
disable timers timesheet de Zeitnehmer ausschalten disable timers timesheet de Zeitnehmer ausschalten
do you want to associate it with the selected %1 entry? common de Wollen Sie ihn mit dem ausgewählten %1 Eintrag verbinden?
each value is a line like <id>[=<label>] timesheet de jeder Wert ist eine Zeile in dem Format <id>[=|label>] each value is a line like <id>[=<label>] timesheet de jeder Wert ist eine Zeile in dem Format <id>[=|label>]
edit status common de Status bearbeiten edit status common de Status bearbeiten
edit this entry timesheet de diesen Eintrag bearbeiten edit this entry timesheet de diesen Eintrag bearbeiten
@ -129,6 +130,7 @@ show a quantity sum (eg. to sum up negative overtime) admin de Zeige eine Mengen
skip record timesheet de Eintrag überspringen skip record timesheet de Eintrag überspringen
start timesheet de Start start timesheet de Start
start & stop timer common de Zeitnehmer starten & stoppen start & stop timer common de Zeitnehmer starten & stoppen
start timer timesheet de Zeitnehmer starten
starttime timesheet de Startzeit starttime timesheet de Startzeit
starttime has to be before endtime !!! timesheet de Startzeit muss vor der Endzeit liegen !!! starttime has to be before endtime !!! timesheet de Startzeit muss vor der Endzeit liegen !!!
status timesheet de Status status timesheet de Status
@ -149,6 +151,7 @@ this week timesheet de Diese Woche
this year timesheet de Dieses Jahr this year timesheet de Dieses Jahr
ticket modified by %1 at %2 timesheet de Ticket geändert von %1 am %2 ticket modified by %1 at %2 timesheet de Ticket geändert von %1 am %2
timer common de Zeitnehmer timer common de Zeitnehmer
timer already running or paused common de Zeitnehmer läuft bereits oder ist pausiert
timesheet common de Stundenzettel timesheet common de Stundenzettel
timesheet csv export timesheet de Export des Stundenzettels als CSV timesheet csv export timesheet de Export des Stundenzettels als CSV
timesheet csv import timesheet de Stundenzettels CSV Import timesheet csv import timesheet de Stundenzettels CSV Import

View File

@ -29,6 +29,7 @@ deletes this field timesheet en Deletes this field
determines the order the fields are displayed timesheet en Determines the order the fields are displayed determines the order the fields are displayed timesheet en Determines the order the fields are displayed
directory with documents to insert entries timesheet en Directory with documents to insert entries directory with documents to insert entries timesheet en Directory with documents to insert entries
disable timers timesheet en Disable timers disable timers timesheet en Disable timers
do you want to associate it with the selected %1 entry? common en Do you want to associate it with the selected %1 entry?
each value is a line like <id>[=<label>] timesheet en Each value is a line like <id>[=<label>] each value is a line like <id>[=<label>] timesheet en Each value is a line like <id>[=<label>]
edit status common en Edit status edit status common en Edit status
edit this entry timesheet en Edit this entry edit this entry timesheet en Edit this entry
@ -129,6 +130,7 @@ show a quantity sum (eg. to sum up negative overtime) admin en Show a quantity s
skip record timesheet en Skip record skip record timesheet en Skip record
start timesheet en Start start timesheet en Start
start & stop timer common en Start & stop timer start & stop timer common en Start & stop timer
start timer timesheet en Start timer
starttime timesheet en Start time starttime timesheet en Start time
starttime has to be before endtime !!! timesheet en Start time has to be before endtime! starttime has to be before endtime !!! timesheet en Start time has to be before endtime!
status timesheet en Status status timesheet en Status
@ -149,6 +151,7 @@ this week timesheet en This week
this year timesheet en This year this year timesheet en This year
ticket modified by %1 at %2 timesheet en Ticket modified by %1 at %2 ticket modified by %1 at %2 timesheet en Ticket modified by %1 at %2
timer common en Timer timer common en Timer
timer already running or paused common en Timer already running or paused
timesheet common en Time Sheet timesheet common en Time Sheet
timesheet csv export timesheet en TimeSheet CSV export timesheet csv export timesheet en TimeSheet CSV export
timesheet csv import timesheet en TimeSheet CSV import timesheet csv import timesheet en TimeSheet CSV import

View File

@ -75,6 +75,7 @@ class Events extends Api\Storage\Base
* Record/persist timer events from UI * Record/persist timer events from UI
* *
* @param array $state * @param array $state
* @return int tse_id of created event
* @throws Api\Exception * @throws Api\Exception
* @throws Api\Exception\WrongParameter * @throws Api\Exception\WrongParameter
*/ */
@ -92,6 +93,16 @@ class Events extends Api\Storage\Base
$type = ($timer === 'overall' ? self::OVERALL : 0) | $type = ($timer === 'overall' ? self::OVERALL : 0) |
($action === 'start' ? self::START : ($action === 'stop' ? self::STOP : self::PAUSE)); ($action === 'start' ? self::START : ($action === 'stop' ? self::STOP : self::PAUSE));
$app = $id = $ts_id = null;
if ($timer === 'specific' && !empty($state['app_id']))
{
list($app, $id) = explode('::', $state['app_id'], 2);
if ($app === self::APP)
{
$ts_id = $id;
}
}
$this->init(); $this->init();
$this->save([ $this->save([
'tse_timestamp' => new Api\DateTime(), 'tse_timestamp' => new Api\DateTime(),
@ -99,6 +110,9 @@ class Events extends Api\Storage\Base
'account_id' => $this->user, 'account_id' => $this->user,
'tse_modifier' => $this->user, 'tse_modifier' => $this->user,
'tse_type' => $type, 'tse_type' => $type,
'tse_app' => $app,
'tse_app_id' => $id,
'ts_id' => $ts_id,
]); ]);
// create timesheet for stopped working time // create timesheet for stopped working time
@ -113,6 +127,27 @@ class Events extends Api\Storage\Base
Api\Json\Response::get()->message(lang('Error storing working time').': '.$e->getMessage(), 'error'); Api\Json\Response::get()->message(lang('Error storing working time').': '.$e->getMessage(), 'error');
} }
} }
return $this->data['tse_id'];
}
/**
* Set app::id on an already started timer
*
* @param int $tse_id id of the running timer-event
* @param string $app_id "app::id" string
* @return void
* @throws Api\Db\Exception\InvalidSql
*/
public function ajax_updateAppId(int $tse_id, string $app_id)
{
if ($tse_id > 0 && !empty($app_id))
{
list($app, $id) = explode('::', $app_id);
$this->db->update(self::TABLE, [
'tse_app' => $app,
'tse_app_id' => $id,
], ['tse_id' => $tse_id], __LINE__, __FILE__, self::APP);
}
} }
/** /**
@ -184,6 +219,21 @@ class Events extends Api\Storage\Base
else else
{ {
self::evaluate($state['specific'], $row); self::evaluate($state['specific'], $row);
// if event is associated with an app:id, set it again
if ($row['tse_app'] && $row['tse_app_id'])
{
$state['specific']['app_id'] = $row['tse_app'].'::'.$row['tse_app_id'];
}
// for not stopped events, set the tse_id, so we can associate with an "app:id"
if ($row['tse_type'] & self::STOP)
{
$state['specific']['id'] = null;
}
else
{
$state['specific']['id'] = $row['tse_id'];
}
} }
} }
// format start-times in UTZ as JavaScript Date() understands // format start-times in UTZ as JavaScript Date() understands