コード例 #1
0
ファイル: Status.php プロジェクト: nickschuch/tl
 /**
  * {@inheritdoc}
  */
 protected function execute(InputInterface $input, OutputInterface $output)
 {
     // Allow special date param in the form of _1 for back one day, _2 for two
     // days etc.
     $date = $input->getArgument('date');
     if (preg_match('/_(\\d+)/', $date, $matches)) {
         $date = date('Y-m-d', time() - 86400 * $matches[1]);
     }
     $data = $this->repository->status($date);
     $table = new Table($output);
     $table->setHeaders(['Slot', 'JobId', 'Time', 'Title']);
     $rows = [];
     $total = 0;
     foreach ($data as $record) {
         $total += $record->duration;
         $details = $this->connector->ticketDetails($record->tid);
         if (!empty($record->active)) {
             $record->tid .= ' *';
         }
         $rows[] = [$record->id, $record->tid, Formatter::formatDuration($record->duration), $details->getTitle()];
     }
     $rows[] = new TableSeparator();
     $rows[] = ['', '<comment>Total</comment>', '<info>' . Formatter::formatDuration($total) . '</info>', ''];
     $table->setRows($rows);
     $table->render();
 }
コード例 #2
0
ファイル: Open.php プロジェクト: nickschuch/tl
 /**
  * {@inheritdoc}
  */
 protected function execute(InputInterface $input, OutputInterface $output)
 {
     if ($data = $this->repository->getActive()) {
         $details = $this->connector->ticketDetails($data->tid);
         $output->writeLn(sprintf('%s [<info>%d</info>] - <comment>%s</comment> [slot: <comment>%d</comment>]', $details->getTitle(), $data->tid, Formatter::formatDuration(time() - $data->start), $data->id));
         return;
     }
     $output->writeln('<error>No active slot</error>');
 }
コード例 #3
0
ファイル: Delete.php プロジェクト: nickschuch/tl
 /**
  * {@inheritdoc}
  */
 protected function execute(InputInterface $input, OutputInterface $output)
 {
     $slot_id = $input->getArgument('slot_id');
     $helper = $this->getHelper('question');
     $question = new ConfirmationQuestion('Are you sure?', false);
     $confirm = NULL;
     if (($slot = $this->repository->slot($slot_id)) && ($confirm = $helper->ask($input, $output, $question)) && $this->repository->delete($slot_id)) {
         $deleted = $this->connector->ticketDetails($slot->tid);
         $output->writeln(sprintf('Deleted slot <comment>%d</comment> against ticket <info>%d</info>: %s, duration <info>%s</info>', $slot->id, $slot->tid, $deleted->getTitle(), Formatter::formatDuration($slot->end - $slot->start)));
         return;
     }
     if ($confirm !== FALSE) {
         $output->writeln('<error>Cannot delete slot, either does not exist or has been sent to back end.</error>');
     }
 }
コード例 #4
0
ファイル: Stop.php プロジェクト: nickschuch/tl
 /**
  * {@inheritdoc}
  */
 protected function execute(InputInterface $input, OutputInterface $output)
 {
     if ($stop = $this->repository->stop()) {
         $stopped = $this->connector->ticketDetails($stop->tid);
         $output->writeln(sprintf('<bg=blue;fg=white;options=bold>[%s]</> Closed slot <comment>%d</comment> against ticket <info>%d</info>: %s, duration <info>%s</info>', (new \DateTime())->format('h:i'), $stop->id, $stop->tid, $stopped->getTitle(), Formatter::formatDuration($stop->duration)));
         if (($comment = $input->getOption('comment')) || $input->getOption('pause')) {
             if ($this->connector->pause($stop->tid, $comment)) {
                 $output->writeln(sprintf('Ticket <comment>%s</comment> set to paused.', $stop->tid));
             } else {
                 $output->writeln('<error>Could not update ticket status</error>');
             }
         }
         return;
     }
     $output->writeln('<error>No active slot</error>');
 }
コード例 #5
0
ファイル: Reviewer.php プロジェクト: nickschuch/tl
 public function getSummary($date = 19780101, $check = FALSE, $exact = FALSE)
 {
     $data = $this->repository->review($date, $check);
     if (count($data) == 0 && !$check) {
         throw new \Exception('All entries stored in remote system');
     }
     $total = 0;
     $offline = FALSE;
     try {
         $categories = $this->connector->fetchCategories();
     } catch (ConnectException $e) {
         $offline = TRUE;
     }
     $exact_total = 0;
     foreach ($data as $record) {
         $total += $record->duration;
         $details = $this->connector->ticketDetails($record->tid);
         $category_id = str_pad($record->category, 3, 0, STR_PAD_LEFT);
         $category = '';
         if ($record->category) {
             if ($offline) {
                 $category = 'Offline';
             } elseif (isset($categories[$category_id])) {
                 $category = $categories[$category_id];
             } else {
                 $category = 'Unknown';
             }
         }
         $row = [$record->id, $record->tid, $record->duration];
         if ($exact) {
             $row[] = Formatter::formatDuration($record->end - $record->start);
             $exact_total += $record->end - $record->start;
         }
         $row = array_merge($row, [substr($details->getTitle(), 0, 25) . '...', $category, $record->comment]);
         $rows[] = $row;
     }
     $rows[] = new TableSeparator();
     if ($exact) {
         $rows[] = ['', '<comment>Total</comment>', '<info>' . $total . ' h</info>', '<info>' . Formatter::formatDuration($exact_total) . '</info>', '', '', ''];
     } else {
         $rows[] = ['', '<comment>Total</comment>', '<info>' . $total . ' h</info>', '', '', ''];
     }
     return $rows;
 }
コード例 #6
0
ファイル: Start.php プロジェクト: nickschuch/tl
 /**
  * {@inheritdoc}
  */
 protected function execute(InputInterface $input, OutputInterface $output)
 {
     $ticket_id = $input->getArgument('issue_number');
     if ($alias = $this->repository->loadAlias($ticket_id)) {
         $ticket_id = $alias;
     }
     if ($title = $this->connector->ticketDetails($ticket_id)) {
         if ($stop = $this->repository->stop()) {
             $stopped = $this->connector->ticketDetails($stop->tid);
             $output->writeln(sprintf('Closed slot <comment>%d</comment> against ticket <info>%d</info>: %s, duration <info>%s</info>', $stop->id, $stop->tid, $stopped->getTitle(), Formatter::formatDuration($stop->duration)));
         }
         try {
             list($slot_id, $continued) = $this->repository->start($ticket_id, $input->getArgument('comment'));
             $output->writeln(sprintf('<bg=blue;fg=white;options=bold>[%s]</> <comment>%s</comment> entry for <info>%d</info>: %s [slot:<comment>%d</comment>]', (new \DateTime())->format('h:i'), $continued ? 'Continued' : 'Started new', $ticket_id, $title->getTitle(), $slot_id));
             if ($input->getOption('status')) {
                 if ($this->connector->setInProgress($ticket_id, $assign = $input->getOption('assign'))) {
                     $output->writeln(sprintf('Ticket <comment>%s</comment> set to in-progress.', $ticket_id));
                     if ($assign) {
                         $output->writeln(sprintf('Ticket <comment>%s</comment> assigned to you.', $ticket_id));
                     }
                 } else {
                     $output->writeln('<error>Could not update ticket status</error>');
                     if ($assign) {
                         $output->writeln('<error>Could not assign ticket</error>');
                     }
                 }
             } elseif ($input->getOption('assign')) {
                 if ($this->connector->assign($ticket_id)) {
                     $output->writeln(sprintf('Ticket <comment>%s</comment> assigned to you.', $ticket_id));
                 } else {
                     $output->writeln('<error>Could not assign ticket</error>');
                 }
             }
         } catch (\Exception $e) {
             $output->writeln(sprintf('<error>Error creating slot: %s</error>', $e->getMessage()));
         }
     } else {
         $output->writeln('<error>Error: no such ticket id or access denied</error>');
     }
 }
コード例 #7
0
ファイル: Billable.php プロジェクト: nickschuch/tl
 /**
  * {@inheritdoc}
  */
 protected function execute(InputInterface $input, OutputInterface $output)
 {
     $period = $input->getArgument('period');
     $start = $input->getOption('start');
     $project = $input->getOption('project');
     $start = $start ? new \DateTime($start) : NULL;
     if (!in_array($period, [static::MONTH, static::DAY, static::WEEK, static::FORTNIGHT], TRUE)) {
         $output->writeln('<error>Period must be one of day|week|month|fortnight</error>');
         $output->writeln('E.g. <comment>tl billable week</comment>');
         return;
     }
     switch ($period) {
         case static::WEEK:
             $date = DateHelper::startOfWeek($start);
             $end = clone $date;
             $end->modify('+7 days');
             break;
         case static::MONTH:
             $date = DateHelper::startOfMonth($start);
             $end = clone $date;
             $end->modify('+1 month');
             $end = DateHelper::startOfMonth($end);
             $end->modify('-1 second');
             break;
         case static::FORTNIGHT:
             $date = DateHelper::startOfWeek($start);
             $date->modify('-7 days');
             $end = clone $date;
             $end->modify('+14 days');
             break;
         default:
             $date = DateHelper::startOfDay($start);
             $end = clone $date;
             $end->modify('+1 day');
     }
     $billable = 0;
     $non_billable = 0;
     $unknown = 0;
     $unknowns = [];
     $projects = [];
     $billable_projects = [];
     $non_billable_projects = [];
     foreach ($this->repository->totalByTicket($date->getTimestamp(), $end->getTimestamp()) as $tid => $duration) {
         $details = $this->connector->ticketDetails($tid);
         if ($details) {
             if (!isset($projects[$details->getProjectId()])) {
                 $projects[$details->getProjectId()] = 0;
             }
             if ($details->isBillable()) {
                 $billable += $duration;
                 $projects[$details->getProjectId()] += $duration;
                 $billable_projects[$details->getProjectId()] = $details->getProjectId();
             } else {
                 $non_billable += $duration;
                 $projects[$details->getProjectId()] += $duration;
                 $non_billable_projects[$details->getProjectId()] = $details->getProjectId();
             }
         } else {
             $unknown += $duration;
             $unknowns[] = $tid;
         }
     }
     $table = new Table($output);
     if (!$project) {
         $table->setHeaders(['Type', 'Hours', 'Percent']);
     } else {
         $table->setHeaders(['Type', 'Project', 'Hours', 'Percent']);
     }
     $total = $billable + $non_billable + $unknown;
     $tag = 'info';
     // @todo make this configurable.
     if ($billable / $total < 0.8) {
         $tag = 'error';
     }
     if ($project) {
         $project_names = $this->connector->projectNames();
         $rows[] = ['Billable', '', '', ''];
         foreach ($billable_projects as $project_id) {
             $project_name = isset($project_names[$project_id]) ? $project_names[$project_id] : "Project ID {$project_id}";
             $rows[] = ['', $project_name, Formatter::formatDuration($projects[$project_id]), ''];
         }
         $rows[] = new TableSeparator();
         $rows[] = ['Billable', '', Formatter::formatDuration($billable), "<{$tag}>" . round(100 * $billable / $total, 2) . "%</{$tag}>"];
         $rows[] = new TableSeparator();
         $rows[] = ['Non-Billable', '', '', ''];
         foreach ($non_billable_projects as $project_id) {
             $project_name = isset($project_names[$project_id]) ? $project_names[$project_id] : "Project ID {$project_id}";
             $rows[] = ['', $project_name, Formatter::formatDuration($projects[$project_id]), ''];
         }
         $rows[] = new TableSeparator();
         $rows[] = ['Non-billable', '', Formatter::formatDuration($non_billable), round(100 * $non_billable / $total, 2) . '%'];
         if ($unknown) {
             $rows[] = new TableSeparator();
             $rows[] = ['Unknown', '', '', ''];
             $rows[] = ['', 'Unknown<comment>*</comment>', Formatter::formatDuration($unknown), round(100 * $unknown / $total, 2) . '%'];
             $rows[] = ['', '<comment>* Deleted or access denied tickets:</comment> ' . implode(',', $unknowns), '', ''];
         }
         $rows[] = new TableSeparator();
         $rows[] = ['', 'Total', Formatter::formatDuration($total), '100%'];
     } else {
         $rows[] = ['Billable', Formatter::formatDuration($billable), "<{$tag}>" . round(100 * $billable / $total, 2) . "%</{$tag}>"];
         $rows[] = ['Non-billable', Formatter::formatDuration($non_billable), round(100 * $non_billable / $total, 2) . '%'];
         if ($unknown) {
             $rows[] = ['Unknown<comment>*</comment>', Formatter::formatDuration($unknown), round(100 * $unknown / $total, 2) . '%'];
             $rows[] = ['<comment>* Deleted or access denied tickets:</comment> ' . implode(',', $unknowns), '', ''];
         }
         $rows[] = new TableSeparator();
         $rows[] = ['Total', Formatter::formatDuration($total), '100%'];
     }
     $table->setRows($rows);
     $table->render();
 }
コード例 #8
0
ファイル: Combine.php プロジェクト: nickschuch/tl
 protected function stopTicket($slot_id, OutputInterface $output)
 {
     if ($stop = $this->repository->stop($slot_id)) {
         $stopped = $this->connector->ticketDetails($stop->tid);
         $output->writeln(sprintf('Closed slot <comment>%d</comment> against ticket <info>%d</info>: %s, duration <info>%s</info>', $stop->id, $stop->tid, $stopped->getTitle(), Formatter::formatDuration($stop->duration)));
     }
 }
コード例 #9
0
ファイル: Tag.php プロジェクト: nickschuch/tl
 /**
  * Tag single entry.
  *
  * @param \Symfony\Component\Console\Input\InputInterface $input
  * @param \Symfony\Component\Console\Output\OutputInterface $output
  * @param $slot_id
  */
 protected function tagOne(InputInterface $input, OutputInterface $output, $slot_id)
 {
     if ($entry = $this->repository->slot($slot_id)) {
         $helper = $this->getHelper('question');
         try {
             $title = $this->connector->ticketDetails($entry->tid);
             $categories = $this->connector->fetchCategories();
         } catch (ConnectException $e) {
             $output->writeln('<error>You are offline, please try again later.</error>');
             return;
         }
         $question = new ChoiceQuestion(sprintf('Enter tag for slot <comment>%d</comment> [<info>%d</info>]: %s [<info>%s h</info>] [%s]', $entry->id, $entry->tid, $title->getTitle(), Formatter::formatDuration($entry->end - $entry->start), static::DEFAULT_TAG), $categories, static::DEFAULT_TAG);
         $tag_id = $helper->ask($input, $output, $question);
         $tag = $categories[$tag_id];
         list(, $tag) = explode(':', $tag);
         $this->repository->tag($tag, $entry->id);
     } else {
         $output->writeln('<error>No such slot - please check your slot ID</error>');
     }
 }
コード例 #10
0
ファイル: FormatterTest.php プロジェクト: nickschuch/tl
 /**
  * Tests formatter for times.
  *
  * @dataProvider providerFormatDuration
  * @covers ::formatDuration
  */
 public function testFormatDuration($start, $end, $expected)
 {
     $duration = $end - $start;
     $this->assertEquals($expected, Formatter::formatDuration($duration));
 }