diff --git a/README.org b/README.org index 187d29b..bcadf4e 100644 --- a/README.org +++ b/README.org @@ -5,7 +5,7 @@ ** Usage - ~./rprt.php rprt -y -p -s~ + ~./rprt.php invoice -y -p -s~ This command would send an invoice for last month created from the template file and last month report from youtrack as an email attachment. @@ -69,9 +69,9 @@ curl 'https://drunomics.myjetbrains.com/youtrack/api/reports?$top=-1&fields=id,n *** most current 1. For version 0.6.7 - - data value objects + - [X] data value objects - phar file - - additional expenses + - [X] additional expenses 2. For version 1.0 - plugin system - for time tracking services diff --git a/app/dependencies.php b/app/dependencies.php index aa723ec..ff8167a 100644 --- a/app/dependencies.php +++ b/app/dependencies.php @@ -3,11 +3,11 @@ use function DI\create; use function DI\get; -use RprtCli\Commands\RprtCommand; +use RprtCli\Commands\InvoiceCommand; +use RprtCli\Commands\ReportCommand; +use RprtCli\Commands\TrackCommand; use RprtCli\Utils\Configuration\ConfigurationInterface; use RprtCli\Utils\Configuration\ConfigurationService; -use RprtCli\Utils\CsvReport\CsvReport; -use RprtCli\Utils\CsvReport\CsvReportInterface; use RprtCli\Utils\CsvReport\ReportCsv; use RprtCli\Utils\CsvReport\ReportCsvInterface; use GuzzleHttp\Client; @@ -60,7 +60,7 @@ return [ get('pdf_export.service') ), 'mailer' => get(MailerInterface::class), - RprtCommand::class => create()->constructor( + InvoiceCommand::class => create()->constructor( get('csv.report'), get('config.service'), get('youtrack.service'), @@ -70,5 +70,10 @@ return [ TrackCommand::class => create()->constructor( get('config.service'), get('youtrack.service') + ), + ReportCommand::class => create()->constructor( + get('config.service'), + get('youtrack.service'), + get('csv.report') ) ]; diff --git a/app/rprt.php b/app/rprt.php index 3c5ffc8..27282c6 100755 --- a/app/rprt.php +++ b/app/rprt.php @@ -2,7 +2,8 @@ build(); $application = new Application(); -$rprtCommand = $container->get(RprtCommand::class); -$application->add($rprtCommand); +$invoiceCommand = $container->get(InvoiceCommand::class); +$application->add($invoiceCommand); +$reportCommand = $container->get(ReportCommand::class); +$application->add($reportCommand); $application->run(); diff --git a/app/src/Commands/RprtCommand.php b/app/src/Commands/InvoiceCommand.php similarity index 97% rename from app/src/Commands/RprtCommand.php rename to app/src/Commands/InvoiceCommand.php index 5fda1b6..35a46df 100644 --- a/app/src/Commands/RprtCommand.php +++ b/app/src/Commands/InvoiceCommand.php @@ -2,7 +2,7 @@ declare(strict_types=1); -// src/Commands/RprtCommand.php; +// src/Commands/InvoiceCommand.php; namespace RprtCli\Commands; @@ -25,9 +25,9 @@ use Symfony\Component\Console\Output\OutputInterface; use function var_dump; /** - * Main file - rprt command. + * Main file - invoice command. */ -class RprtCommand extends Command +class InvoiceCommand extends Command { protected $csv; @@ -61,8 +61,8 @@ class RprtCommand extends Command */ protected function configure() : void { - $this->setName('rprt'); - $this->setDescription('Generate monthly report'); + $this->setName('invoice'); + $this->setDescription('Generate an invoice from (monthly) report'); // @TODO $this->addUsage(''); // @TODO add sub options (config overrides) $this->addOption( @@ -85,7 +85,7 @@ class RprtCommand extends Command ); $this->addOption( 'test', - 't', + '', InputOption::VALUE_NONE, 'Test login into youtrack service. Prints out your name.' ); @@ -129,7 +129,7 @@ class RprtCommand extends Command ); $this->addOption( 'report', - 'g', + 't', InputOption::VALUE_OPTIONAL, 'Show time tracked for report.', FALSE @@ -146,7 +146,7 @@ class RprtCommand extends Command $list = $this->youtrack->listReports(); $output->writeln(var_export($list, TRUE)); } - if ($input->hasParameterOption('--report') || $input->hasParameterOption('-g')) { + if ($input->hasParameterOption('--report') || $input->hasParameterOption('-t')) { if ($report = $input->getOption('report')) { $this->youtrack->setReportId($report); } @@ -174,8 +174,8 @@ class RprtCommand extends Command } if ($youtrack || $file = $input->getOption('file')) { // Youtrack can also provide a file name. - var_dump($file); - $data = $this->csv->getReportData($file); + // var_dump($file); + $data = $this->csv->getInvoiceData($file); if (!empty($expenses)) { $data = array_merge($data, $expenses); } diff --git a/app/src/Commands/ReportCommand.php b/app/src/Commands/ReportCommand.php new file mode 100644 index 0000000..f623129 --- /dev/null +++ b/app/src/Commands/ReportCommand.php @@ -0,0 +1,107 @@ +config = $configuration; + // @TODO generalize tracking service. + $this->trackingService = $tracking_service; + $this->csv = $csv; + parent::__construct($name); + } + + protected function configure() :void { + $this->setName('report'); + $this->setDescription('Get a time-tracking report into command line.'); + $this->addOption( + 'report', + 'r', + InputOption::VALUE_OPTIONAL, + 'Select a report from list of your reports' + ); + $this->addOption( + 'time-range', + 't', + InputOption::VALUE_REQUIRED, + 'Calculates report from tracking service work items directly for time range' + ); + } + + protected function execute(InputInterface $input, OutputInterface $output) :int { + // Could just parse a csv file or actually get workItems from youtrack ... + if ($input->hasParameterOption('--report') || $input->hasParameterOption('-r')) { + if ($report = $input->getOption('report')) { + $this->trackingService->setReportId($report); + } else { + $reports = $this->trackingService->listReports(); + $count = 1; + foreach ($reports as $id => $name) { + $output->writeln("[{$count}] {$name} ({$id})"); + $count++; + } + $output->writeln("[{$count}] None (null)"); + $report = readline('Select id of the report: '); + // Asume people are literate. + if (!in_array($report, array_keys($reports) )) { + $output->writeln('Non-existing report. Exiting.'); + return Command::SUCCESS; + } + $this->trackingService->setReportId($report); + } + } + + // Currently we only support csv download. + $report_id = $this->trackingService->getReportId(); + $file = $this->trackingService->downloadReport($report_id); + // var_dump($file); + $data = $this->csv->generateReportTable($file); + $table = $this->buildTable($output, $data); + $table->render(); + + return Command::SUCCESS; + } + + /** + * Builds table from the report csv data. + * + * @TODO: Code duplication with InvoiceCommand::getTable. + */ + protected function buildTable(OutputInterface $output, array $rows): Table { + $table = new Table($output); + $table->setHeaders([ + 'Ticket Id', 'Name', 'Time', 'Estimation', + ]); + foreach ($rows as $key => $row) { + if (!$row) { + $rows[$key] = new TableSeparator(); + } elseif (is_array($row) && is_null($row[0]) && is_null($row[2])) { + // Check which elements in array are null. + $rows[$key] = [new TableCell($row[1], ['colspan' => 2]), new TableCell((string) $row[3], ['colspan' => 2])]; + } + } + $table->setRows($rows); + return $table; + } + +} diff --git a/app/src/Utils/CsvReport/CsvReport.php b/app/src/Utils/CsvReport/CsvReport.php index 775942f..ae6d388 100644 --- a/app/src/Utils/CsvReport/CsvReport.php +++ b/app/src/Utils/CsvReport/CsvReport.php @@ -36,7 +36,7 @@ class CsvReport implements CsvReportInterface /** * {@inheritdoc} */ - public function getReportData(string $filePath) : array + public function getInvoiceData(string $filePath) : array { $output = []; // @TODO replace with config service. diff --git a/app/src/Utils/CsvReport/CsvReportInterface.php b/app/src/Utils/CsvReport/CsvReportInterface.php index 3a6af84..ce33ba4 100644 --- a/app/src/Utils/CsvReport/CsvReportInterface.php +++ b/app/src/Utils/CsvReport/CsvReportInterface.php @@ -17,7 +17,7 @@ interface CsvReportInterface * * Project key as key and number of hours as value. */ - public function getReportData(string $filePath) : array; + public function getInvoiceData(string $filePath) : array; /** * Data for default drunomics pdf export. diff --git a/app/src/Utils/CsvReport/ReportCsv.php b/app/src/Utils/CsvReport/ReportCsv.php index 519f4a1..77f3e6a 100644 --- a/app/src/Utils/CsvReport/ReportCsv.php +++ b/app/src/Utils/CsvReport/ReportCsv.php @@ -38,7 +38,7 @@ class ReportCsv implements ReportCsvInterface /** * {@inheritdoc} */ - public function getReportData(string $filePath) : array + public function getInvoiceData(string $filePath) : array { $output = []; // @TODO replace with config service. @@ -117,6 +117,7 @@ class ReportCsv implements ReportCsvInterface } } if ($add_separator) { + // @TODO replace separators with constants for normal separating. $rows[] = null; $rows[] = ['Gesamt netto', number_format($totalHours, 2, ',', '.'), ' ', number_format($totalPrice, 2, ',', '.')]; $add_separator = FALSE; @@ -124,7 +125,7 @@ class ReportCsv implements ReportCsvInterface foreach ($data as $invoice_element) { if ($invoice_element instanceof ExpensesInterface) { if (!isset($added_expenses)) { - // Make next line bold and centered. + // @TODO - separator 0: Make next line bold and centered. $rows[] = 0; $rows[] = [ null, @@ -168,6 +169,64 @@ class ReportCsv implements ReportCsvInterface return $rows; } + public function generateReportTable(string $filePath) { + // ticket-id, ticket-name, time-spent + $data = $this->parseReportData($filePath); + [$previous, $time_sum, $project_time, $table, $all_projects] = [$data[0]['id'], 0, 0, [], []]; + foreach ($data as $line) { + $project = explode('-', $line['id'])[0]; + $previous_project = explode('-', $previous)[0]; + $project_time += (float) $line['time']; + $table[] = array_values($line); + if ($project !== $previous_project) { + // When project changes, add a sum of time for that project. + $table[] = null; + $table[] = [null, $previous_project, null, $project_time/60]; + $table[] = null; + $time_sum += (float) $project_time; + $project_time = 0; + $all_projects[] = $project; + } + $previous = $line['id']; + } + // Add sum for the last project. + $table[] = null; + $table[] = [null, $project, null, $project_time / 60]; + $time_sum += (float) $project_time; + $all_projects[] = $project; + // Add a sum of time for whole day. + $table[] = null; + $table[] = [null, implode(', ', $all_projects), null, $time_sum/60]; + return $table; + } + + /** + * {@inheritdoc} + */ + protected function parseReportData(string $filePath): array + { + $output = []; + // @TODO replace with config service. + // $config = $this->dummyConfig()['projects']; + if ($file = fopen($filePath, 'r')) { + while (($line = fgetcsv($file)) !== false) { + if (!is_numeric($line[4])) { + // Skip header at least. + continue; + } + // @TODO validate line + $output[] = [ + 'id' => $line[1], + 'name' => substr($line[2], 0, 80), + 'time' => $line[4], + 'estimation' => $line[3], + ]; + } + } + return $output; + } + + /** * Should be moved into test class. */ diff --git a/app/src/Utils/CsvReport/ReportCsvInterface.php b/app/src/Utils/CsvReport/ReportCsvInterface.php index 4983d26..75adc85 100644 --- a/app/src/Utils/CsvReport/ReportCsvInterface.php +++ b/app/src/Utils/CsvReport/ReportCsvInterface.php @@ -17,7 +17,7 @@ interface ReportCsvInterface * * Project key as key and number of hours as value. */ - public function getReportData(string $filePath): array; + public function getInvoiceData(string $filePath): array; /** * Returns array of rows created from array of InvoiceElements.