togglで勤怠管理、Redmineのチケットに作業時間を付ける運用時、両者の合計は一致するはずだが、手入力運用では面倒。
togglデータにRedmineのチケット情報を持たせて、Redmineへ時間データを付けられたら必ず一致して手間が省けて超便利です。
ということで各APIを呼び出し、処理するツールを作成しました。
フロー
前提
togglのdescriptionに、所定の形式でRedmineのチケット番号を書く
(例)黄色部分。チケット番号 12533

コード
※細かいエラー処理は省いています。
※実際のコードから抜粋しているため、修正ミスがある(このままでは動かない)かもしれません。あくまでざっとした流れの参考として見てください。
toggl APIを利用してデータ取得するメソッド
ToggleRepository.php
/**
* Toggglの作業記録を取得する
*
* @param string $apiToken
* @param string $startDate=''
* @param string $endDate=''
* @return mixed
*
*/
public function getEntries(
string $apiToken,
string $startDate='',
string $endDate=''
): mixed
{
$togglUri = 'https://api.track.toggl.com/api/v9/me/time_entries'; // 実際はconfig等に設定しておく
$options = ['auth' => [$apiToken, 'api_token']]; // 取得対象のユーザのapi tokenをセットする
$query = http_build_query([
'start_date' => $startDate,
'end_date' => $endDate,
]);
$entryRequest = (new \GuzzleHttp\Client())->request('GET', "{$togglUri}?{$query}", $options);
$res = $entryRequest->getBody()->getContents();
return json_decode($res, true);
}
Redmine APIを利用してデータ登録するメソッド
/**
* Redmineに時間エントリを登録する
*
* @param array $xmlValues
* @return bool
*
*/
public function addTimeEntry(string $apiToken, array $xmlValues) : bool
{
$xml = new \SimpleXMLElement('<time_entry></time_entry>');
foreach ($xmlValues as $key => $value) {
$xml->addChild($key, (string)$value);
}
//Redmine に作業時間を登録する
$redmineUri = self::REDMINE_URL . "time_entries.xml?key={$apiToken}";
$request = (new \GuzzleHttp\Client())->request('POST', $redmineUri, [
'headers' => ['Content-Type' => 'text/xml',],
'body' => $xml->asXML()
]);
return true;
}
メイン処理
/**
* main処理
* @return array
*/
private function exec() :array
{
$this->togglApiToken = 'xxxxxxxxxxxxx' // 取得対象のユーザのapi token
$this->redmineApiKey = 'ZZZZZZZZZZZZZ'; // 対象ユーザのRedmineのAPIキー
$this->redmineUserId = 0; // 対象ユーザのRedmineのユーザID
// toggl : 作業記録を取得する
$entries = $this->toggleRepository->getEntries(
$this->togglApiToken,
$this->dateTime->setTime(00, 00, 00)->format('c'),
$this->dateTimeEnd->setTime(23, 59, 59)->format('c')
);
// toggl の作業内容から Redmine用の作業時間データを作成し登録する
return $this->makeRecord($entries);
}
/**
* Toggl の作業内容から Redmine用の作業時間データを作成し登録する
*
* @param array $entries
* @param array $redmineTimeRecs
* @return array : 実行結果
*
*/
private function makeRecord(array $entries, array $redmineTimeRecs) : array
{
foreach ($entries as $entry) {
$description = '';
// 作業時間の時間
// 開始・終了の差分
$start = new \DateTime($entry['start']);
$start->setTimezone($this->timezone);
if (!isset($entry['stop'])) {
$this->results->addIssueSkipNowExec($start, $description, 'timer 実行中です');
continue;
}
$stop = new \DateTime($entry['stop']);
$stop->setTimezone($this->timezone);
$diff = $stop->diff($start);
$min = round(($diff->i / 60), 3);
$diffHours = $diff->h + $min;
// Redmine でタイマーを打ったときのフォーマットに沿って` #1234 `の文字列をチケットIDとして検索
preg_match('/\#([0-9]+)/', $description, $preg_match_results);
//Redmine に作業時間を登録する
$ret = $this->addToRedmine($issueId, $entry, $start, $diffHours, $description);
if ($ret) {
// 成功時処理
} else {
// エラー時処理
}
}
return [
// 結果
];
}
/**
* Redmine に作業時間を登録する
*
* @param int $issueId
* @param array $entry
* @param Datetime $start
* @param float $diffHours
* @param string $description
* @return bool
*
*/
private function addToRedmine (
int $issueId,
array $entry,
Datetime $start,
float $diffHours,
string $description
) : bool
{
$xmlValues = [
'issue_id' => $issueId,
'spent_on' => $start->format('Y-m-d'),
'hours' => $diffHours,
'activity_id' => 8, // $entry['tags']からタグが取得できるので、必要ならRedmineのタグのIDと対応させてセットする
'user_id' => $this->redmineUserId, // 対象ユーザのRedmineのユーザID
'comments' => $description,
];
return $this->redmineRepository->addTimeEntry($this->redmineApiKey, $xmlValues);
}
実行結果
Redmine、赤部分が追加されている。

Note
実際には、以下の処理に対応しています
・すでにredmineに時間登録済みか?重複チェック
・redmineに該当番号のチケットがあるか?チェック
・該当番号のチケットにそのユーザが権限を持っているか?チェック