Add report command, rename to invoice.

master
Lio Novelli 2022-05-11 19:43:05 +02:00
parent 1e2e6bade4
commit c8e38922c0
9 changed files with 199 additions and 25 deletions

View File

@ -5,7 +5,7 @@
** Usage ** 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 This command would send an invoice for last month created from the template
file and last month report from youtrack as an email attachment. 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 *** most current
1. For version 0.6.7 1. For version 0.6.7
- data value objects - [X] data value objects
- phar file - phar file
- additional expenses - [X] additional expenses
2. For version 1.0 2. For version 1.0
- plugin system - plugin system
- for time tracking services - for time tracking services

View File

@ -3,11 +3,11 @@
use function DI\create; use function DI\create;
use function DI\get; 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\ConfigurationInterface;
use RprtCli\Utils\Configuration\ConfigurationService; use RprtCli\Utils\Configuration\ConfigurationService;
use RprtCli\Utils\CsvReport\CsvReport;
use RprtCli\Utils\CsvReport\CsvReportInterface;
use RprtCli\Utils\CsvReport\ReportCsv; use RprtCli\Utils\CsvReport\ReportCsv;
use RprtCli\Utils\CsvReport\ReportCsvInterface; use RprtCli\Utils\CsvReport\ReportCsvInterface;
use GuzzleHttp\Client; use GuzzleHttp\Client;
@ -60,7 +60,7 @@ return [
get('pdf_export.service') get('pdf_export.service')
), ),
'mailer' => get(MailerInterface::class), 'mailer' => get(MailerInterface::class),
RprtCommand::class => create()->constructor( InvoiceCommand::class => create()->constructor(
get('csv.report'), get('csv.report'),
get('config.service'), get('config.service'),
get('youtrack.service'), get('youtrack.service'),
@ -70,5 +70,10 @@ return [
TrackCommand::class => create()->constructor( TrackCommand::class => create()->constructor(
get('config.service'), get('config.service'),
get('youtrack.service') get('youtrack.service')
),
ReportCommand::class => create()->constructor(
get('config.service'),
get('youtrack.service'),
get('csv.report')
) )
]; ];

View File

@ -2,7 +2,8 @@
<?php <?php
use Symfony\Component\Console\Application; use Symfony\Component\Console\Application;
use RprtCli\Commands\RprtCommand; use RprtCli\Commands\InvoiceCommand;
use RprtCli\Commands\ReportCommand;
use DI\ContainerBuilder; use DI\ContainerBuilder;
require __DIR__ . '/vendor/autoload.php'; require __DIR__ . '/vendor/autoload.php';
@ -13,7 +14,9 @@ $container = $builder->build();
$application = new Application(); $application = new Application();
$rprtCommand = $container->get(RprtCommand::class); $invoiceCommand = $container->get(InvoiceCommand::class);
$application->add($rprtCommand); $application->add($invoiceCommand);
$reportCommand = $container->get(ReportCommand::class);
$application->add($reportCommand);
$application->run(); $application->run();

View File

@ -2,7 +2,7 @@
declare(strict_types=1); declare(strict_types=1);
// src/Commands/RprtCommand.php; // src/Commands/InvoiceCommand.php;
namespace RprtCli\Commands; namespace RprtCli\Commands;
@ -25,9 +25,9 @@ use Symfony\Component\Console\Output\OutputInterface;
use function var_dump; use function var_dump;
/** /**
* Main file - rprt command. * Main file - invoice command.
*/ */
class RprtCommand extends Command class InvoiceCommand extends Command
{ {
protected $csv; protected $csv;
@ -61,8 +61,8 @@ class RprtCommand extends Command
*/ */
protected function configure() : void protected function configure() : void
{ {
$this->setName('rprt'); $this->setName('invoice');
$this->setDescription('Generate monthly report'); $this->setDescription('Generate an invoice from (monthly) report');
// @TODO $this->addUsage(''); // @TODO $this->addUsage('');
// @TODO add sub options (config overrides) // @TODO add sub options (config overrides)
$this->addOption( $this->addOption(
@ -85,7 +85,7 @@ class RprtCommand extends Command
); );
$this->addOption( $this->addOption(
'test', 'test',
't', '',
InputOption::VALUE_NONE, InputOption::VALUE_NONE,
'Test login into youtrack service. Prints out your name.' 'Test login into youtrack service. Prints out your name.'
); );
@ -129,7 +129,7 @@ class RprtCommand extends Command
); );
$this->addOption( $this->addOption(
'report', 'report',
'g', 't',
InputOption::VALUE_OPTIONAL, InputOption::VALUE_OPTIONAL,
'Show time tracked for report.', 'Show time tracked for report.',
FALSE FALSE
@ -146,7 +146,7 @@ class RprtCommand extends Command
$list = $this->youtrack->listReports(); $list = $this->youtrack->listReports();
$output->writeln(var_export($list, TRUE)); $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')) { if ($report = $input->getOption('report')) {
$this->youtrack->setReportId($report); $this->youtrack->setReportId($report);
} }
@ -174,8 +174,8 @@ class RprtCommand extends Command
} }
if ($youtrack || $file = $input->getOption('file')) { if ($youtrack || $file = $input->getOption('file')) {
// Youtrack can also provide a file name. // Youtrack can also provide a file name.
var_dump($file); // var_dump($file);
$data = $this->csv->getReportData($file); $data = $this->csv->getInvoiceData($file);
if (!empty($expenses)) { if (!empty($expenses)) {
$data = array_merge($data, $expenses); $data = array_merge($data, $expenses);
} }

View File

@ -0,0 +1,107 @@
<?php
declare(strict_types=1);
namespace RprtCli\Commands;
use RprtCli\Utils\Configuration\ConfigurationInterface;
use RprtCli\Utils\CsvReport\ReportCsvInterface;
use RprtCli\Utils\TimeTrackingServices\YoutrackInterface;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Helper\Table;
use Symfony\Component\Console\Helper\TableSeparator;
use Symfony\Component\Console\Helper\TableCell;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
class ReportCommand extends Command {
protected $trackingService;
protected $config;
protected $csv;
public function __construct(ConfigurationInterface $configuration, YoutrackInterface $tracking_service, ReportCsvInterface $csv, ?string $name = null) {
$this->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;
}
}

View File

@ -36,7 +36,7 @@ class CsvReport implements CsvReportInterface
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
public function getReportData(string $filePath) : array public function getInvoiceData(string $filePath) : array
{ {
$output = []; $output = [];
// @TODO replace with config service. // @TODO replace with config service.

View File

@ -17,7 +17,7 @@ interface CsvReportInterface
* *
* Project key as key and number of hours as value. * 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. * Data for default drunomics pdf export.

View File

@ -38,7 +38,7 @@ class ReportCsv implements ReportCsvInterface
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
public function getReportData(string $filePath) : array public function getInvoiceData(string $filePath) : array
{ {
$output = []; $output = [];
// @TODO replace with config service. // @TODO replace with config service.
@ -117,6 +117,7 @@ class ReportCsv implements ReportCsvInterface
} }
} }
if ($add_separator) { if ($add_separator) {
// @TODO replace separators with constants for normal separating.
$rows[] = null; $rows[] = null;
$rows[] = ['Gesamt netto', number_format($totalHours, 2, ',', '.'), ' ', number_format($totalPrice, 2, ',', '.')]; $rows[] = ['Gesamt netto', number_format($totalHours, 2, ',', '.'), ' ', number_format($totalPrice, 2, ',', '.')];
$add_separator = FALSE; $add_separator = FALSE;
@ -124,7 +125,7 @@ class ReportCsv implements ReportCsvInterface
foreach ($data as $invoice_element) { foreach ($data as $invoice_element) {
if ($invoice_element instanceof ExpensesInterface) { if ($invoice_element instanceof ExpensesInterface) {
if (!isset($added_expenses)) { if (!isset($added_expenses)) {
// Make next line bold and centered. // @TODO - separator 0: Make next line bold and centered.
$rows[] = 0; $rows[] = 0;
$rows[] = [ $rows[] = [
null, null,
@ -168,6 +169,64 @@ class ReportCsv implements ReportCsvInterface
return $rows; 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. * Should be moved into test class.
*/ */

View File

@ -17,7 +17,7 @@ interface ReportCsvInterface
* *
* Project key as key and number of hours as value. * 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. * Returns array of rows created from array of InvoiceElements.