Install code sniffer and run code beautifier.
parent
cfc623ba72
commit
4dcbcb228a
|
@ -28,12 +28,18 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"squizlabs/php_codesniffer": "^3.5",
|
"squizlabs/php_codesniffer": "^3.7",
|
||||||
"phpunit/phpunit": "^9.5",
|
"phpunit/phpunit": "^9.5",
|
||||||
"opsway/psr12-strict-coding-standard": "^0.5.0"
|
"opsway/psr12-strict-coding-standard": "^0.5.0",
|
||||||
|
"phpcompatibility/php-compatibility": "^9.3"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"cs": "phpcs",
|
"cs": "phpcs --colors --standard=PSR12",
|
||||||
"cbf": "phpcbf"
|
"cbf": "phpcbf"
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"allow-plugins": {
|
||||||
|
"dealerdirect/phpcodesniffer-composer-installer": true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||||
"This file is @generated automatically"
|
"This file is @generated automatically"
|
||||||
],
|
],
|
||||||
"content-hash": "81050635de2f87c3f7f693ec8cb30645",
|
"content-hash": "7b0be09c8f282dfcceb075d1455caa28",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
"name": "doctrine/lexer",
|
"name": "doctrine/lexer",
|
||||||
|
@ -2949,6 +2949,68 @@
|
||||||
},
|
},
|
||||||
"time": "2022-02-21T01:04:05+00:00"
|
"time": "2022-02-21T01:04:05+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "phpcompatibility/php-compatibility",
|
||||||
|
"version": "9.3.5",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/PHPCompatibility/PHPCompatibility.git",
|
||||||
|
"reference": "9fb324479acf6f39452e0655d2429cc0d3914243"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/PHPCompatibility/PHPCompatibility/zipball/9fb324479acf6f39452e0655d2429cc0d3914243",
|
||||||
|
"reference": "9fb324479acf6f39452e0655d2429cc0d3914243",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": ">=5.3",
|
||||||
|
"squizlabs/php_codesniffer": "^2.3 || ^3.0.2"
|
||||||
|
},
|
||||||
|
"conflict": {
|
||||||
|
"squizlabs/php_codesniffer": "2.6.2"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"phpunit/phpunit": "~4.5 || ^5.0 || ^6.0 || ^7.0"
|
||||||
|
},
|
||||||
|
"suggest": {
|
||||||
|
"dealerdirect/phpcodesniffer-composer-installer": "^0.5 || This Composer plugin will sort out the PHPCS 'installed_paths' automatically.",
|
||||||
|
"roave/security-advisories": "dev-master || Helps prevent installing dependencies with known security issues."
|
||||||
|
},
|
||||||
|
"type": "phpcodesniffer-standard",
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"LGPL-3.0-or-later"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Wim Godden",
|
||||||
|
"homepage": "https://github.com/wimg",
|
||||||
|
"role": "lead"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Juliette Reinders Folmer",
|
||||||
|
"homepage": "https://github.com/jrfnl",
|
||||||
|
"role": "lead"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Contributors",
|
||||||
|
"homepage": "https://github.com/PHPCompatibility/PHPCompatibility/graphs/contributors"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "A set of sniffs for PHP_CodeSniffer that checks for PHP cross-version compatibility.",
|
||||||
|
"homepage": "http://techblog.wimgodden.be/tag/codesniffer/",
|
||||||
|
"keywords": [
|
||||||
|
"compatibility",
|
||||||
|
"phpcs",
|
||||||
|
"standards"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/PHPCompatibility/PHPCompatibility/issues",
|
||||||
|
"source": "https://github.com/PHPCompatibility/PHPCompatibility"
|
||||||
|
},
|
||||||
|
"time": "2019-12-27T09:44:58+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "phpdocumentor/reflection-common",
|
"name": "phpdocumentor/reflection-common",
|
||||||
"version": "2.2.0",
|
"version": "2.2.0",
|
||||||
|
@ -4659,16 +4721,16 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "squizlabs/php_codesniffer",
|
"name": "squizlabs/php_codesniffer",
|
||||||
"version": "3.5.8",
|
"version": "3.7.1",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/squizlabs/PHP_CodeSniffer.git",
|
"url": "https://github.com/squizlabs/PHP_CodeSniffer.git",
|
||||||
"reference": "9d583721a7157ee997f235f327de038e7ea6dac4"
|
"reference": "1359e176e9307e906dc3d890bcc9603ff6d90619"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/9d583721a7157ee997f235f327de038e7ea6dac4",
|
"url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/1359e176e9307e906dc3d890bcc9603ff6d90619",
|
||||||
"reference": "9d583721a7157ee997f235f327de038e7ea6dac4",
|
"reference": "1359e176e9307e906dc3d890bcc9603ff6d90619",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
|
@ -4706,7 +4768,12 @@
|
||||||
"phpcs",
|
"phpcs",
|
||||||
"standards"
|
"standards"
|
||||||
],
|
],
|
||||||
"time": "2020-10-23T02:01:07+00:00"
|
"support": {
|
||||||
|
"issues": "https://github.com/squizlabs/PHP_CodeSniffer/issues",
|
||||||
|
"source": "https://github.com/squizlabs/PHP_CodeSniffer",
|
||||||
|
"wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki"
|
||||||
|
},
|
||||||
|
"time": "2022-06-18T07:21:10+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "theseer/tokenizer",
|
"name": "theseer/tokenizer",
|
||||||
|
@ -4869,5 +4936,5 @@
|
||||||
"prefer-lowest": false,
|
"prefer-lowest": false,
|
||||||
"platform": [],
|
"platform": [],
|
||||||
"platform-dev": [],
|
"platform-dev": [],
|
||||||
"plugin-api-version": "2.1.0"
|
"plugin-api-version": "2.3.0"
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ declare(strict_types=1);
|
||||||
|
|
||||||
namespace RprtCli\Commands;
|
namespace RprtCli\Commands;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
use RprtCli\Utils\Configuration\ConfigurationInterface;
|
use RprtCli\Utils\Configuration\ConfigurationInterface;
|
||||||
use RprtCli\Utils\CsvReport\ReportCsvInterface;
|
use RprtCli\Utils\CsvReport\ReportCsvInterface;
|
||||||
use RprtCli\Utils\Mailer\MailerInterface;
|
use RprtCli\Utils\Mailer\MailerInterface;
|
||||||
|
@ -15,14 +16,24 @@ use RprtCli\ValueObjects\Expenses;
|
||||||
use RprtCli\ValueObjects\WorkInvoiceElement;
|
use RprtCli\ValueObjects\WorkInvoiceElement;
|
||||||
use Symfony\Component\Console\Command\Command;
|
use Symfony\Component\Console\Command\Command;
|
||||||
use Symfony\Component\Console\Helper\Table;
|
use Symfony\Component\Console\Helper\Table;
|
||||||
use Symfony\Component\Console\Helper\TableSeparator;
|
|
||||||
use Symfony\Component\Console\Helper\TableCell;
|
use Symfony\Component\Console\Helper\TableCell;
|
||||||
|
use Symfony\Component\Console\Helper\TableSeparator;
|
||||||
use Symfony\Component\Console\Input\InputInterface;
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
use Symfony\Component\Console\Input\InputOption;
|
use Symfony\Component\Console\Input\InputOption;
|
||||||
use Symfony\Component\Console\Output\OutputInterface;
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
// use Symfony\Contracts\Translation\TranslatorInterface;
|
|
||||||
|
|
||||||
|
use function array_merge;
|
||||||
|
use function explode;
|
||||||
|
use function is_array;
|
||||||
|
use function is_null;
|
||||||
|
use function is_string;
|
||||||
|
use function readline;
|
||||||
|
use function strpos;
|
||||||
|
use function strtolower;
|
||||||
use function var_dump;
|
use function var_dump;
|
||||||
|
use function var_export;
|
||||||
|
|
||||||
|
// use Symfony\Contracts\Translation\TranslatorInterface;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Main file - invoice command.
|
* Main file - invoice command.
|
||||||
|
@ -37,7 +48,7 @@ class InvoiceCommand extends Command
|
||||||
|
|
||||||
protected $pdfExport;
|
protected $pdfExport;
|
||||||
|
|
||||||
const TYPE_WORK = 1;
|
const TYPE_WORK = 1;
|
||||||
const TYPE_EXPENSE = 2;
|
const TYPE_EXPENSE = 2;
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
|
@ -51,8 +62,8 @@ class InvoiceCommand extends Command
|
||||||
$this->csv = $csv;
|
$this->csv = $csv;
|
||||||
$this->configuration = $configuration;
|
$this->configuration = $configuration;
|
||||||
$this->youtrack = $youtrack;
|
$this->youtrack = $youtrack;
|
||||||
$this->pdfExport = $pdf_export;
|
$this->pdfExport = $pdf_export;
|
||||||
$this->mailer = $mailer;
|
$this->mailer = $mailer;
|
||||||
parent::__construct($name);
|
parent::__construct($name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -112,14 +123,14 @@ class InvoiceCommand extends Command
|
||||||
'e',
|
'e',
|
||||||
InputOption::VALUE_OPTIONAL,
|
InputOption::VALUE_OPTIONAL,
|
||||||
'List of additional expenses in format expense1=value1;expenses2=value2... or empty for interactive output.',
|
'List of additional expenses in format expense1=value1;expenses2=value2... or empty for interactive output.',
|
||||||
FALSE
|
false
|
||||||
);
|
);
|
||||||
$this->addOption(
|
$this->addOption(
|
||||||
'custom',
|
'custom',
|
||||||
'c',
|
'c',
|
||||||
InputOption::VALUE_OPTIONAL,
|
InputOption::VALUE_OPTIONAL,
|
||||||
'Additional custom work untracked in format: name1=time1;name2=time2... Project to assign work items to has to be configured in app config. Leave empty for interactive output.',
|
'Additional custom work untracked in format: name1=time1;name2=time2... Project to assign work items to has to be configured in app config. Leave empty for interactive output.',
|
||||||
FALSE
|
false
|
||||||
);
|
);
|
||||||
$this->addOption(
|
$this->addOption(
|
||||||
'list-reports',
|
'list-reports',
|
||||||
|
@ -132,7 +143,7 @@ class InvoiceCommand extends Command
|
||||||
'r',
|
'r',
|
||||||
InputOption::VALUE_OPTIONAL,
|
InputOption::VALUE_OPTIONAL,
|
||||||
'Show time tracked for report.',
|
'Show time tracked for report.',
|
||||||
FALSE
|
false
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -144,16 +155,15 @@ class InvoiceCommand extends Command
|
||||||
}
|
}
|
||||||
if ($input->getOption('list-reports')) {
|
if ($input->getOption('list-reports')) {
|
||||||
$list = $this->youtrack->listReports();
|
$list = $this->youtrack->listReports();
|
||||||
$output->writeln(var_export($list, TRUE));
|
$output->writeln(var_export($list, true));
|
||||||
return Command::SUCCESS;
|
return Command::SUCCESS;
|
||||||
}
|
}
|
||||||
if ($input->hasParameterOption('--report') || $input->hasParameterOption('-r')) {
|
if ($input->hasParameterOption('--report') || $input->hasParameterOption('-r')) {
|
||||||
if ($report = $input->getOption('report')) {
|
if ($report = $input->getOption('report')) {
|
||||||
$this->youtrack->setReportId($report);
|
$this->youtrack->setReportId($report);
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
$reports = $this->youtrack->listReports();
|
$reports = $this->youtrack->listReports();
|
||||||
$count = 1;
|
$count = 1;
|
||||||
foreach ($reports as $id => $name) {
|
foreach ($reports as $id => $name) {
|
||||||
$output->writeln("[{$count}] {$name} ({$id})");
|
$output->writeln("[{$count}] {$name} ({$id})");
|
||||||
$count++;
|
$count++;
|
||||||
|
@ -167,7 +177,7 @@ class InvoiceCommand extends Command
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ($youtrack = $input->getOption('youtrack')) {
|
if ($youtrack = $input->getOption('youtrack')) {
|
||||||
$report_id = $this->youtrack->getReportId();
|
$report_id = $this->youtrack->getReportId();
|
||||||
$cache_clear_status = $this->youtrack->clearReportCache($report_id);
|
$cache_clear_status = $this->youtrack->clearReportCache($report_id);
|
||||||
if ($output->isVerbose()) {
|
if ($output->isVerbose()) {
|
||||||
$output->writeln("Report <info>{$report_id}</info> cache cleared, status: {$cache_clear_status}");
|
$output->writeln("Report <info>{$report_id}</info> cache cleared, status: {$cache_clear_status}");
|
||||||
|
@ -185,8 +195,8 @@ class InvoiceCommand extends Command
|
||||||
if ($output->isVerbose()) {
|
if ($output->isVerbose()) {
|
||||||
$output->writeln("Csv file downloaded to: <info>{$file}</info>");
|
$output->writeln("Csv file downloaded to: <info>{$file}</info>");
|
||||||
}
|
}
|
||||||
$data = $this->csv->getInvoiceData($file);
|
$data = $this->csv->getInvoiceData($file);
|
||||||
if (!empty($expenses)) {
|
if (! empty($expenses)) {
|
||||||
$data = array_merge($data, $expenses);
|
$data = array_merge($data, $expenses);
|
||||||
}
|
}
|
||||||
// $table = $this->generateTable($output, $data);
|
// $table = $this->generateTable($output, $data);
|
||||||
|
@ -219,17 +229,20 @@ class InvoiceCommand extends Command
|
||||||
return Command::SUCCESS;
|
return Command::SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function getTable(OutputInterface $output, array $data) :Table {
|
protected function getTable(OutputInterface $output, array $data) : Table
|
||||||
$rows = $this->csv->generateTable($data);
|
{
|
||||||
|
$rows = $this->csv->generateTable($data);
|
||||||
$table = new Table($output);
|
$table = new Table($output);
|
||||||
$table->setHeaders([
|
$table->setHeaders([
|
||||||
'Project', 'Hours', 'Rate', 'Price',
|
'Project',
|
||||||
|
'Hours',
|
||||||
|
'Rate',
|
||||||
|
'Price',
|
||||||
]);
|
]);
|
||||||
foreach ($rows as $key => $row) {
|
foreach ($rows as $key => $row) {
|
||||||
if (!$row) {
|
if (! $row) {
|
||||||
$rows[$key] = new TableSeparator();
|
$rows[$key] = new TableSeparator();
|
||||||
}
|
} elseif (is_array($row) && is_null($row[1]) && is_null($row[0])) {
|
||||||
elseif (is_array($row) && is_null($row[1]) && is_null($row[0])) {
|
|
||||||
// Check which elements in array are null.
|
// Check which elements in array are null.
|
||||||
$rows[$key] = [new TableCell($row[2], ['colspan' => 3]), $row[3]];
|
$rows[$key] = [new TableCell($row[2], ['colspan' => 3]), $row[3]];
|
||||||
}
|
}
|
||||||
|
@ -248,7 +261,10 @@ class InvoiceCommand extends Command
|
||||||
{
|
{
|
||||||
$table = new Table($output);
|
$table = new Table($output);
|
||||||
$table->setHeaders([
|
$table->setHeaders([
|
||||||
'Project', 'Hours', 'Rate', 'Price',
|
'Project',
|
||||||
|
'Hours',
|
||||||
|
'Rate',
|
||||||
|
'Price',
|
||||||
]);
|
]);
|
||||||
[$rows, $totalHours, $totalPrice] = [[], 0, 0];
|
[$rows, $totalHours, $totalPrice] = [[], 0, 0];
|
||||||
$projectsConfig = $this->configuration->get('projects');
|
$projectsConfig = $this->configuration->get('projects');
|
||||||
|
@ -274,9 +290,9 @@ class InvoiceCommand extends Command
|
||||||
$totalPrice += $price;
|
$totalPrice += $price;
|
||||||
unset($data[$name]);
|
unset($data[$name]);
|
||||||
}
|
}
|
||||||
if (!empty($data)) {
|
if (! empty($data)) {
|
||||||
foreach ($data as $name => $value) {
|
foreach ($data as $name => $value) {
|
||||||
if (strpos(strtolower($name), 'expanses') !== FALSE) {
|
if (strpos(strtolower($name), 'expanses') !== false) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -314,65 +330,66 @@ class InvoiceCommand extends Command
|
||||||
*
|
*
|
||||||
* @return Expenses[]
|
* @return Expenses[]
|
||||||
*/
|
*/
|
||||||
protected function getExpenses($expenses) {
|
protected function getExpenses($expenses)
|
||||||
|
{
|
||||||
$output = [];
|
$output = [];
|
||||||
if (is_string($expenses)) {
|
if (is_string($expenses)) {
|
||||||
foreach (explode(';', $expenses) as $expense) {
|
foreach (explode(';', $expenses) as $expense) {
|
||||||
[$name, $value] = explode('=', $expense);
|
[$name, $value] = explode('=', $expense);
|
||||||
$output[] = new Expenses($name, (float) $value);
|
$output[] = new Expenses($name, (float) $value);
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
else {
|
$continue = true;
|
||||||
$continue = TRUE;
|
|
||||||
while ($continue) {
|
while ($continue) {
|
||||||
$name = readline('Enter expenses name or leave empty to stop: ');
|
$name = readline('Enter expenses name or leave empty to stop: ');
|
||||||
$value = (float) readline('Enter expenses value: ');
|
$value = (float) readline('Enter expenses value: ');
|
||||||
if (!empty($name)) {
|
if (! empty($name)) {
|
||||||
$output[] = new Expenses($name, $value);
|
$output[] = new Expenses($name, $value);
|
||||||
}
|
} else {
|
||||||
else {
|
$continue = false;
|
||||||
$continue = FALSE;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return $output;
|
return $output;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function getCustomWorkOrExpenses($custom, $type) {
|
protected function getCustomWorkOrExpenses($custom, $type)
|
||||||
|
{
|
||||||
$output = [];
|
$output = [];
|
||||||
if (is_string($custom)) {
|
if (is_string($custom)) {
|
||||||
foreach (explode(';', $custom) as $item) {
|
foreach (explode(';', $custom) as $item) {
|
||||||
[$name, $value] = explode('=', $item);
|
[$name, $value] = explode('=', $item);
|
||||||
$output[] = $this->createInvoiceElement($name, (float) $value, $type);
|
$output[] = $this->createInvoiceElement($name, (float) $value, $type);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
$continue = TRUE;
|
$continue = true;
|
||||||
if ($type == self::TYPE_WORK) {
|
if ($type === self::TYPE_WORK) {
|
||||||
$message_name = 'Enter project name or leave empty to stop: ';
|
$message_name = 'Enter project name or leave empty to stop: ';
|
||||||
$message_value = 'Enter time spent of project: ';
|
$message_value = 'Enter time spent of project: ';
|
||||||
} elseif ($type == self::TYPE_EXPENSE) {
|
} elseif ($type === self::TYPE_EXPENSE) {
|
||||||
$message_name = 'Enter expenses name or leave empty to stop: ';
|
$message_name = 'Enter expenses name or leave empty to stop: ';
|
||||||
$message_value = 'Enter expenses value: ';
|
$message_value = 'Enter expenses value: ';
|
||||||
}
|
}
|
||||||
while ($continue) {
|
while ($continue) {
|
||||||
$name = readline($message_name);
|
$name = readline($message_name);
|
||||||
$value = (float) readline($message_value);
|
$value = (float) readline($message_value);
|
||||||
if (!empty($name)) {
|
if (! empty($name)) {
|
||||||
$output[] = $this->createInvoiceElement($name, $value, $type);
|
$output[] = $this->createInvoiceElement($name, $value, $type);
|
||||||
} else {
|
} else {
|
||||||
$continue = FALSE;
|
$continue = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return $output;
|
return $output;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function createInvoiceElement(string $name, float $value, int $type) {
|
protected function createInvoiceElement(string $name, float $value, int $type)
|
||||||
if ($type == self::TYPE_WORK) {
|
{
|
||||||
|
if ($type === self::TYPE_WORK) {
|
||||||
return new WorkInvoiceElement($name, (float) $value);
|
return new WorkInvoiceElement($name, (float) $value);
|
||||||
} elseif ($type == self::TYPE_EXPENSE) {
|
} elseif ($type === self::TYPE_EXPENSE) {
|
||||||
return new Expenses($name, (float) $value);
|
return new Expenses($name, (float) $value);
|
||||||
}
|
}
|
||||||
throw new \Exception('Unkown invoice element type.');
|
throw new Exception('Unkown invoice element type.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,29 +9,36 @@ use RprtCli\Utils\CsvReport\ReportCsvInterface;
|
||||||
use RprtCli\Utils\TimeTrackingServices\YoutrackInterface;
|
use RprtCli\Utils\TimeTrackingServices\YoutrackInterface;
|
||||||
use Symfony\Component\Console\Command\Command;
|
use Symfony\Component\Console\Command\Command;
|
||||||
use Symfony\Component\Console\Helper\Table;
|
use Symfony\Component\Console\Helper\Table;
|
||||||
use Symfony\Component\Console\Helper\TableSeparator;
|
|
||||||
use Symfony\Component\Console\Helper\TableCell;
|
use Symfony\Component\Console\Helper\TableCell;
|
||||||
|
use Symfony\Component\Console\Helper\TableSeparator;
|
||||||
use Symfony\Component\Console\Input\InputInterface;
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
use Symfony\Component\Console\Input\InputOption;
|
use Symfony\Component\Console\Input\InputOption;
|
||||||
use Symfony\Component\Console\Output\OutputInterface;
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
use Symfony\Component\Console\Question\ChoiceQuestion;
|
||||||
|
|
||||||
class ReportCommand extends Command {
|
use function array_flip;
|
||||||
|
use function is_array;
|
||||||
|
use function is_null;
|
||||||
|
|
||||||
|
class ReportCommand extends Command
|
||||||
|
{
|
||||||
protected $trackingService;
|
protected $trackingService;
|
||||||
|
|
||||||
protected $config;
|
protected $config;
|
||||||
|
|
||||||
protected $csv;
|
protected $csv;
|
||||||
|
|
||||||
public function __construct(ConfigurationInterface $configuration, YoutrackInterface $tracking_service, ReportCsvInterface $csv, ?string $name = null) {
|
public function __construct(ConfigurationInterface $configuration, YoutrackInterface $tracking_service, ReportCsvInterface $csv, ?string $name = null)
|
||||||
|
{
|
||||||
$this->config = $configuration;
|
$this->config = $configuration;
|
||||||
// @TODO generalize tracking service.
|
// @TODO generalize tracking service.
|
||||||
$this->trackingService = $tracking_service;
|
$this->trackingService = $tracking_service;
|
||||||
$this->csv = $csv;
|
$this->csv = $csv;
|
||||||
parent::__construct($name);
|
parent::__construct($name);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function configure() :void {
|
protected function configure() : void
|
||||||
|
{
|
||||||
$this->setName('report');
|
$this->setName('report');
|
||||||
$this->setDescription('Get a time-tracking report into command line.');
|
$this->setDescription('Get a time-tracking report into command line.');
|
||||||
$this->addOption(
|
$this->addOption(
|
||||||
|
@ -48,7 +55,8 @@ class ReportCommand extends Command {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function execute(InputInterface $input, OutputInterface $output) :int {
|
protected function execute(InputInterface $input, OutputInterface $output) : int
|
||||||
|
{
|
||||||
if ($timeRange = $input->getOption('time-range')) {
|
if ($timeRange = $input->getOption('time-range')) {
|
||||||
// @TODO: Implement time range option:
|
// @TODO: Implement time range option:
|
||||||
// - Request workTime items from tracking service
|
// - Request workTime items from tracking service
|
||||||
|
@ -62,29 +70,31 @@ class ReportCommand extends Command {
|
||||||
$reports = $this->trackingService->listReports();
|
$reports = $this->trackingService->listReports();
|
||||||
// Could just parse a csv file or actually get workItems from youtrack ...
|
// Could just parse a csv file or actually get workItems from youtrack ...
|
||||||
if ($input->hasParameterOption('--report') || $input->hasParameterOption('-r')) {
|
if ($input->hasParameterOption('--report') || $input->hasParameterOption('-r')) {
|
||||||
if ($report = $input->getOption('report')) {
|
if (! $report = $input->getOption('report')) {
|
||||||
$this->trackingService->setReportId($report);
|
// @TODO Make selection nicer.
|
||||||
} else {
|
// QuestionHelper: https://symfony.com/doc/current/components/console/helpers/questionhelper.html
|
||||||
$count = 1;
|
$helper = $this->getHelper('question');
|
||||||
foreach ($reports as $id => $name) {
|
$question = new ChoiceQuestion('Select report:', $reports);
|
||||||
$output->writeln("[{$count}] {$name} ({$id})");
|
$question
|
||||||
$count++;
|
->setErrorMessage('Report %s does not exist!')
|
||||||
}
|
->setAutocompleterValues($reports);
|
||||||
$output->writeln("[{$count}] None (null)");
|
$report = $helper->ask($input, $output, $question);
|
||||||
$report = readline('Select id of the report: ');
|
$output->writeln('Report ' . $report . ' selected.');
|
||||||
// Asume people are literate.
|
}
|
||||||
if (!in_array($report, array_keys($reports) )) {
|
// If parameter option is not recognised check if report name was given.
|
||||||
$output->writeln('Non-existing report. Exiting.');
|
if (! isset($reports[$report])) {
|
||||||
|
if (! isset(array_flip($reports)[$report])) {
|
||||||
|
$output->writeln('Non-existing report ' . $report . '. Exiting.');
|
||||||
return Command::SUCCESS;
|
return Command::SUCCESS;
|
||||||
}
|
}
|
||||||
$this->trackingService->setReportId($report);
|
$report = array_flip($reports)[$report];
|
||||||
}
|
}
|
||||||
}
|
$this->trackingService->setReportId($report);
|
||||||
elseif ($report = $this->config->get('tracking_service.youtrack.report.default')) {
|
} elseif ($report = $this->config->get('tracking_service.youtrack.report.default')) {
|
||||||
$this->trackingService->setReportId($report);
|
$this->trackingService->setReportId($report);
|
||||||
}
|
}
|
||||||
// Currently we only support csv download.
|
// Currently we only support csv download.
|
||||||
$report_id = $this->trackingService->getReportId();
|
$report_id = $this->trackingService->getReportId();
|
||||||
$report_name = $reports[$report_id];
|
$report_name = $reports[$report_id];
|
||||||
// Code duplication.
|
// Code duplication.
|
||||||
$cache_clear_status = $this->trackingService->clearReportCache($report_id);
|
$cache_clear_status = $this->trackingService->clearReportCache($report_id);
|
||||||
|
@ -96,7 +106,7 @@ class ReportCommand extends Command {
|
||||||
$output->writeln("Csv file downloaded to: <info>{$file}</info>");
|
$output->writeln("Csv file downloaded to: <info>{$file}</info>");
|
||||||
}
|
}
|
||||||
$output->writeln("report: <info>{$report_name}</info>");
|
$output->writeln("report: <info>{$report_name}</info>");
|
||||||
$data = $this->csv->generateReportTable($file);
|
$data = $this->csv->generateReportTable($file);
|
||||||
$table = $this->buildTable($output, $data);
|
$table = $this->buildTable($output, $data);
|
||||||
$table->render();
|
$table->render();
|
||||||
|
|
||||||
|
@ -108,13 +118,17 @@ class ReportCommand extends Command {
|
||||||
*
|
*
|
||||||
* @TODO: Code duplication with InvoiceCommand::getTable.
|
* @TODO: Code duplication with InvoiceCommand::getTable.
|
||||||
*/
|
*/
|
||||||
protected function buildTable(OutputInterface $output, array $rows): Table {
|
protected function buildTable(OutputInterface $output, array $rows) : Table
|
||||||
|
{
|
||||||
$table = new Table($output);
|
$table = new Table($output);
|
||||||
$table->setHeaders([
|
$table->setHeaders([
|
||||||
'Ticket Id', 'Name', 'Time', 'Estimation',
|
'Ticket Id',
|
||||||
|
'Name',
|
||||||
|
'Time',
|
||||||
|
'Estimation',
|
||||||
]);
|
]);
|
||||||
foreach ($rows as $key => $row) {
|
foreach ($rows as $key => $row) {
|
||||||
if (!$row) {
|
if (! $row) {
|
||||||
$rows[$key] = new TableSeparator();
|
$rows[$key] = new TableSeparator();
|
||||||
} elseif (is_array($row) && is_null($row[0]) && is_null($row[2])) {
|
} elseif (is_array($row) && is_null($row[0]) && is_null($row[2])) {
|
||||||
// Check which elements in array are null.
|
// Check which elements in array are null.
|
||||||
|
@ -124,5 +138,4 @@ class ReportCommand extends Command {
|
||||||
$table->setRows($rows);
|
$table->setRows($rows);
|
||||||
return $table;
|
return $table;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,8 +14,8 @@ use Symfony\Component\Console\Command\Command;
|
||||||
* Later connect this command to the Emacs and have your time tracked directly
|
* Later connect this command to the Emacs and have your time tracked directly
|
||||||
* from orgmode.
|
* from orgmode.
|
||||||
*/
|
*/
|
||||||
class TrackCommand extends Command {
|
class TrackCommand extends Command
|
||||||
|
{
|
||||||
protected $config;
|
protected $config;
|
||||||
|
|
||||||
protected $youtrack;
|
protected $youtrack;
|
||||||
|
@ -25,19 +25,17 @@ class TrackCommand extends Command {
|
||||||
YoutrackInterface $youtrack,
|
YoutrackInterface $youtrack,
|
||||||
?string $name = null
|
?string $name = null
|
||||||
) {
|
) {
|
||||||
$this->config = $config;
|
$this->config = $config;
|
||||||
$this->youtrack = $youtrack;
|
$this->youtrack = $youtrack;
|
||||||
parent::__construct($name);
|
parent::__construct($name);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function configure(): void {
|
protected function configure() : void
|
||||||
|
{
|
||||||
$this->setName('youtrack');
|
$this->setName('youtrack');
|
||||||
$this->setDescription('Track time into your youtrack service');
|
$this->setDescription('Track time into your youtrack service');
|
||||||
$this->addUsage('rprt-cli youtrack --issue=[issue-name] --minutes=[minutes] --date=[days-ago] --description=[text] --work-type=[work-type]');
|
$this->addUsage('rprt-cli youtrack --issue=[issue-name] --minutes=[minutes] --date=[days-ago] --description=[text] --work-type=[work-type]');
|
||||||
// Options or arguments? Technically they are arguments but default value could be provided by config.
|
// Options or arguments? Technically they are arguments but default value could be provided by config.
|
||||||
// Options are more suitable.
|
// Options are more suitable.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,19 +4,14 @@ declare(strict_types=1);
|
||||||
|
|
||||||
// src/Utils/Configuration/TranslationService.php
|
// src/Utils/Configuration/TranslationService.php
|
||||||
|
|
||||||
use Symfony\Component\Translation\Translator;
|
|
||||||
use RprtCli\Utils\Configuration\ConfigurationInterface;
|
use RprtCli\Utils\Configuration\ConfigurationInterface;
|
||||||
|
|
||||||
class TranslationService {
|
class TranslationService
|
||||||
|
{
|
||||||
protected $config;
|
protected $config;
|
||||||
|
|
||||||
public function __construct(ConfigurationInterface $configuration)
|
public function __construct(ConfigurationInterface $configuration)
|
||||||
{
|
{
|
||||||
$this->config = $configuration;
|
$this->config = $configuration;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,8 +10,11 @@ use function array_key_first;
|
||||||
use function array_keys;
|
use function array_keys;
|
||||||
use function fgetcsv;
|
use function fgetcsv;
|
||||||
use function fopen;
|
use function fopen;
|
||||||
|
use function is_array;
|
||||||
|
use function number_format;
|
||||||
use function preg_match;
|
use function preg_match;
|
||||||
use function reset;
|
use function reset;
|
||||||
|
use function var_dump;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a report of projects and hours.
|
* Creates a report of projects and hours.
|
||||||
|
@ -61,10 +64,9 @@ class CsvReport implements CsvReportInterface
|
||||||
/**
|
/**
|
||||||
* Get correct values from the raw data lines of csv.
|
* Get correct values from the raw data lines of csv.
|
||||||
*
|
*
|
||||||
* @param array $rawData
|
*
|
||||||
* Columns with data are specified in config.
|
* Columns with data are specified in config.
|
||||||
*
|
*
|
||||||
* @return array
|
|
||||||
* Project key and unit of time spent.
|
* Project key and unit of time spent.
|
||||||
*/
|
*/
|
||||||
protected function parseCsvFile(array $rawData) : array
|
protected function parseCsvFile(array $rawData) : array
|
||||||
|
@ -78,10 +80,11 @@ class CsvReport implements CsvReportInterface
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function arangeDataForDefaultPdfExport(array $data): array {
|
public function arangeDataForDefaultPdfExport(array $data) : array
|
||||||
|
{
|
||||||
[$rows, $totalHours, $totalPrice] = [[], 0, 0];
|
[$rows, $totalHours, $totalPrice] = [[], 0, 0];
|
||||||
$projectsConfig = $this->configurationService->get('projects');
|
$projectsConfig = $this->configurationService->get('projects');
|
||||||
$header = $this->configurationService->get('export.labels', null);
|
$header = $this->configurationService->get('export.labels', null);
|
||||||
if (is_array($header)) {
|
if (is_array($header)) {
|
||||||
$rows[] = $header;
|
$rows[] = $header;
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,9 +22,8 @@ interface CsvReportInterface
|
||||||
/**
|
/**
|
||||||
* Data for default drunomics pdf export.
|
* Data for default drunomics pdf export.
|
||||||
*
|
*
|
||||||
* @param array $data
|
*
|
||||||
* Parsed data from csv report.
|
* Parsed data from csv report.
|
||||||
*/
|
*/
|
||||||
public function arangeDataForDefaultPdfExport(array $data): array;
|
public function arangeDataForDefaultPdfExport(array $data) : array;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,17 +11,25 @@ use RprtCli\ValueObjects\WorkInvoiceElementInterface;
|
||||||
|
|
||||||
use function array_key_first;
|
use function array_key_first;
|
||||||
use function array_keys;
|
use function array_keys;
|
||||||
|
use function array_unshift;
|
||||||
|
use function array_values;
|
||||||
|
use function explode;
|
||||||
use function fgetcsv;
|
use function fgetcsv;
|
||||||
use function fopen;
|
use function fopen;
|
||||||
|
use function implode;
|
||||||
|
use function is_array;
|
||||||
|
use function is_numeric;
|
||||||
|
use function number_format;
|
||||||
use function preg_match;
|
use function preg_match;
|
||||||
use function reset;
|
use function reset;
|
||||||
|
use function substr;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a report of projects and hours.
|
* Creates a report of projects and hours.
|
||||||
*
|
*
|
||||||
* Uses value objects instead of arrays.
|
* Uses value objects instead of arrays.
|
||||||
*/
|
*/
|
||||||
class ReportCsv implements ReportCsvInterface
|
class ReportCsv implements ReportCsvInterface
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* A configuration service.
|
* A configuration service.
|
||||||
|
@ -67,10 +75,9 @@ class ReportCsv implements ReportCsvInterface
|
||||||
/**
|
/**
|
||||||
* Get correct values from the raw data lines of csv.
|
* Get correct values from the raw data lines of csv.
|
||||||
*
|
*
|
||||||
* @param array $rawData
|
*
|
||||||
* Columns with data are specified in config.
|
* Columns with data are specified in config.
|
||||||
*
|
*
|
||||||
* @return array
|
|
||||||
* Project key and unit of time spent.
|
* Project key and unit of time spent.
|
||||||
*/
|
*/
|
||||||
protected function parseCsvFile(array $rawData) : array
|
protected function parseCsvFile(array $rawData) : array
|
||||||
|
@ -87,9 +94,10 @@ class ReportCsv implements ReportCsvInterface
|
||||||
/**
|
/**
|
||||||
* Input is array of Work elements and expenses.
|
* Input is array of Work elements and expenses.
|
||||||
*/
|
*/
|
||||||
public function generateTable(array $data): array {
|
public function generateTable(array $data) : array
|
||||||
[$rows, $totalHours, $totalPrice, $add_separator] = [[], 0, 0, FALSE];
|
{
|
||||||
$projectsConfig = $this->configurationService->get('projects');
|
[$rows, $totalHours, $totalPrice, $add_separator] = [[], 0, 0, false];
|
||||||
|
$projectsConfig = $this->configurationService->get('projects');
|
||||||
// $header = $this->configurationService->get('export.labels', null);
|
// $header = $this->configurationService->get('export.labels', null);
|
||||||
$header = null;
|
$header = null;
|
||||||
if (is_array($header)) {
|
if (is_array($header)) {
|
||||||
|
@ -98,36 +106,36 @@ class ReportCsv implements ReportCsvInterface
|
||||||
// First only list work invoice elements.
|
// First only list work invoice elements.
|
||||||
foreach ($data as $key => $invoice_element) {
|
foreach ($data as $key => $invoice_element) {
|
||||||
if ($invoice_element instanceof WorkInvoiceElementInterface) {
|
if ($invoice_element instanceof WorkInvoiceElementInterface) {
|
||||||
$add_separator = TRUE;
|
$add_separator = true;
|
||||||
$project = $invoice_element->getName();
|
$project = $invoice_element->getName();
|
||||||
$time = $invoice_element->getTime();
|
$time = $invoice_element->getTime();
|
||||||
$config = $projectsConfig[$project];
|
$config = $projectsConfig[$project];
|
||||||
$hours = $config['time_format'] == 'm' ? $time/60 : $time;
|
$hours = $config['time_format'] === 'm' ? $time / 60 : $time;
|
||||||
$price = $hours * (float) $config['price'];
|
$price = $hours * (float) $config['price'];
|
||||||
$row = [
|
$row = [
|
||||||
$config['name'] ?? $project,
|
$config['name'] ?? $project,
|
||||||
number_format($hours, 2, ',', '.'),
|
number_format($hours, 2, ',', '.'),
|
||||||
number_format($config['price'], 2, ',', '.'),
|
number_format($config['price'], 2, ',', '.'),
|
||||||
number_format($price, 2, ',', '.'),
|
number_format($price, 2, ',', '.'),
|
||||||
];
|
];
|
||||||
$totalHours += $hours;
|
$totalHours += $hours;
|
||||||
$totalPrice += $price;
|
$totalPrice += $price;
|
||||||
$rows[] = $row;
|
$rows[] = $row;
|
||||||
unset($data[$key]);
|
unset($data[$key]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ($add_separator) {
|
if ($add_separator) {
|
||||||
// @TODO replace separators with constants for normal separating.
|
// @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;
|
||||||
}
|
}
|
||||||
if (empty($data)) {
|
if (empty($data)) {
|
||||||
$add_separator = TRUE;
|
$add_separator = true;
|
||||||
}
|
}
|
||||||
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)) {
|
||||||
// Separator 0: Make next line bold and centered.
|
// Separator 0: Make next line bold and centered.
|
||||||
$rows[] = ReportCsvInterface::SEPARATOR_HARD;
|
$rows[] = ReportCsvInterface::SEPARATOR_HARD;
|
||||||
$rows[] = [
|
$rows[] = [
|
||||||
|
@ -137,17 +145,17 @@ class ReportCsv implements ReportCsvInterface
|
||||||
'EUR',
|
'EUR',
|
||||||
];
|
];
|
||||||
// Don't make next line bold. See RprtCli\PdfExport\PdfExportService::parsedDataToHtml.
|
// Don't make next line bold. See RprtCli\PdfExport\PdfExportService::parsedDataToHtml.
|
||||||
$rows[] = ReportCsvInterface::SEPARATOR_SOFT;
|
$rows[] = ReportCsvInterface::SEPARATOR_SOFT;
|
||||||
$added_expenses = TRUE;
|
$added_expenses = true;
|
||||||
}
|
}
|
||||||
$add_separator = TRUE;
|
$add_separator = true;
|
||||||
$rows[] = [
|
$rows[] = [
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
$invoice_element->getName(),
|
$invoice_element->getName(),
|
||||||
number_format($invoice_element->getValue(), 2, ',', '.'),
|
number_format($invoice_element->getValue(), 2, ',', '.'),
|
||||||
];
|
];
|
||||||
$totalPrice += $invoice_element->getValue();
|
$totalPrice += $invoice_element->getValue();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ($add_separator) {
|
if ($add_separator) {
|
||||||
|
@ -160,19 +168,21 @@ class ReportCsv implements ReportCsvInterface
|
||||||
/**
|
/**
|
||||||
* {@inheritdoc}
|
* {@inheritdoc}
|
||||||
*/
|
*/
|
||||||
public function arangeDataForDefaultPdfExport(array $data) :array {
|
public function arangeDataForDefaultPdfExport(array $data) : array
|
||||||
$rows = $this->generateTable($data);
|
{
|
||||||
|
$rows = $this->generateTable($data);
|
||||||
$header = $this->configurationService->get('export.labels', null);
|
$header = $this->configurationService->get('export.labels', null);
|
||||||
array_unshift($rows, $header);
|
array_unshift($rows, $header);
|
||||||
foreach ($rows as $key => $row) {
|
foreach ($rows as $key => $row) {
|
||||||
if (!$row) {
|
if (! $row) {
|
||||||
unset($data[$key]);
|
unset($data[$key]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return $rows;
|
return $rows;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function generateReportTable(string $filePath) {
|
public function generateReportTable(string $filePath)
|
||||||
|
{
|
||||||
// ticket-id, ticket-name, time-spent
|
// ticket-id, ticket-name, time-spent
|
||||||
$data = $this->parseReportData($filePath);
|
$data = $this->parseReportData($filePath);
|
||||||
if (empty($data)) {
|
if (empty($data)) {
|
||||||
|
@ -180,51 +190,51 @@ class ReportCsv implements ReportCsvInterface
|
||||||
}
|
}
|
||||||
[$previous, $time_sum, $project_time, $table, $all_projects] = [$data[0]['id'], 0, 0, [], []];
|
[$previous, $time_sum, $project_time, $table, $all_projects] = [$data[0]['id'], 0, 0, [], []];
|
||||||
foreach ($data as $line) {
|
foreach ($data as $line) {
|
||||||
$project = explode('-', $line['id'])[0];
|
$project = explode('-', $line['id'])[0];
|
||||||
$previous_project = explode('-', $previous)[0];
|
$previous_project = explode('-', $previous)[0];
|
||||||
if ($project !== $previous_project) {
|
if ($project !== $previous_project) {
|
||||||
// When project changes, add a sum of time for that project.
|
// When project changes, add a sum of time for that project.
|
||||||
$table[] = ReportCsvInterface::SEPARATOR_MEDIUM;
|
$table[] = ReportCsvInterface::SEPARATOR_MEDIUM;
|
||||||
$table[] = [null, $previous_project, null, $project_time/60];
|
$table[] = [null, $previous_project, null, $project_time / 60];
|
||||||
$table[] = ReportCsvInterface::SEPARATOR_MEDIUM;
|
$table[] = ReportCsvInterface::SEPARATOR_MEDIUM;
|
||||||
$time_sum += (float) $project_time;
|
$time_sum += (float) $project_time;
|
||||||
$project_time = 0;
|
$project_time = 0;
|
||||||
$all_projects[] = $project;
|
$all_projects[] = $project;
|
||||||
}
|
}
|
||||||
$project_time += (float) $line['time'];
|
$project_time += (float) $line['time'];
|
||||||
$previous = $line['id'];
|
$previous = $line['id'];
|
||||||
$table[] = array_values($line);
|
$table[] = array_values($line);
|
||||||
}
|
}
|
||||||
// Add sum for the last project.
|
// Add sum for the last project.
|
||||||
$table[] = ReportCsvInterface::SEPARATOR_MEDIUM;
|
$table[] = ReportCsvInterface::SEPARATOR_MEDIUM;
|
||||||
$table[] = [null, $project, null, $project_time / 60];
|
$table[] = [null, $project, null, $project_time / 60];
|
||||||
$time_sum += (float) $project_time;
|
$time_sum += (float) $project_time;
|
||||||
// $all_projects[] = $project;
|
// $all_projects[] = $project;
|
||||||
// Add a sum of time for whole day.
|
// Add a sum of time for whole day.
|
||||||
$table[] = ReportCsvInterface::SEPARATOR_MEDIUM;
|
$table[] = ReportCsvInterface::SEPARATOR_MEDIUM;
|
||||||
$table[] = [null, implode(', ', $all_projects), null, $time_sum/60];
|
$table[] = [null, implode(', ', $all_projects), null, $time_sum / 60];
|
||||||
return $table;
|
return $table;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritdoc}
|
* {@inheritdoc}
|
||||||
*/
|
*/
|
||||||
protected function parseReportData(string $filePath): array
|
protected function parseReportData(string $filePath) : array
|
||||||
{
|
{
|
||||||
$output = [];
|
$output = [];
|
||||||
// @TODO replace with config service.
|
// @TODO replace with config service.
|
||||||
// $config = $this->dummyConfig()['projects'];
|
// $config = $this->dummyConfig()['projects'];
|
||||||
if ($file = fopen($filePath, 'r')) {
|
if ($file = fopen($filePath, 'r')) {
|
||||||
while (($line = fgetcsv($file)) !== false) {
|
while (($line = fgetcsv($file)) !== false) {
|
||||||
if (!is_numeric($line[4])) {
|
if (! is_numeric($line[4])) {
|
||||||
// Skip header at least.
|
// Skip header at least.
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// @TODO validate line
|
// @TODO validate line
|
||||||
$output[] = [
|
$output[] = [
|
||||||
'id' => $line[1],
|
'id' => $line[1],
|
||||||
'name' => substr($line[2], 0, 60),
|
'name' => substr($line[2], 0, 60),
|
||||||
'time' => $line[4],
|
'time' => $line[4],
|
||||||
'estimation' => $line[3],
|
'estimation' => $line[3],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@ -232,7 +242,6 @@ class ReportCsv implements ReportCsvInterface
|
||||||
return $output;
|
return $output;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Should be moved into test class.
|
* Should be moved into test class.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -9,16 +9,15 @@ namespace RprtCli\Utils\CsvReport;
|
||||||
*/
|
*/
|
||||||
interface ReportCsvInterface
|
interface ReportCsvInterface
|
||||||
{
|
{
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Normal separator.
|
* Normal separator.
|
||||||
*/
|
*/
|
||||||
const SEPARATOR_SOFT = FALSE;
|
const SEPARATOR_SOFT = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Medium separator. Next line bold.
|
* Medium separator. Next line bold.
|
||||||
*/
|
*/
|
||||||
const SEPARATOR_MEDIUM = NULL;
|
const SEPARATOR_MEDIUM = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Next line should be bold and centered.
|
* Next line should be bold and centered.
|
||||||
|
@ -33,20 +32,20 @@ interface ReportCsvInterface
|
||||||
*
|
*
|
||||||
* Project key as key and number of hours as value.
|
* Project key as key and number of hours as value.
|
||||||
*/
|
*/
|
||||||
public function getInvoiceData(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.
|
||||||
*
|
*
|
||||||
* If row is null, it is meant to be a table separator.
|
* If row is null, it is meant to be a table separator.
|
||||||
*/
|
*/
|
||||||
public function generateTable(array $data) :array;
|
public function generateTable(array $data) : array;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Data for default drunomics pdf export.
|
* Data for default drunomics pdf export.
|
||||||
*
|
*
|
||||||
* @param array $data
|
*
|
||||||
* Parsed data from csv report.
|
* Parsed data from csv report.
|
||||||
*/
|
*/
|
||||||
public function arangeDataForDefaultPdfExport(array $data): array;
|
public function arangeDataForDefaultPdfExport(array $data) : array;
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,6 @@ namespace RprtCli\Utils\Mailer;
|
||||||
/**
|
/**
|
||||||
* Methods for symfony (swift)mailer service.
|
* Methods for symfony (swift)mailer service.
|
||||||
*/
|
*/
|
||||||
interface MailerInterface {
|
interface MailerInterface
|
||||||
|
{
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,21 +6,33 @@ declare(strict_types=1);
|
||||||
|
|
||||||
namespace RprtCli\Utils\Mailer;
|
namespace RprtCli\Utils\Mailer;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
use RprtCli\Utils\Configuration\ConfigurationInterface;
|
use RprtCli\Utils\Configuration\ConfigurationInterface;
|
||||||
use RprtCli\Utils\PdfExport\PdfExportInterface;
|
use RprtCli\Utils\PdfExport\PdfExportInterface;
|
||||||
use Symfony\Component\Mime\Email;
|
|
||||||
use \Exception;
|
|
||||||
use Symfony\Component\Mailer\Transport;
|
|
||||||
use Symfony\Component\Mailer\Mailer;
|
use Symfony\Component\Mailer\Mailer;
|
||||||
|
use Symfony\Component\Mailer\Transport;
|
||||||
|
use Symfony\Component\Mime\Email;
|
||||||
|
|
||||||
|
use function date;
|
||||||
|
use function explode;
|
||||||
|
use function fgets;
|
||||||
|
use function file_exists;
|
||||||
|
use function rawurlencode;
|
||||||
|
use function readline;
|
||||||
|
use function strtotime;
|
||||||
|
use function system;
|
||||||
|
use function trim;
|
||||||
|
use function var_dump;
|
||||||
|
|
||||||
|
use const STDIN;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send emails with invoices as attachments.
|
* Send emails with invoices as attachments.
|
||||||
*
|
*
|
||||||
* https://symfony.com/doc/current/mailer.html
|
* https://symfony.com/doc/current/mailer.html
|
||||||
*/
|
*/
|
||||||
class MailerService implements MailerInterface {
|
class MailerService implements MailerInterface
|
||||||
|
{
|
||||||
|
|
||||||
protected $config;
|
protected $config;
|
||||||
|
|
||||||
protected $pdf;
|
protected $pdf;
|
||||||
|
@ -34,31 +46,35 @@ class MailerService implements MailerInterface {
|
||||||
|
|
||||||
protected $email;
|
protected $email;
|
||||||
|
|
||||||
public function __construct(ConfigurationInterface $config, PdfExportInterface $pdf) {
|
public function __construct(ConfigurationInterface $config, PdfExportInterface $pdf)
|
||||||
|
{
|
||||||
$this->config = $config;
|
$this->config = $config;
|
||||||
$this->pdf = $pdf;
|
$this->pdf = $pdf;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setRecipients(array $to): void
|
public function setRecipients(array $to) : void
|
||||||
{
|
{
|
||||||
$this->to = $to;
|
$this->to = $to;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setSubject(string $subject): void {
|
public function setSubject(string $subject) : void
|
||||||
|
{
|
||||||
$this->subject = $subject;
|
$this->subject = $subject;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setAttachment(string $path): void {
|
public function setAttachment(string $path) : void
|
||||||
|
{
|
||||||
// @TODO - add some error handling.
|
// @TODO - add some error handling.
|
||||||
$this->attachment = $path;
|
$this->attachment = $path;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getProperty(string $property) {
|
public function getProperty(string $property)
|
||||||
|
{
|
||||||
// Only for simple value properies - string and numbers.
|
// Only for simple value properies - string and numbers.
|
||||||
// from, to, subject.
|
// from, to, subject.
|
||||||
if (!isset($this->{$property})) {
|
if (! isset($this->{$property})) {
|
||||||
$value = $this->config->get('email.' . $property, FALSE);
|
$value = $this->config->get('email.' . $property, false);
|
||||||
if (!$value) {
|
if (! $value) {
|
||||||
$value = readline("Property {$property} is not configured. Enter value: ");
|
$value = readline("Property {$property} is not configured. Enter value: ");
|
||||||
}
|
}
|
||||||
$this->{$property} = $value;
|
$this->{$property} = $value;
|
||||||
|
@ -66,10 +82,11 @@ class MailerService implements MailerInterface {
|
||||||
return $this->{$property};
|
return $this->{$property};
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function getRecipients() {
|
protected function getRecipients()
|
||||||
if (!isset($this->to)) {
|
{
|
||||||
$value = $this->config->get('email.to', FALSE);
|
if (! isset($this->to)) {
|
||||||
if (!$value) {
|
$value = $this->config->get('email.to', false);
|
||||||
|
if (! $value) {
|
||||||
$value = explode(',', readline('Provide recipients\' emails separated by a comma: '));
|
$value = explode(',', readline('Provide recipients\' emails separated by a comma: '));
|
||||||
}
|
}
|
||||||
$this->to = $value;
|
$this->to = $value;
|
||||||
|
@ -77,7 +94,8 @@ class MailerService implements MailerInterface {
|
||||||
return $this->to;
|
return $this->to;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function readPassword($prompt = "Enter Password:") {
|
private function readPassword($prompt = "Enter Password:")
|
||||||
|
{
|
||||||
echo $prompt;
|
echo $prompt;
|
||||||
system('stty -echo');
|
system('stty -echo');
|
||||||
$password = trim(fgets(STDIN));
|
$password = trim(fgets(STDIN));
|
||||||
|
@ -85,10 +103,11 @@ class MailerService implements MailerInterface {
|
||||||
return $password;
|
return $password;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function getPasswordProperty() {
|
protected function getPasswordProperty()
|
||||||
if (!isset($this->password)) {
|
{
|
||||||
$value = $this->config->get('email.password', FALSE);
|
if (! isset($this->password)) {
|
||||||
if (!$value) {
|
$value = $this->config->get('email.password', false);
|
||||||
|
if (! $value) {
|
||||||
$value = $this->readPassword();
|
$value = $this->readPassword();
|
||||||
}
|
}
|
||||||
$this->password = $value;
|
$this->password = $value;
|
||||||
|
@ -96,8 +115,8 @@ class MailerService implements MailerInterface {
|
||||||
return $this->password;
|
return $this->password;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function sendMail(string $from, array $to, string $subject, string $text, array $attachment = []) : void
|
||||||
public function sendMail(string $from, array $to, string $subject, string $text, array $attachment = []): void {
|
{
|
||||||
$email = new Email();
|
$email = new Email();
|
||||||
$email->from($from);
|
$email->from($from);
|
||||||
$email->to(...$to);
|
$email->to(...$to);
|
||||||
|
@ -105,21 +124,21 @@ class MailerService implements MailerInterface {
|
||||||
// https://github.com/symfony/mailer
|
// https://github.com/symfony/mailer
|
||||||
$email->subject($subject);
|
$email->subject($subject);
|
||||||
$email->text($text);
|
$email->text($text);
|
||||||
if (!empty($attachment)) {
|
if (! empty($attachment)) {
|
||||||
if (!isset($attachment['path'])) {
|
if (! isset($attachment['path'])) {
|
||||||
var_dump('Attachment path missing!');
|
var_dump('Attachment path missing!');
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
$email->attachFromPath($attachment['path'], $attachment['name'] ?? null, $attachment['type'] ?? null);
|
$email->attachFromPath($attachment['path'], $attachment['name'] ?? null, $attachment['type'] ?? null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Not sure whether it would be nicer to use class property instead of variable.
|
// Not sure whether it would be nicer to use class property instead of variable.
|
||||||
$transport = $this->getTransport();
|
$transport = $this->getTransport();
|
||||||
$mailer = $this->getMailer($transport);
|
$mailer = $this->getMailer($transport);
|
||||||
$mailer->send($email);
|
$mailer->send($email);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getTransport() {
|
public function getTransport()
|
||||||
|
{
|
||||||
// @TODO remove username and password from config.
|
// @TODO remove username and password from config.
|
||||||
$username = rawurlencode($this->getProperty('username'));
|
$username = rawurlencode($this->getProperty('username'));
|
||||||
$password = rawurlencode($this->getPasswordProperty());
|
$password = rawurlencode($this->getPasswordProperty());
|
||||||
|
@ -129,13 +148,15 @@ class MailerService implements MailerInterface {
|
||||||
return Transport::fromDsn($mailer_dsn);
|
return Transport::fromDsn($mailer_dsn);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getMailer($transport) {
|
public function getMailer($transport)
|
||||||
|
{
|
||||||
return new Mailer($transport);
|
return new Mailer($transport);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function sendDefaultMail(string $output): void {
|
public function sendDefaultMail(string $output) : void
|
||||||
$tokens = $this->pdf->gatherTokensForTemplate($this->getEmailTemplatePath(), FALSE, $this->getDefaultTokens(), 'email.tokens');
|
{
|
||||||
$text = $this->pdf->replaceTokensInTemplate($this->getEmailTemplatePath(), $tokens);
|
$tokens = $this->pdf->gatherTokensForTemplate($this->getEmailTemplatePath(), false, $this->getDefaultTokens(), 'email.tokens');
|
||||||
|
$text = $this->pdf->replaceTokensInTemplate($this->getEmailTemplatePath(), $tokens);
|
||||||
$this->sendMail(
|
$this->sendMail(
|
||||||
$this->getProperty('from'),
|
$this->getProperty('from'),
|
||||||
$this->getProperty('to'),
|
$this->getProperty('to'),
|
||||||
|
@ -145,21 +166,23 @@ class MailerService implements MailerInterface {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getDefaultTokens(): array {
|
public function getDefaultTokens() : array
|
||||||
$tokens = [];
|
{
|
||||||
$date = strtotime('-1 month');
|
$tokens = [];
|
||||||
|
$date = strtotime('-1 month');
|
||||||
$tokens['month'] = date('F', $date);
|
$tokens['month'] = date('F', $date);
|
||||||
$tokens['year'] = date('Y', $date);
|
$tokens['year'] = date('Y', $date);
|
||||||
return $tokens;
|
return $tokens;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function getEmailTemplatePath(): ?string {
|
protected function getEmailTemplatePath() : ?string
|
||||||
if (!isset($this->templatePath)) {
|
{
|
||||||
$template_path = $this->config->get('email.template_path', FALSE);
|
if (! isset($this->templatePath)) {
|
||||||
if (!$template_path) {
|
$template_path = $this->config->get('email.template_path', false);
|
||||||
|
if (! $template_path) {
|
||||||
$template_path = readline('Enter template file path: ');
|
$template_path = readline('Enter template file path: ');
|
||||||
}
|
}
|
||||||
if (!file_exists($template_path)) {
|
if (! file_exists($template_path)) {
|
||||||
throw new Exception('Template file not found!');
|
throw new Exception('Template file not found!');
|
||||||
}
|
}
|
||||||
$this->templatePath = $template_path;
|
$this->templatePath = $template_path;
|
||||||
|
@ -167,12 +190,12 @@ class MailerService implements MailerInterface {
|
||||||
return $this->templatePath;
|
return $this->templatePath;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setEmailTemplatePath(string $path): void {
|
public function setEmailTemplatePath(string $path) : void
|
||||||
|
{
|
||||||
if (file_exists($path)) {
|
if (file_exists($path)) {
|
||||||
$this->templatePath = $path;
|
$this->templatePath = $path;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
throw new Exception('Email template file not found!');
|
throw new Exception('Email template file not found!');
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,36 +7,35 @@ namespace RprtCli\Utils\PdfExport;
|
||||||
/**
|
/**
|
||||||
* Handles exporting parsed csv data to pdf files.
|
* Handles exporting parsed csv data to pdf files.
|
||||||
*/
|
*/
|
||||||
interface PdfExportInterface {
|
interface PdfExportInterface
|
||||||
|
{
|
||||||
/**
|
/**
|
||||||
* Retrieves path to template file either from command option, configuration
|
* Retrieves path to template file either from command option, configuration
|
||||||
* or from uer input.
|
* or from uer input.
|
||||||
*/
|
*/
|
||||||
public function getTemplatePath(): ?string;
|
public function getTemplatePath() : ?string;
|
||||||
|
|
||||||
public function setTemplatePath(string $path): void;
|
public function setTemplatePath(string $path) : void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates html table from parsed csv data.
|
* Creates html table from parsed csv data.
|
||||||
*
|
*
|
||||||
* @param array $data
|
*
|
||||||
* First line is header. The rest of them is table body.
|
* First line is header. The rest of them is table body.
|
||||||
*/
|
*/
|
||||||
public function parsedDataToHtmlTable(array $data): ?string;
|
public function parsedDataToHtmlTable(array $data) : ?string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reads the template file and replaces token values.
|
* Reads the template file and replaces token values.
|
||||||
*/
|
*/
|
||||||
public function replaceTokensInTemplate(string $template_path, array $tokens): ?string;
|
public function replaceTokensInTemplate(string $template_path, array $tokens) : ?string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates and export file.
|
* Creates and export file.
|
||||||
*
|
*
|
||||||
* @param string $html
|
*
|
||||||
* Template file with tokens replaced.
|
* Template file with tokens replaced.
|
||||||
*
|
*
|
||||||
* @return bool
|
|
||||||
* True if export was successfull.
|
* True if export was successfull.
|
||||||
*/
|
*/
|
||||||
public function pdfExport(string $html) : bool;
|
public function pdfExport(string $html) : bool;
|
||||||
|
@ -44,39 +43,37 @@ interface PdfExportInterface {
|
||||||
/**
|
/**
|
||||||
* Goes through the whole process of creating a pdf for the invoice.
|
* Goes through the whole process of creating a pdf for the invoice.
|
||||||
*
|
*
|
||||||
* @param array $nice_data
|
*
|
||||||
* Parsed csv report export data.
|
* Parsed csv report export data.
|
||||||
*
|
*
|
||||||
* @return string
|
|
||||||
* Path of the pdf file.
|
* Path of the pdf file.
|
||||||
*/
|
*/
|
||||||
public function fromDefaultDataToPdf(array $nice_data, array $tokens = []): string;
|
public function fromDefaultDataToPdf(array $nice_data, array $tokens = []) : string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets output file override via command paramater.
|
* Sets output file override via command paramater.
|
||||||
*
|
*
|
||||||
* @param string $output
|
*
|
||||||
* Path of the output pdf file ('/tmp/test.pdf').
|
* Path of the output pdf file ('/tmp/test.pdf').
|
||||||
*/
|
*/
|
||||||
public function setOutput(string $output): void;
|
public function setOutput(string $output) : void;
|
||||||
|
|
||||||
// @TODO support multiple templates by adding template id in config.
|
// @TODO support multiple templates by adding template id in config.
|
||||||
// @TODO implement twig.
|
// @TODO implement twig.
|
||||||
/**
|
/**
|
||||||
* Get tokens to replace in the template.
|
* Get tokens to replace in the template.
|
||||||
*
|
*
|
||||||
* @param string $template_path
|
*
|
||||||
* Path to the template file (long term plan is to support multiple templates).
|
* Path to the template file (long term plan is to support multiple templates).
|
||||||
* @param bool $skip_missing
|
*
|
||||||
|
*
|
||||||
* Just skip missing tokens.
|
* Just skip missing tokens.
|
||||||
* @param array $runtime_tokens
|
*
|
||||||
* Provide tokens at runtime of the application (not supported yet). @TODO
|
* Provide tokens at runtime of the application (not supported yet). @TODO
|
||||||
* @param string $config
|
*
|
||||||
* Config path to tokens.
|
* Config path to tokens.
|
||||||
*
|
*
|
||||||
* @return array
|
|
||||||
* Token keys and values array.
|
* Token keys and values array.
|
||||||
*/
|
*/
|
||||||
public function gatherTokensForTemplate(string $template_path, bool $skip_missing, array $runtime_tokens = [], string $config = 'export.token'): array;
|
public function gatherTokensForTemplate(string $template_path, bool $skip_missing, array $runtime_tokens = [], string $config = 'export.token') : array;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,12 +5,23 @@ declare(strict_types=1);
|
||||||
namespace RprtCli\Utils\PdfExport;
|
namespace RprtCli\Utils\PdfExport;
|
||||||
|
|
||||||
use Exception;
|
use Exception;
|
||||||
use RprtCli\Utils\Configuration\ConfigurationInterface;
|
|
||||||
use Mpdf\Output\Destination;
|
use Mpdf\Output\Destination;
|
||||||
|
use RprtCli\Utils\Configuration\ConfigurationInterface;
|
||||||
use RprtCli\Utils\CsvReport\ReportCsvInterface;
|
use RprtCli\Utils\CsvReport\ReportCsvInterface;
|
||||||
|
|
||||||
class PdfExportService implements PdfExportInterface {
|
use function array_shift;
|
||||||
|
use function date;
|
||||||
|
use function file_exists;
|
||||||
|
use function file_get_contents;
|
||||||
|
use function implode;
|
||||||
|
use function mktime;
|
||||||
|
use function preg_match_all;
|
||||||
|
use function readline;
|
||||||
|
use function str_replace;
|
||||||
|
use function strtotime;
|
||||||
|
|
||||||
|
class PdfExportService implements PdfExportInterface
|
||||||
|
{
|
||||||
protected $templatePath;
|
protected $templatePath;
|
||||||
protected $output;
|
protected $output;
|
||||||
|
|
||||||
|
@ -18,18 +29,20 @@ class PdfExportService implements PdfExportInterface {
|
||||||
|
|
||||||
protected $mpdf;
|
protected $mpdf;
|
||||||
|
|
||||||
public function __construct(ConfigurationInterface $config, $mpdf) {
|
public function __construct(ConfigurationInterface $config, $mpdf)
|
||||||
|
{
|
||||||
$this->config = $config;
|
$this->config = $config;
|
||||||
$this->mpdf = $mpdf;
|
$this->mpdf = $mpdf;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getTemplatePath(): ?string {
|
public function getTemplatePath() : ?string
|
||||||
if (!isset($this->templatePath)) {
|
{
|
||||||
$template_path = $this->config->get('export.template_path', FALSE);
|
if (! isset($this->templatePath)) {
|
||||||
if (!$template_path) {
|
$template_path = $this->config->get('export.template_path', false);
|
||||||
|
if (! $template_path) {
|
||||||
$template_path = readline('Enter template file path: ');
|
$template_path = readline('Enter template file path: ');
|
||||||
}
|
}
|
||||||
if (!file_exists($template_path)) {
|
if (! file_exists($template_path)) {
|
||||||
throw new Exception('Template file not found!');
|
throw new Exception('Template file not found!');
|
||||||
}
|
}
|
||||||
$this->templatePath = $template_path;
|
$this->templatePath = $template_path;
|
||||||
|
@ -37,7 +50,8 @@ class PdfExportService implements PdfExportInterface {
|
||||||
return $this->templatePath;
|
return $this->templatePath;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setTemplatePath(string $path): void {
|
public function setTemplatePath(string $path) : void
|
||||||
|
{
|
||||||
if (file_exists($path)) {
|
if (file_exists($path)) {
|
||||||
$this->templatePath = $path;
|
$this->templatePath = $path;
|
||||||
return;
|
return;
|
||||||
|
@ -50,40 +64,39 @@ class PdfExportService implements PdfExportInterface {
|
||||||
// @TODO would it make sense to allow more per user extending?
|
// @TODO would it make sense to allow more per user extending?
|
||||||
// @TODO - too much assumptions on data structure. Create a class with
|
// @TODO - too much assumptions on data structure. Create a class with
|
||||||
// precise data structure and use that to pass the data around.
|
// precise data structure and use that to pass the data around.
|
||||||
public function parsedDataToHtmlTable(array $data): ?string {
|
public function parsedDataToHtmlTable(array $data) : ?string
|
||||||
$table = '<table><thead><tr class="tr-header">';
|
{
|
||||||
$header = array_shift($data);
|
$table = '<table><thead><tr class="tr-header">';
|
||||||
|
$header = array_shift($data);
|
||||||
$classes = '';
|
$classes = '';
|
||||||
foreach ($header as $index => $cell) {
|
foreach ($header as $index => $cell) {
|
||||||
$table .= "<th class=\"th-{$index}\">{$cell}</th>";
|
$table .= "<th class=\"th-{$index}\">{$cell}</th>";
|
||||||
}
|
}
|
||||||
$table .= '</tr></thead><tbody>';
|
$table .= '</tr></thead><tbody>';
|
||||||
foreach ($data as $row_index => $row) {
|
foreach ($data as $row_index => $row) {
|
||||||
if (!$row) {
|
if (! $row) {
|
||||||
if ($row === ReportCsvInterface::SEPARATOR_MEDIUM) {
|
if ($row === ReportCsvInterface::SEPARATOR_MEDIUM) {
|
||||||
$classes = 'bold';
|
$classes = 'bold';
|
||||||
}
|
} elseif ($row === ReportCsvInterface::SEPARATOR_HARD) {
|
||||||
elseif ($row === ReportCsvInterface::SEPARATOR_HARD) {
|
|
||||||
$classes = 'bold center';
|
$classes = 'bold center';
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
list($cells, $colspan) = [[], 0];
|
[$cells, $colspan] = [[], 0];
|
||||||
foreach ($row as $index => $cell) {
|
foreach ($row as $index => $cell) {
|
||||||
if (!$cell) {
|
if (! $cell) {
|
||||||
$colspan += 1;
|
$colspan += 1;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if ($colspan) {
|
if ($colspan) {
|
||||||
$colspan += 1;
|
$colspan += 1;
|
||||||
$cells[] = "<td class=\"td-{$index} colspan\" colspan=\"{$colspan}\">{$cell}</td>";
|
$cells[] = "<td class=\"td-{$index} colspan\" colspan=\"{$colspan}\">{$cell}</td>";
|
||||||
$colspan = 0;
|
$colspan = 0;
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
$cells[] = "<td class=\"td-{$index}\">{$cell}</td>";
|
$cells[] = "<td class=\"td-{$index}\">{$cell}</td>";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$rows[] = "<tr class=\"tr-{$row_index} {$classes}\">" . implode($cells) . '</tr>';
|
$rows[] = "<tr class=\"tr-{$row_index} {$classes}\">" . implode($cells) . '</tr>';
|
||||||
$classes = '';
|
$classes = '';
|
||||||
}
|
}
|
||||||
$table .= implode('', $rows);
|
$table .= implode('', $rows);
|
||||||
|
@ -92,7 +105,7 @@ class PdfExportService implements PdfExportInterface {
|
||||||
return $table;
|
return $table;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function replaceTokensInTemplate(string $template_path, array $tokens): ?string
|
public function replaceTokensInTemplate(string $template_path, array $tokens) : ?string
|
||||||
{
|
{
|
||||||
$template = file_get_contents($template_path);
|
$template = file_get_contents($template_path);
|
||||||
foreach ($tokens as $key => $value) {
|
foreach ($tokens as $key => $value) {
|
||||||
|
@ -102,7 +115,8 @@ class PdfExportService implements PdfExportInterface {
|
||||||
}
|
}
|
||||||
|
|
||||||
// @TODO write a method to gather tokens.
|
// @TODO write a method to gather tokens.
|
||||||
public function getTokensInTemplate(string $template): array {
|
public function getTokensInTemplate(string $template) : array
|
||||||
|
{
|
||||||
// @TODO find substrings of type [[key]]
|
// @TODO find substrings of type [[key]]
|
||||||
preg_match_all('/\[\[([a-z0-9-_]+)\]\]/', $template, $match);
|
preg_match_all('/\[\[([a-z0-9-_]+)\]\]/', $template, $match);
|
||||||
return $match[1];
|
return $match[1];
|
||||||
|
@ -112,41 +126,40 @@ class PdfExportService implements PdfExportInterface {
|
||||||
/**
|
/**
|
||||||
* Get tokens to replace in the template.
|
* Get tokens to replace in the template.
|
||||||
*
|
*
|
||||||
* @param string $template_path
|
*
|
||||||
* Path to the template file (long term plan is to support multiple templates).
|
* Path to the template file (long term plan is to support multiple templates).
|
||||||
* @param bool $skip_missing
|
*
|
||||||
|
*
|
||||||
* Just skip missing tokens.
|
* Just skip missing tokens.
|
||||||
* @param array $runtime_tokens
|
*
|
||||||
* Provide tokens at runtime of the application (not supported yet). @TODO
|
* Provide tokens at runtime of the application (not supported yet). @TODO
|
||||||
* @param string $config
|
*
|
||||||
* Config path to tokens.
|
* Config path to tokens.
|
||||||
*
|
*
|
||||||
* @return array
|
|
||||||
* Token keys and values array.
|
* Token keys and values array.
|
||||||
*/
|
*/
|
||||||
public function gatherTokensForTemplate(string $template_path, bool $skip_missing = FALSE, array $runtime_tokens = [], string $config = 'export.tokens'): array {
|
public function gatherTokensForTemplate(string $template_path, bool $skip_missing = false, array $runtime_tokens = [], string $config = 'export.tokens') : array
|
||||||
list($tokens, $missing) = [[], []];
|
{
|
||||||
$token_keys = $this->getTokensInTemplate(file_get_contents($template_path));
|
[$tokens, $missing] = [[], []];
|
||||||
$config_tokens = $this->config->get($config);
|
$token_keys = $this->getTokensInTemplate(file_get_contents($template_path));
|
||||||
|
$config_tokens = $this->config->get($config);
|
||||||
foreach ($token_keys as $token_key) {
|
foreach ($token_keys as $token_key) {
|
||||||
if (isset($runtime_tokens[$token_key])) {
|
if (isset($runtime_tokens[$token_key])) {
|
||||||
$tokens[$token_key] = $runtime_tokens[$token_key];
|
$tokens[$token_key] = $runtime_tokens[$token_key];
|
||||||
}
|
} elseif (! isset($config_tokens[$token_key]) && ! $skip_missing) {
|
||||||
elseif (!isset($config_tokens[$token_key]) && !$skip_missing) {
|
|
||||||
$tokens[$token_key] = readline("Enter value to replace [[{$token_key}]] in template: ");
|
$tokens[$token_key] = readline("Enter value to replace [[{$token_key}]] in template: ");
|
||||||
}
|
} elseif (isset($config_tokens[$token_key])) {
|
||||||
elseif (isset($config_tokens[$token_key])) {
|
|
||||||
$tokens[$token_key] = $config_tokens[$token_key];
|
$tokens[$token_key] = $config_tokens[$token_key];
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
$missing[] = $token_key;
|
$missing[] = $token_key;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return $tokens;
|
return $tokens;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function pdfExport(string $html, $output = null): bool {
|
public function pdfExport(string $html, $output = null) : bool
|
||||||
$this->mpdf->SetProtection(array('print'));
|
{
|
||||||
|
$this->mpdf->SetProtection(['print']);
|
||||||
// @TODO make configurable.
|
// @TODO make configurable.
|
||||||
$this->mpdf->SetTitle("Invoice");
|
$this->mpdf->SetTitle("Invoice");
|
||||||
$this->mpdf->SetAuthor("rprt-cli");
|
$this->mpdf->SetAuthor("rprt-cli");
|
||||||
|
@ -156,29 +169,32 @@ class PdfExportService implements PdfExportInterface {
|
||||||
return file_exists($this->output);
|
return file_exists($this->output);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function getOutput() {
|
protected function getOutput()
|
||||||
|
{
|
||||||
if (isset($this->output)) {
|
if (isset($this->output)) {
|
||||||
return $this->output;
|
return $this->output;
|
||||||
}
|
}
|
||||||
$output = $this->config->get('export.output', NULL) ?? readline('Enter output file path: ');
|
$output = $this->config->get('export.output', null) ?? readline('Enter output file path: ');
|
||||||
$date = strtotime("-1 month");
|
$date = strtotime("-1 month");
|
||||||
$output = str_replace('[[month]]', date('F', $date), $output);
|
$output = str_replace('[[month]]', date('F', $date), $output);
|
||||||
$output = str_replace('[[year]]', date('Y', $date), $output);
|
$output = str_replace('[[year]]', date('Y', $date), $output);
|
||||||
$this->output = $output;
|
$this->output = $output;
|
||||||
return $output;
|
return $output;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setOutput(string $path): void {
|
public function setOutput(string $path) : void
|
||||||
|
{
|
||||||
$this->output = $path;
|
$this->output = $path;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function fromDefaultDataToPdf(array $data, array $tokens = []): string {
|
public function fromDefaultDataToPdf(array $data, array $tokens = []) : string
|
||||||
$template_path = $this->getTemplatePath();
|
{
|
||||||
$tokens = $this->defaultTokens();
|
$template_path = $this->getTemplatePath();
|
||||||
|
$tokens = $this->defaultTokens();
|
||||||
$tokens['table'] = $this->parsedDataToHtmlTable($data);
|
$tokens['table'] = $this->parsedDataToHtmlTable($data);
|
||||||
$tokens = $this->gatherTokensForTemplate($template_path, FALSE, $tokens);
|
$tokens = $this->gatherTokensForTemplate($template_path, false, $tokens);
|
||||||
$html = $this->replaceTokensInTemplate($template_path, $tokens);
|
$html = $this->replaceTokensInTemplate($template_path, $tokens);
|
||||||
$success = $this->pdfExport($html);
|
$success = $this->pdfExport($html);
|
||||||
if ($success) {
|
if ($success) {
|
||||||
return $this->getOutput();
|
return $this->getOutput();
|
||||||
}
|
}
|
||||||
|
@ -188,14 +204,14 @@ class PdfExportService implements PdfExportInterface {
|
||||||
/**
|
/**
|
||||||
* Get default tokens.
|
* Get default tokens.
|
||||||
*/
|
*/
|
||||||
protected function defaultTokens(): array {
|
protected function defaultTokens() : array
|
||||||
$tokens = [];
|
{
|
||||||
$tokens['today'] = date('j. m. y');
|
$tokens = [];
|
||||||
$month_ago = strtotime('1 month ago');
|
$tokens['today'] = date('j. m. y');
|
||||||
$tokens['number'] = date('m/y', $month_ago);
|
$month_ago = strtotime('1 month ago');
|
||||||
|
$tokens['number'] = date('m/y', $month_ago);
|
||||||
$tokens['date_start'] = date('1. m. Y', $month_ago);
|
$tokens['date_start'] = date('1. m. Y', $month_ago);
|
||||||
$tokens['date_end'] = date("d. m. Y", mktime(0, 0, 0, (int) date("m"), 0));
|
$tokens['date_end'] = date("d. m. Y", mktime(0, 0, 0, (int) date("m"), 0));
|
||||||
return $tokens;
|
return $tokens;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,12 +19,19 @@ interface YoutrackInterface
|
||||||
/**
|
/**
|
||||||
* Downloads report and returns file path.
|
* Downloads report and returns file path.
|
||||||
*
|
*
|
||||||
* @param string $report_id
|
*
|
||||||
* Youtrack internal report id.
|
* Youtrack internal report id.
|
||||||
*
|
*
|
||||||
* @return NULL|string
|
*
|
||||||
* If fetch was unsuccssefull return false, otherwise the file path.
|
* If fetch was unsuccssefull return false, otherwise the file path.
|
||||||
*/
|
*/
|
||||||
public function downloadReport(string $report_id) : ?string;
|
public function downloadReport(string $report_id) : ?string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a list of reports.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Array of reports with ids as keys and names as values.
|
||||||
|
*/
|
||||||
|
public function listReports() : array;
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,13 +6,28 @@ declare(strict_types=1);
|
||||||
|
|
||||||
namespace RprtCli\Utils\TimeTrackingServices;
|
namespace RprtCli\Utils\TimeTrackingServices;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
use GuzzleHttp\ClientInterface;
|
use GuzzleHttp\ClientInterface;
|
||||||
use GuzzleHttp\Exception\ClientException;
|
use GuzzleHttp\Exception\ClientException;
|
||||||
use RprtCli\Utils\Configuration\ConfigurationInterface;
|
use RprtCli\Utils\Configuration\ConfigurationInterface;
|
||||||
|
use Throwable;
|
||||||
|
|
||||||
|
use function array_combine;
|
||||||
|
use function array_filter;
|
||||||
|
use function array_map;
|
||||||
|
use function file_put_contents;
|
||||||
|
use function floor;
|
||||||
|
use function json_decode;
|
||||||
|
use function microtime;
|
||||||
|
use function readline;
|
||||||
|
use function sleep;
|
||||||
|
use function sys_get_temp_dir;
|
||||||
|
use function tempnam;
|
||||||
|
use function trim;
|
||||||
|
use function var_dump;
|
||||||
|
|
||||||
class YoutrackService implements YoutrackInterface
|
class YoutrackService implements YoutrackInterface
|
||||||
{
|
{
|
||||||
|
|
||||||
protected $ytToken;
|
protected $ytToken;
|
||||||
protected $ytBaseUrl;
|
protected $ytBaseUrl;
|
||||||
|
|
||||||
|
@ -24,45 +39,47 @@ class YoutrackService implements YoutrackInterface
|
||||||
|
|
||||||
public function __construct(ConfigurationInterface $config, ClientInterface $http_client)
|
public function __construct(ConfigurationInterface $config, ClientInterface $http_client)
|
||||||
{
|
{
|
||||||
$this->config = $config;
|
$this->config = $config;
|
||||||
$this->httpClient = $http_client;
|
$this->httpClient = $http_client;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testYoutrackapi(): ?string
|
public function testYoutrackapi() : ?string
|
||||||
{
|
{
|
||||||
// Get base url from config or add input.
|
// Get base url from config or add input.
|
||||||
// Get token or add input.
|
// Get token or add input.
|
||||||
$path = 'youtrack/api/admin/users/me';
|
$path = 'youtrack/api/admin/users/me';
|
||||||
$yt_url = $this->getYtUrl($path);
|
$yt_url = $this->getYtUrl($path);
|
||||||
$yt_token = $this->getYtToken();
|
$yt_token = $this->getYtToken();
|
||||||
$query = ['fields' => 'id,email,fullName'];
|
$query = ['fields' => 'id,email,fullName'];
|
||||||
$headers = [
|
$headers = [
|
||||||
"Authorization" => "Bearer $yt_token",
|
"Authorization" => "Bearer $yt_token",
|
||||||
'Cache-Control' =>'no-cache',
|
'Cache-Control' => 'no-cache',
|
||||||
];
|
];
|
||||||
$me_response = $this->httpClient->request('GET', $yt_url, [
|
$me_response = $this->httpClient->request('GET', $yt_url, [
|
||||||
'query' => $query,
|
'query' => $query,
|
||||||
'headers' => $headers
|
'headers' => $headers,
|
||||||
]);
|
]);
|
||||||
$test = (string) $me_response->getBody()->getContents();
|
$test = (string) $me_response->getBody()->getContents();
|
||||||
$me_json = (array) json_decode($test);
|
$me_json = (array) json_decode($test);
|
||||||
if ($me_json && isset($me_json['fullName'])) {
|
if ($me_json && isset($me_json['fullName'])) {
|
||||||
return $me_json['fullName'];
|
return $me_json['fullName'];
|
||||||
}
|
}
|
||||||
|
|
||||||
return NULL;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function requestYoutrackPath(string $path, array $query) {
|
protected function requestYoutrackPath(string $path, array $query)
|
||||||
$yt_url = $this->getYtUrl($path);
|
{
|
||||||
|
$yt_url = $this->getYtUrl($path);
|
||||||
$headers = $this->getHeaders();
|
$headers = $this->getHeaders();
|
||||||
return $this->httpClient->request('GET', $yt_url, [
|
return $this->httpClient->request('GET', $yt_url, [
|
||||||
'query' => $query,
|
'query' => $query,
|
||||||
'headers' => $headers
|
'headers' => $headers,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function getHeaders() {
|
protected function getHeaders()
|
||||||
|
{
|
||||||
$yt_token = $this->getYtToken();
|
$yt_token = $this->getYtToken();
|
||||||
return [
|
return [
|
||||||
"Authorization" => "Bearer $yt_token",
|
"Authorization" => "Bearer $yt_token",
|
||||||
|
@ -70,7 +87,7 @@ class YoutrackService implements YoutrackInterface
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getReportId(): ?string
|
public function getReportId() : ?string
|
||||||
{
|
{
|
||||||
// --report option value should take precedence.
|
// --report option value should take precedence.
|
||||||
// @TODO error handling.
|
// @TODO error handling.
|
||||||
|
@ -78,132 +95,135 @@ class YoutrackService implements YoutrackInterface
|
||||||
return $this->report_id;
|
return $this->report_id;
|
||||||
}
|
}
|
||||||
$yt_report_id = $this->config->get('tracking_service.youtrack.report_id');
|
$yt_report_id = $this->config->get('tracking_service.youtrack.report_id');
|
||||||
if (!$yt_report_id) {
|
if (! $yt_report_id) {
|
||||||
$yt_report_id = readline('Enter the report id: ');
|
$yt_report_id = readline('Enter the report id: ');
|
||||||
}
|
}
|
||||||
return $yt_report_id;
|
return $yt_report_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setReportId(string $report_id) :void {
|
public function setReportId(string $report_id) : void
|
||||||
|
{
|
||||||
$this->report_id = $report_id;
|
$this->report_id = $report_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function downloadReport(string $report_id): ?string
|
public function downloadReport(string $report_id) : ?string
|
||||||
{
|
{
|
||||||
$path = "youtrack/api/reports/$report_id/export/csv";
|
$path = "youtrack/api/reports/$report_id/export/csv";
|
||||||
$query = ['$top' => -1];
|
$query = ['$top' => -1];
|
||||||
$yt_token = $this->getYtToken();
|
$yt_token = $this->getYtToken();
|
||||||
$headers = [
|
$headers = [
|
||||||
'Accept' => 'application/json, text/plain, */*',
|
'Accept' => 'application/json, text/plain, */*',
|
||||||
// 'Accept-Encoding' => 'gzip, deflate, br',
|
// 'Accept-Encoding' => 'gzip, deflate, br',
|
||||||
// 'Connection' => 'keep-alive',
|
// 'Connection' => 'keep-alive',
|
||||||
'Accept-Language' => 'en-US,en;q=0.5',
|
'Accept-Language' => 'en-US,en;q=0.5',
|
||||||
"Authorization" => "Bearer $yt_token",
|
"Authorization" => "Bearer $yt_token",
|
||||||
];
|
];
|
||||||
try {
|
try {
|
||||||
$csv_response = $this->httpClient->request('GET', $this->getYtUrl($path), [
|
$csv_response = $this->httpClient->request('GET', $this->getYtUrl($path), [
|
||||||
'headers' => $headers,
|
'headers' => $headers,
|
||||||
'query' => $query,
|
'query' => $query,
|
||||||
]);
|
]);
|
||||||
// Write csv response test into temporary file.
|
// Write csv response test into temporary file.
|
||||||
$csv_file = tempnam(sys_get_temp_dir(), "rprt-csv-{$report_id}");
|
$csv_file = tempnam(sys_get_temp_dir(), "rprt-csv-{$report_id}");
|
||||||
file_put_contents($csv_file, $csv_response->getBody());
|
file_put_contents($csv_file, $csv_response->getBody());
|
||||||
return $csv_file;
|
return $csv_file;
|
||||||
}
|
} catch (ClientException $e) {
|
||||||
catch (ClientException $e) {
|
|
||||||
$status = $e->getResponse()->getStatusCode();
|
$status = $e->getResponse()->getStatusCode();
|
||||||
if ($status == 409) {
|
if ($status === 409) {
|
||||||
sleep(3);
|
sleep(3);
|
||||||
// @TODO Find a way to break of of loop if necessary!
|
// @TODO Find a way to break of of loop if necessary!
|
||||||
var_dump("409 response status during download of report {$report_id}. Sleep 3 and try again.");
|
var_dump("409 response status during download of report {$report_id}. Sleep 3 and try again.");
|
||||||
return $this->downloadReport($report_id);
|
return $this->downloadReport($report_id);
|
||||||
}
|
}
|
||||||
}
|
} catch (Throwable $t) {
|
||||||
catch (\Throwable $t) {
|
|
||||||
$status = $t->getMessage();
|
$status = $t->getMessage();
|
||||||
var_dump($status);
|
var_dump($status);
|
||||||
}
|
}
|
||||||
throw new \Exception("Unable to download report {$report_id}!");
|
throw new Exception("Unable to download report {$report_id}!");
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function getYtToken(): string {
|
protected function getYtToken() : string
|
||||||
|
{
|
||||||
if (isset($this->ytToken)) {
|
if (isset($this->ytToken)) {
|
||||||
return $this->ytToken;
|
return $this->ytToken;
|
||||||
}
|
}
|
||||||
$yt_token = $this->config->get('tracking_service.youtrack.auth_token', FALSE);
|
$yt_token = $this->config->get('tracking_service.youtrack.auth_token', false);
|
||||||
if (!$yt_token) {
|
if (! $yt_token) {
|
||||||
$yt_token = readline('Enter your youtrack authentication token: ');
|
$yt_token = readline('Enter your youtrack authentication token: ');
|
||||||
}
|
}
|
||||||
return $yt_token;
|
return $yt_token;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setYtToken(string $token): void {
|
public function setYtToken(string $token) : void
|
||||||
|
{
|
||||||
$this->ytToken = $token;
|
$this->ytToken = $token;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function getYtUrl(string $path = ''): ?string {
|
protected function getYtUrl(string $path = '') : ?string
|
||||||
|
{
|
||||||
if (isset($this->ytBaseUrl)) {
|
if (isset($this->ytBaseUrl)) {
|
||||||
$yt_base_url = $this->ytBaseUrl;
|
$yt_base_url = $this->ytBaseUrl;
|
||||||
}
|
} else {
|
||||||
else {
|
$yt_base_url = $this->config->get('tracking_service.youtrack.base_url', false);
|
||||||
$yt_base_url = $this->config->get('tracking_service.youtrack.base_url', FALSE);
|
|
||||||
}
|
}
|
||||||
if (empty($yt_base_url)) {
|
if (empty($yt_base_url)) {
|
||||||
$yt_base_url = readline('Enter base url for of the youtrack service: ');
|
$yt_base_url = readline('Enter base url for of the youtrack service: ');
|
||||||
}
|
}
|
||||||
if (!empty($path)) {
|
if (! empty($path)) {
|
||||||
$yt_base_url = $yt_base_url . '/' . trim($path, '/');
|
$yt_base_url .= '/' . trim($path, '/');
|
||||||
}
|
}
|
||||||
return $yt_base_url;
|
return $yt_base_url;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setYtUrl(string $base_url) {
|
public function setYtUrl(string $base_url)
|
||||||
|
{
|
||||||
$this->ytBaseUrl = $base_url;
|
$this->ytBaseUrl = $base_url;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function listReports() {
|
public function listReports() : array
|
||||||
|
{
|
||||||
// Now filter results by own = true;
|
// Now filter results by own = true;
|
||||||
$url = '/youtrack/api/reports';
|
$url = '/youtrack/api/reports';
|
||||||
$query = [
|
$query = [
|
||||||
'$top' => -1,
|
'$top' => -1,
|
||||||
'fields' => 'id,name,own,owner(login,name)',
|
'fields' => 'id,name,own,owner(login,name)',
|
||||||
];
|
];
|
||||||
$response = $this->requestYoutrackPath($url, $query);
|
$response = $this->requestYoutrackPath($url, $query);
|
||||||
$body = (string) $response->getBody()->getContents();
|
$body = (string) $response->getBody()->getContents();
|
||||||
$body_json = (array) json_decode($body);
|
$body_json = (array) json_decode($body);
|
||||||
$my_reports = array_filter($body_json, fn($report) => $report->own);
|
$my_reports = array_filter($body_json, fn($report) => $report->own);
|
||||||
$reports = array_combine(
|
$reports = array_combine(
|
||||||
array_map(fn($r) => $r->id, $my_reports),
|
array_map(fn($r) => $r->id, $my_reports),
|
||||||
array_map(fn ($r) => $r->name, $my_reports)
|
array_map(fn ($r) => $r->name, $my_reports)
|
||||||
);
|
);
|
||||||
return $reports;
|
return $reports;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function clearReportCache(string $report_id) :int {
|
public function clearReportCache(string $report_id) : int
|
||||||
$path = "/youtrack/api/reports/${report_id}/status";
|
{
|
||||||
$query = [
|
$path = "/youtrack/api/reports/${report_id}/status";
|
||||||
'$top' => -1,
|
$query = [
|
||||||
'fields' => 'calculationInProgress,error(id),errorMessage,isOutdated,lastCalculated,progress,wikifiedErrorMessage'
|
'$top' => -1,
|
||||||
|
'fields' => 'calculationInProgress,error(id),errorMessage,isOutdated,lastCalculated,progress,wikifiedErrorMessage',
|
||||||
];
|
];
|
||||||
$post = [
|
$post = [
|
||||||
'lastCalculated' => floor(microtime(true) * 1000),
|
'lastCalculated' => floor(microtime(true) * 1000),
|
||||||
'calculationInProgress' => true,
|
'calculationInProgress' => true,
|
||||||
'wikifiedErrorMessage' => '',
|
'wikifiedErrorMessage' => '',
|
||||||
'isOutdated' => false,
|
'isOutdated' => false,
|
||||||
'progress' => -1,
|
'progress' => -1,
|
||||||
'error' => null,
|
'error' => null,
|
||||||
'errorMessage' => null,
|
'errorMessage' => null,
|
||||||
'$type' => 'ReportStatus'
|
'$type' => 'ReportStatus',
|
||||||
];
|
];
|
||||||
$yt_url = $this->getYtUrl($path);
|
$yt_url = $this->getYtUrl($path);
|
||||||
$response = $this->httpClient->request('POST', $yt_url, [
|
$response = $this->httpClient->request('POST', $yt_url, [
|
||||||
'query' => $query,
|
'query' => $query,
|
||||||
'headers' => $this->getHeaders(),
|
'headers' => $this->getHeaders(),
|
||||||
'json' => $post,
|
'json' => $post,
|
||||||
]);
|
]);
|
||||||
// $body = (string) $response->getBody()->getContents();
|
// $body = (string) $response->getBody()->getContents();
|
||||||
// var_dump($body);
|
// var_dump($body);
|
||||||
return $response->getStatusCode();
|
return $response->getStatusCode();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,8 +4,8 @@ declare(strict_types=1);
|
||||||
|
|
||||||
namespace RprtCli\ValueObjects;
|
namespace RprtCli\ValueObjects;
|
||||||
|
|
||||||
class Expenses implements ExpensesInterface {
|
class Expenses implements ExpensesInterface
|
||||||
|
{
|
||||||
/**
|
/**
|
||||||
* Expenses in current currency.
|
* Expenses in current currency.
|
||||||
*/
|
*/
|
||||||
|
@ -16,17 +16,18 @@ class Expenses implements ExpensesInterface {
|
||||||
*/
|
*/
|
||||||
private string $name;
|
private string $name;
|
||||||
|
|
||||||
public function __construct(string $name, float $value) {
|
public function __construct(string $name, float $value)
|
||||||
$this->name = $name;
|
{
|
||||||
|
$this->name = $name;
|
||||||
$this->value = $value;
|
$this->value = $value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getValue(): float
|
public function getValue() : float
|
||||||
{
|
{
|
||||||
return $this->value;
|
return $this->value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getName(): string
|
public function getName() : string
|
||||||
{
|
{
|
||||||
return $this->name;
|
return $this->name;
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,8 +4,7 @@ declare(strict_types=1);
|
||||||
|
|
||||||
namespace RprtCli\ValueObjects;
|
namespace RprtCli\ValueObjects;
|
||||||
|
|
||||||
interface ExpensesInterface extends InvoiceElementInterface {
|
interface ExpensesInterface extends InvoiceElementInterface
|
||||||
|
{
|
||||||
public function getValue() :float;
|
public function getValue() : float;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,11 +7,10 @@ namespace RprtCli\ValueObjects;
|
||||||
/**
|
/**
|
||||||
* Main interface for invoice elements.
|
* Main interface for invoice elements.
|
||||||
*/
|
*/
|
||||||
interface InvoiceElementInterface {
|
interface InvoiceElementInterface
|
||||||
|
{
|
||||||
/**
|
/**
|
||||||
* Project or expenses name.
|
* Project or expenses name.
|
||||||
*/
|
*/
|
||||||
public function getName() :string;
|
public function getName() : string;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,8 +4,8 @@ declare(strict_types=1);
|
||||||
|
|
||||||
namespace RprtCli\ValueObjects;
|
namespace RprtCli\ValueObjects;
|
||||||
|
|
||||||
class WorkInvoiceElement implements WorkInvoiceElementInterface {
|
class WorkInvoiceElement implements WorkInvoiceElementInterface
|
||||||
|
{
|
||||||
private float $time;
|
private float $time;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -13,17 +13,19 @@ class WorkInvoiceElement implements WorkInvoiceElementInterface {
|
||||||
*/
|
*/
|
||||||
private string $name;
|
private string $name;
|
||||||
|
|
||||||
public function __construct(string $name, float $time) {
|
public function __construct(string $name, float $time)
|
||||||
|
{
|
||||||
$this->name = $name;
|
$this->name = $name;
|
||||||
$this->time = $time;
|
$this->time = $time;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getTime() :float {
|
public function getTime() : float
|
||||||
|
{
|
||||||
return $this->time;
|
return $this->time;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getName() :string {
|
public function getName() : string
|
||||||
|
{
|
||||||
return $this->name;
|
return $this->name;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,12 +4,12 @@ declare(strict_types=1);
|
||||||
|
|
||||||
namespace RprtCli\ValueObjects;
|
namespace RprtCli\ValueObjects;
|
||||||
|
|
||||||
interface WorkInvoiceElementInterface extends InvoiceElementInterface {
|
interface WorkInvoiceElementInterface extends InvoiceElementInterface
|
||||||
|
{
|
||||||
public function getTime() :float ;
|
public function getTime() : float;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get project name.
|
* Get project name.
|
||||||
*/
|
*/
|
||||||
public function getName() :string ;
|
public function getName() : string;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue