Add pdf export.
parent
9cf4d23012
commit
e605729aaf
|
@ -69,5 +69,5 @@
|
|||
|
||||
*** Get csv file
|
||||
|
||||
curl 'https://drunomics.myjetbrains.com/youtrack/api/reports/83-554/export/csv?&$top=-1' -H 'Accept: application/json, text/plain, */*' -H 'Accept-Language: en-US,en;q=0.5' --compressed -H "Authorization: Bearer $TKN" > ~/Documents/Drunomics/workhours/2021/21-09.csv
|
||||
~curl 'https://drunomics.myjetbrains.com/youtrack/api/reports/83-554/export/csv?&$top=-1' -H 'Accept: application/json, text/plain, */*' -H 'Accept-Language: en-US,en;q=0.5' --compressed -H "Authorization: Bearer $TKN" > ~/Documents/Drunomics/workhours/2021/21-09.csv~
|
||||
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
<html>
|
||||
<head>
|
||||
<style>
|
||||
body {
|
||||
font-family: sans-serif;
|
||||
font-size: 10pt;
|
||||
}
|
||||
p { margin: 0pt; }
|
||||
td { vertical-align: top; }
|
||||
.items td {
|
||||
border-left: 0.1mm solid #000000;
|
||||
border-right: 0.1mm solid #000000;
|
||||
}
|
||||
table thead td { background-color: #EEEEEE;
|
||||
text-align: center;
|
||||
border: 0.1mm solid #000000;
|
||||
font-variant: small-caps;
|
||||
}
|
||||
.items td.blanktotal {
|
||||
background-color: #EEEEEE;
|
||||
border: 0.1mm solid #000000;
|
||||
background-color: #FFFFFF;
|
||||
border: 0mm none #000000;
|
||||
border-top: 0.1mm solid #000000;
|
||||
border-right: 0.1mm solid #000000;
|
||||
}
|
||||
.items td.totals {
|
||||
text-align: right;
|
||||
border: 0.1mm solid #000000;
|
||||
}
|
||||
.items td.cost,
|
||||
.center {
|
||||
text-align: center;
|
||||
}
|
||||
.right {
|
||||
text-align: right;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<p class="center">My company name</br>My company address</br>123456789, mycompany@email.com</br>987654321</p>
|
||||
<p></p>
|
||||
<p>Other company</br>Other company address</br>1111 City</br>State</p>
|
||||
<p></p>
|
||||
<p></p>
|
||||
<p></p>
|
||||
<p class="right">City, on 20. 9. 2021</p>
|
||||
<p><strong>Honorarnote</br>Nummer [[number]]</strong></p>
|
||||
<p></p>
|
||||
<p>Für meine Tätigkeit Programmieren von [[date_start]] bis [[date_end]] erlaube ich mir, folgenden Betrag in Rechnung zu stellen:</p>
|
||||
[[table]]
|
||||
<p>Ich ersuche Sie höflich, den oben angeführt auf meine Kontonummer [[account_number]] mit der Bankleitzahl ABCDSI33 zu überweisen.</p>
|
||||
<p>Vielen Dank für den Auftrag,</br>
|
||||
mit besten Grüßen,</p>
|
||||
<p></p>
|
||||
<p></p>
|
||||
<p>My name</p>
|
||||
</body>
|
||||
</html>
|
|
@ -1,9 +1,22 @@
|
|||
##########################################
|
||||
# Configuration file for RprtCli Command #
|
||||
##########################################
|
||||
tracking service:
|
||||
tracking_service:
|
||||
youtrack:
|
||||
auth token: '<value from youtrack hub>'
|
||||
auth_token: '<value from youtrack hub>'
|
||||
base_url: 'https://test.youtrack.com'
|
||||
report_id: '<89-123>'
|
||||
export:
|
||||
template_path: '~/.config/rprt-cli/invoice-template.html'
|
||||
output: '/tmp/YYYY-mm-invoice.pdf'
|
||||
tokens:
|
||||
key: 'value to replace key'
|
||||
another_key: 'value to replace another key'
|
||||
labels:
|
||||
- 'Project'
|
||||
- 'Hours'
|
||||
- 'Rate'
|
||||
- 'Price'
|
||||
# reports:
|
||||
# report short name:
|
||||
# table:
|
||||
|
@ -21,10 +34,5 @@ projects:
|
|||
time column: 4
|
||||
# time format m - minutes, h - hours
|
||||
time format: 'm'
|
||||
labels:
|
||||
project: Project
|
||||
# hours: Quantity
|
||||
hours: Hours
|
||||
rate: 'Price per hour'
|
||||
price: Price
|
||||
|
||||
locale: 'en_GB'
|
||||
|
|
|
@ -9,6 +9,9 @@ use RprtCli\Utils\Configuration\ConfigurationService;
|
|||
use RprtCli\Utils\CsvReport\CsvReport;
|
||||
use RprtCli\Utils\CsvReport\CsvReportInterface;
|
||||
use GuzzleHttp\Client;
|
||||
use Mpdf\Mpdf;
|
||||
use RprtCli\Utils\PdfExport\PdfExportInterface;
|
||||
use RprtCli\Utils\PdfExport\PdfExportService;
|
||||
use RprtCli\Utils\TimeTrackingServices\YoutrackInterface;
|
||||
use RprtCli\Utils\TimeTrackingServices\YoutrackService;
|
||||
|
||||
|
@ -22,6 +25,7 @@ return [
|
|||
// 'translator' => ['default_path' => '%kernel.project_dir%/translations'],
|
||||
// 'guzzle' => create()->constructor(Client::class),
|
||||
'guzzle' => get(Client::class),
|
||||
'mpdf' => get(Mpdf::class),
|
||||
ConfigurationInterface::class => get(ConfigurationService::class),
|
||||
ConfigurationService::class => create()->constructor(
|
||||
get('config.path'),
|
||||
|
@ -34,6 +38,12 @@ return [
|
|||
get('guzzle')
|
||||
),
|
||||
'youtrack.service' => get(YoutrackInterface::class),
|
||||
PdfExportInterface::class => get(PdfExportService::class),
|
||||
PdfExportService::class => create()->constructor(
|
||||
get('config.service'),
|
||||
get('mpdf')
|
||||
),
|
||||
'pdf_export.service' => get(PdfExportInterface::class),
|
||||
// 'locale' => get('config.service')->method('get', 'en'),
|
||||
// Translator::class => create()->constructor('sl')->method('addLoader', 'po', new PoFileLoader),
|
||||
// 'translator' => get(Translator::class),
|
||||
|
@ -45,6 +55,7 @@ return [
|
|||
RprtCommand::class => create()->constructor(
|
||||
get('csv.report'),
|
||||
get('config.service'),
|
||||
get('youtrack.service')
|
||||
get('youtrack.service'),
|
||||
get('pdf_export.service')
|
||||
),
|
||||
];
|
||||
|
|
|
@ -8,6 +8,7 @@ namespace RprtCli\Commands;
|
|||
|
||||
use RprtCli\Utils\Configuration\ConfigurationInterface;
|
||||
use RprtCli\Utils\CsvReport\CsvReportInterface;
|
||||
use RprtCli\Utils\PdfExport\PdfExportInterface;
|
||||
use RprtCli\Utils\TimeTrackingServices\YoutrackInterface;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Helper\Table;
|
||||
|
@ -30,15 +31,19 @@ class RprtCommand extends Command
|
|||
|
||||
protected $youtrack;
|
||||
|
||||
protected $pdfExport;
|
||||
|
||||
public function __construct(
|
||||
CsvReportInterface $csv,
|
||||
ConfigurationInterface $configuration,
|
||||
YoutrackInterface $youtrack,
|
||||
PdfExportInterface $pdf_export,
|
||||
?string $name = null
|
||||
) {
|
||||
$this->csv = $csv;
|
||||
$this->configuration = $configuration;
|
||||
$this->youtrack = $youtrack;
|
||||
$this->pdfExport = $pdf_export;
|
||||
parent::__construct($name);
|
||||
}
|
||||
|
||||
|
@ -82,10 +87,23 @@ class RprtCommand extends Command
|
|||
$test = $this->youtrack->testYoutrackapi();
|
||||
$output->writeln($test);
|
||||
}
|
||||
if ($file = $input->getOption('file')) {
|
||||
if ($youtrack = $input->getOption('youtrack')) {
|
||||
$report_id = $this->youtrack->getReportId();
|
||||
$file = $this->youtrack->downloadReport($report_id);
|
||||
}
|
||||
if ($youtrack || $file = $input->getOption('file')) {
|
||||
// Youtrack can also provide a file name.
|
||||
var_dump($file);
|
||||
$data = $this->csv->getReportData($file);
|
||||
$table = $this->generateTable($output, $data);
|
||||
$table->render();
|
||||
|
||||
if ($pdf = $input->getOption('pdf')) {
|
||||
$nice_data = $this->csv->arangeDataForPdfExport($data);
|
||||
// @TODO method gatherTokens();
|
||||
$this->pdfExport->fromDataToPdf($nice_data);
|
||||
}
|
||||
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
|
||||
|
|
|
@ -58,9 +58,10 @@ class CsvReport implements CsvReportInterface
|
|||
/**
|
||||
* Get correct values from the raw data lines of csv.
|
||||
*
|
||||
*
|
||||
* @param array $rawData
|
||||
* Columns with data are specified in config.
|
||||
*
|
||||
* @return array
|
||||
* Project key and unit of time spent.
|
||||
*/
|
||||
protected function parseCsvFile(array $rawData) : array
|
||||
|
@ -74,6 +75,41 @@ class CsvReport implements CsvReportInterface
|
|||
return [];
|
||||
}
|
||||
|
||||
public function arangeDataForPdfExport(array $data): array {
|
||||
[$rows, $totalHours, $totalPrice] = [[], 0, 0];
|
||||
$projectsConfig = $this->configurationService->get('projects');
|
||||
$header = $this->configurationService->get('export.labels', null);
|
||||
if (is_array($header)) {
|
||||
$rows[] = $header;
|
||||
}
|
||||
foreach ($projectsConfig as $name => $config) {
|
||||
if (! isset($data[$name])) {
|
||||
// @TODO Proper error handling.
|
||||
var_dump('Project ' . $name . ' is not set!');
|
||||
continue;
|
||||
}
|
||||
$hours = $data[$name];
|
||||
if ($config['time_format'] === 'm') {
|
||||
$hours /= 60;
|
||||
}
|
||||
$price = $hours * (int) $config['price'];
|
||||
$row = [
|
||||
$config['name'],
|
||||
$hours,
|
||||
$config['price'],
|
||||
$hours * $config['price'],
|
||||
];
|
||||
$rows[] = $row;
|
||||
$totalHours += $hours;
|
||||
$totalPrice += $price;
|
||||
}
|
||||
// @TODO Check rate in final result.
|
||||
// $rows[] = [$this->translator->trans('Sum'), $totalHours, $config['price'], $totalPrice];
|
||||
$rows[] = ['Sum', $totalHours, $config['price'], $totalPrice];
|
||||
|
||||
return $rows;
|
||||
}
|
||||
|
||||
protected function dummyConfig() : array
|
||||
{
|
||||
return [
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace RprtCli\Utils\PdfExport;
|
||||
|
||||
/**
|
||||
* Handles exporting parsed csv data to pdf files.
|
||||
*/
|
||||
interface PdfExportInterface {
|
||||
|
||||
/**
|
||||
* Retrieves path to template file either from command option, configuration
|
||||
* or from uer input.
|
||||
*/
|
||||
public function getTemplatePath(): ?string;
|
||||
|
||||
public function setTemplatePath(string $path): void;
|
||||
|
||||
/**
|
||||
* Creates html table from parsed csv data.
|
||||
*
|
||||
* @param array $data
|
||||
* First line is header. The rest of them is table body.
|
||||
*/
|
||||
public function parsedDataToHtmlTable(array $data): ?string;
|
||||
|
||||
/**
|
||||
* Reads the template file and replaces token values.
|
||||
*/
|
||||
public function replaceTokensInTemplate(string $template_path, array $tokens): ?string;
|
||||
|
||||
/**
|
||||
* Creates and export file.
|
||||
*
|
||||
* @param string $html
|
||||
* Template file with tokens replaced.
|
||||
*
|
||||
* @return bool
|
||||
* True if export was successfull.
|
||||
*/
|
||||
public function pdfExport(string $html) : bool;
|
||||
|
||||
}
|
|
@ -0,0 +1,103 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace RprtCli\Utils\PdfExport;
|
||||
|
||||
use Exception;
|
||||
use RprtCli\Utils\Configuration\ConfigurationInterface;
|
||||
use Mpdf\Output\Destination;
|
||||
|
||||
class PdfExportService implements PdfExportInterface {
|
||||
|
||||
protected $templatePath;
|
||||
protected $output;
|
||||
|
||||
protected $config;
|
||||
|
||||
protected $mpdf;
|
||||
|
||||
public function __construct(ConfigurationInterface $config, $mpdf) {
|
||||
$this->config = $config;
|
||||
$this->mpdf = $mpdf;
|
||||
}
|
||||
|
||||
public function getTemplatePath(): ?string
|
||||
{
|
||||
if (!isset($this->templatePath)) {
|
||||
$template_path = $this->config->get('report.template_path', FALSE);
|
||||
if (!$template_path) {
|
||||
$template_path = readline('Enter template file path: ');
|
||||
}
|
||||
if (!file_exists($template_path)) {
|
||||
throw new Exception('Template file not found!');
|
||||
}
|
||||
$this->templatePath = $template_path;
|
||||
}
|
||||
return $this->templatePath;
|
||||
}
|
||||
|
||||
public function setTemplatePath(string $path): void {
|
||||
if (file_exists($path)) {
|
||||
$this->templatePath = $path;
|
||||
return;
|
||||
}
|
||||
throw new Exception('Template file not found!');
|
||||
}
|
||||
|
||||
public function parsedDataToHtmlTable(array $data): ?string {
|
||||
$table = '<table><thead><tr>';
|
||||
$header = array_shift($data);
|
||||
foreach ($header as $cell) {
|
||||
$table .= "<th>{$cell}</th>";
|
||||
}
|
||||
$table .= '</tr></thead><tbody>';
|
||||
foreach ($data as $row) {
|
||||
$cells = [];
|
||||
foreach ($row as $cell) {
|
||||
$cells[] = "<td>{$cell}</td>";
|
||||
}
|
||||
$rows[] = '<tr>' . implode($cells) . '</tr>';
|
||||
}
|
||||
$table .= implode('', $rows);
|
||||
$table .= '</tbody></table>';
|
||||
return $table;
|
||||
}
|
||||
|
||||
public function replaceTokensInTemplate(string $template_path, array $tokens): ?string
|
||||
{
|
||||
$template = file_get_contents($template_path);
|
||||
foreach ($tokens as $key => $value) {
|
||||
$template = str_replace("[[{$key}]]", $value, $template);
|
||||
}
|
||||
return $template;
|
||||
}
|
||||
|
||||
public function pdfExport(string $html, $output = null): bool {
|
||||
$this->mpdf->SetProtection(array('print'));
|
||||
$this->mpdf->SetTitle("Acme Trading Co. - Invoice");
|
||||
$this->mpdf->SetAuthor("Acme Trading Co.");
|
||||
$this->mpdf->SetDisplayMode('fullpage');
|
||||
|
||||
$this->mpdf->WriteHTML($html);
|
||||
|
||||
if (!$this->output) {
|
||||
$this->output = $this->config->get('report.output', NULL) ?? readline('Enter output file path: ');
|
||||
}
|
||||
$this->mpdf->Output($this->output, Destination::FILE);
|
||||
return file_exists($this->output);
|
||||
}
|
||||
|
||||
public function setOutput(string $path) {
|
||||
$this->output = $path;
|
||||
}
|
||||
|
||||
public function fromDataToPdf(array $data, array $tokens = []): bool {
|
||||
$template_path = $this->getTemplatePath();
|
||||
$tokens['table'] = $this->parsedDataToHtmlTable($data);
|
||||
$html = $this->replaceTokensInTemplate($template_path, $tokens);
|
||||
$success = $this->pdfExport($html);
|
||||
return $success;
|
||||
}
|
||||
|
||||
}
|
|
@ -12,6 +12,9 @@ use RprtCli\Utils\Configuration\ConfigurationInterface;
|
|||
class YoutrackService implements YoutrackInterface
|
||||
{
|
||||
|
||||
protected $ytToken;
|
||||
protected $ytBaseUrl;
|
||||
|
||||
protected $config;
|
||||
|
||||
protected $httpClient;
|
||||
|
@ -51,7 +54,7 @@ class YoutrackService implements YoutrackInterface
|
|||
{
|
||||
// --report option value should take precedence.
|
||||
// @TODO error handling.
|
||||
$yt_report_id = $this->config->get('youtrack.report_id');
|
||||
$yt_report_id = $this->config->get('tracking_service.youtrack.report_id');
|
||||
if (!$yt_report_id) {
|
||||
$yt_report_id = readline('Enter the report id: ');
|
||||
}
|
||||
|
@ -81,6 +84,9 @@ class YoutrackService implements YoutrackInterface
|
|||
}
|
||||
|
||||
protected function getYtToken(): string {
|
||||
if (isset($this->ytToken)) {
|
||||
return $this->ytToken;
|
||||
}
|
||||
$yt_token = $this->config->get('tracking_service.youtrack.auth_token', FALSE); //
|
||||
if (!$yt_token) {
|
||||
$yt_token = readline('Enter your youtrack authentication token: ');
|
||||
|
@ -88,9 +94,18 @@ class YoutrackService implements YoutrackInterface
|
|||
return $yt_token;
|
||||
}
|
||||
|
||||
public function setYtToken(string $token): void {
|
||||
$this->ytToken = $token;
|
||||
}
|
||||
|
||||
protected function getYtUrl(string $path = ''): ?string {
|
||||
$yt_base_url = $this->config->get('tracking_service.youtrack.base_url', FALSE);
|
||||
if (!$yt_base_url) {
|
||||
if (isset($this->ytBaseUrl)) {
|
||||
$yt_base_url = $this->ytBaseUrl;
|
||||
}
|
||||
else {
|
||||
$yt_base_url = $this->config->get('tracking_service.youtrack.base_url', FALSE);
|
||||
}
|
||||
if (empty($yt_base_url)) {
|
||||
$yt_base_url = readline('Enter base url for of the youtrack service: ');
|
||||
}
|
||||
if (!empty($path)) {
|
||||
|
@ -98,4 +113,8 @@ class YoutrackService implements YoutrackInterface
|
|||
}
|
||||
return $yt_base_url;
|
||||
}
|
||||
|
||||
public function setYtUrl(string $base_url) {
|
||||
$this->ytBaseUrl = $base_url;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue