/** * {@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(); }
/** * {@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>'); }
/** * {@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>'); } }
/** * {@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>'); }
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; }
/** * {@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>'); } }
/** * {@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(); }
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))); } }
/** * 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>'); } }
/** * Tests formatter for times. * * @dataProvider providerFormatDuration * @covers ::formatDuration */ public function testFormatDuration($start, $end, $expected) { $duration = $end - $start; $this->assertEquals($expected, Formatter::formatDuration($duration)); }