mirror of
https://github.com/EGroupware/egroupware.git
synced 2024-12-22 14:41:29 +01:00
WIP timesheet timers: allow to add more time on an existing timesheet with right click "Start timer"
This commit is contained in:
parent
03fdceb847
commit
d8e993dc75
@ -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
|
||||||
*
|
*
|
||||||
|
@ -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(
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user