2021-04-04 22:41:15 +02:00
< ? php
2021-09-20 01:08:42 +02:00
declare ( strict_types = 1 );
2022-05-11 19:43:05 +02:00
// src/Commands/InvoiceCommand.php;
2021-04-04 22:41:15 +02:00
namespace RprtCli\Commands ;
2021-04-05 17:20:59 +02:00
use RprtCli\Utils\Configuration\ConfigurationInterface ;
2022-04-30 14:55:18 +02:00
use RprtCli\Utils\CsvReport\ReportCsvInterface ;
2021-10-02 03:52:07 +02:00
use RprtCli\Utils\Mailer\MailerInterface ;
2021-09-21 01:13:15 +02:00
use RprtCli\Utils\PdfExport\PdfExportInterface ;
2021-09-20 01:08:42 +02:00
use RprtCli\Utils\TimeTrackingServices\YoutrackInterface ;
2022-04-30 14:55:18 +02:00
use RprtCli\ValueObjects\Expenses ;
use RprtCli\ValueObjects\WorkInvoiceElement ;
2021-04-04 22:41:15 +02:00
use Symfony\Component\Console\Command\Command ;
use Symfony\Component\Console\Helper\Table ;
use Symfony\Component\Console\Helper\TableSeparator ;
2022-04-30 14:55:18 +02:00
use Symfony\Component\Console\Helper\TableCell ;
2021-09-20 01:08:42 +02:00
use Symfony\Component\Console\Input\InputInterface ;
2021-04-05 16:23:06 +02:00
use Symfony\Component\Console\Input\InputOption ;
2021-09-20 01:08:42 +02:00
use Symfony\Component\Console\Output\OutputInterface ;
// use Symfony\Contracts\Translation\TranslatorInterface;
2021-04-04 22:41:15 +02:00
2021-09-20 01:08:42 +02:00
use function var_dump ;
2021-04-04 22:41:15 +02:00
2021-09-20 01:08:42 +02:00
/**
2022-05-11 19:43:05 +02:00
* Main file - invoice command .
2021-09-20 01:08:42 +02:00
*/
2022-05-11 19:43:05 +02:00
class InvoiceCommand extends Command
2021-09-20 01:08:42 +02:00
{
protected $csv ;
2021-04-05 17:20:59 +02:00
2021-09-20 01:08:42 +02:00
protected $configuration ;
2021-04-04 22:41:15 +02:00
2021-09-20 01:08:42 +02:00
protected $youtrack ;
2021-04-04 22:41:15 +02:00
2021-09-21 01:13:15 +02:00
protected $pdfExport ;
2022-04-30 14:55:18 +02:00
const TYPE_WORK = 1 ;
const TYPE_EXPENSE = 2 ;
2021-09-20 01:08:42 +02:00
public function __construct (
2022-04-30 14:55:18 +02:00
ReportCsvInterface $csv ,
2021-09-20 01:08:42 +02:00
ConfigurationInterface $configuration ,
YoutrackInterface $youtrack ,
2021-09-21 01:13:15 +02:00
PdfExportInterface $pdf_export ,
2021-10-02 03:52:07 +02:00
MailerInterface $mailer ,
2021-09-20 01:08:42 +02:00
? string $name = null
) {
$this -> csv = $csv ;
$this -> configuration = $configuration ;
$this -> youtrack = $youtrack ;
2021-09-21 01:13:15 +02:00
$this -> pdfExport = $pdf_export ;
2021-10-02 03:52:07 +02:00
$this -> mailer = $mailer ;
2021-09-20 01:08:42 +02:00
parent :: __construct ( $name );
}
2021-04-05 16:23:06 +02:00
2021-09-20 01:08:42 +02:00
/**
* Get configuration .
*/
protected function configure () : void
{
2022-05-11 19:43:05 +02:00
$this -> setName ( 'invoice' );
$this -> setDescription ( 'Generate an invoice from (monthly) report' );
2021-09-20 01:08:42 +02:00
// @TODO $this->addUsage('');
2021-09-22 23:46:07 +02:00
// @TODO add sub options (config overrides)
2021-09-20 01:08:42 +02:00
$this -> addOption (
'file' ,
'f' ,
InputOption :: VALUE_REQUIRED ,
'Specify the input csv file to generate report from.'
);
$this -> addOption (
'youtrack' ,
'y' ,
InputOption :: VALUE_NONE ,
2022-05-02 13:42:41 +02:00
'Use youtrack api to get a report. If this option is used --file does not have any effect..'
2021-09-20 01:08:42 +02:00
);
$this -> addOption (
'pdf' ,
'p' ,
InputOption :: VALUE_NONE ,
'Create invoice pdf from template.'
);
$this -> addOption (
'test' ,
2022-05-11 19:43:05 +02:00
'' ,
2021-09-20 01:08:42 +02:00
InputOption :: VALUE_NONE ,
2022-04-30 14:55:18 +02:00
'Test login into youtrack service. Prints out your name.'
2021-09-20 01:08:42 +02:00
);
2021-09-22 23:46:07 +02:00
$this -> addOption (
'output' ,
'o' ,
InputOption :: VALUE_REQUIRED ,
'Provide output file path. This option overrides configuration.'
);
$this -> addOption (
'send' ,
's' ,
InputOption :: VALUE_NONE ,
'Send pdf export via email to recipient.'
);
2021-10-03 16:45:40 +02:00
$this -> addOption (
2022-05-02 13:42:41 +02:00
'recipients' ,
2022-05-15 11:55:50 +02:00
't' ,
2021-10-03 16:45:40 +02:00
InputOption :: VALUE_REQUIRED ,
'Comma separated list of recipients that should get the exported pdf.'
);
2022-04-30 14:55:18 +02:00
$this -> addOption (
'expenses' ,
'e' ,
InputOption :: VALUE_OPTIONAL ,
'List of additional expenses in format expense1=value1;expenses2=value2... or empty for interactive output.' ,
FALSE
);
$this -> addOption (
'custom' ,
'c' ,
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.' ,
FALSE
);
2022-05-02 13:42:41 +02:00
$this -> addOption (
'list-reports' ,
'l' ,
InputOption :: VALUE_NONE ,
'List my reports'
);
$this -> addOption (
'report' ,
2022-05-15 11:55:50 +02:00
'r' ,
2022-05-02 13:42:41 +02:00
InputOption :: VALUE_OPTIONAL ,
'Show time tracked for report.' ,
FALSE
);
2021-04-05 16:23:06 +02:00
}
2021-09-20 01:08:42 +02:00
protected function execute ( InputInterface $input , OutputInterface $output ) : int
{
if ( $input -> getOption ( 'test' )) {
$test = $this -> youtrack -> testYoutrackapi ();
$output -> writeln ( $test );
}
2022-05-02 13:42:41 +02:00
if ( $input -> getOption ( 'list-reports' )) {
$list = $this -> youtrack -> listReports ();
$output -> writeln ( var_export ( $list , TRUE ));
2022-05-15 13:04:50 +02:00
return Command :: SUCCESS ;
2022-05-02 13:42:41 +02:00
}
2022-05-15 12:37:21 +02:00
if ( $input -> hasParameterOption ( '--report' ) || $input -> hasParameterOption ( '-r' )) {
2022-05-02 13:42:41 +02:00
if ( $report = $input -> getOption ( 'report' )) {
$this -> youtrack -> setReportId ( $report );
}
else {
$reports = $this -> youtrack -> listReports ();
$count = 1 ;
foreach ( $reports as $id => $name ) {
$output -> writeln ( " [ { $count } ] { $name } ( { $id } ) " );
$count ++ ;
}
$report = readline ( 'Select id of the report: ' );
// Asume people are literate.
$this -> youtrack -> setReportId ( $report );
}
2022-05-15 12:37:21 +02:00
if ( $output -> isVerbose ()) {
$output -> writeln ( " Setting report: <info> { $report } </info>. " );
}
2022-05-02 13:42:41 +02:00
}
2021-09-21 01:13:15 +02:00
if ( $youtrack = $input -> getOption ( 'youtrack' )) {
$report_id = $this -> youtrack -> getReportId ();
2022-05-15 11:55:50 +02:00
$cache_clear_status = $this -> youtrack -> clearReportCache ( $report_id );
if ( $output -> isVerbose ()) {
2022-05-15 12:37:21 +02:00
$output -> writeln ( " Report <info> { $report_id } </info> cache cleared, status: { $cache_clear_status } " );
2022-05-15 11:55:50 +02:00
}
2021-09-21 01:13:15 +02:00
$file = $this -> youtrack -> downloadReport ( $report_id );
}
2022-05-02 13:42:41 +02:00
if ( $input -> hasParameterOption ( '--expenses' ) || $input -> hasParameterOption ( '-e' )) {
$expenses = $this -> getCustomWorkOrExpenses ( $input -> getOption ( 'expenses' ), self :: TYPE_EXPENSE );
2022-04-30 14:55:18 +02:00
}
2022-05-02 13:42:41 +02:00
if ( $input -> hasParameterOption ( '--custom' ) || $input -> hasParameterOption ( '-c' )) {
$custom = $this -> getCustomWorkOrExpenses ( $input -> getOption ( 'custom' ), self :: TYPE_WORK );
2022-04-30 14:55:18 +02:00
}
2021-09-21 01:13:15 +02:00
if ( $youtrack || $file = $input -> getOption ( 'file' )) {
// Youtrack can also provide a file name.
2022-05-15 11:55:50 +02:00
if ( $output -> isVerbose ()) {
$output -> writeln ( " Csv file downloaded to: <info> { $file } </info> " );
}
2022-05-11 19:43:05 +02:00
$data = $this -> csv -> getInvoiceData ( $file );
2022-04-30 14:55:18 +02:00
if ( ! empty ( $expenses )) {
$data = array_merge ( $data , $expenses );
}
// $table = $this->generateTable($output, $data);
$table = $this -> getTable ( $output , $data );
2021-09-20 01:08:42 +02:00
$table -> render ();
2021-09-21 01:13:15 +02:00
2022-04-30 14:55:18 +02:00
if ( $input -> getOption ( 'pdf' )) {
2021-10-02 03:52:07 +02:00
$nice_data = $this -> csv -> arangeDataForDefaultPdfExport ( $data );
2021-09-21 01:13:15 +02:00
// @TODO method gatherTokens();
2022-04-30 14:55:18 +02:00
if ( $out = $input -> getOption ( 'output' )) {
$this -> pdfExport -> setOutput ( $out );
2021-09-22 23:46:07 +02:00
}
2021-10-02 03:52:07 +02:00
$output_path = $this -> pdfExport -> fromDefaultDataToPdf ( $nice_data );
2022-04-30 14:55:18 +02:00
// Notify the user where the file was generated to.
2022-05-18 18:58:43 +02:00
$output -> writeln ( " The file was generated at <info> ${ output_path } </info>. " );
2021-09-21 01:13:15 +02:00
}
2021-09-22 23:46:07 +02:00
// return Command::SUCCESS;
}
2022-04-30 14:55:18 +02:00
if ( $input -> getOption ( 'send' ) && $output_path ) {
// @TODO If no output path print an error.
2021-09-22 23:46:07 +02:00
// Send email to configured address.
2021-10-03 16:45:40 +02:00
if ( $recipients = $input -> getOption ( 'send-to' )) {
$this -> mailer -> setRecipients ( explode ( ',' , $recipients ));
}
2021-10-02 03:52:07 +02:00
$this -> mailer -> sendDefaultMail ( $output_path );
2021-09-20 01:08:42 +02:00
}
2021-09-22 23:46:07 +02:00
// $this->dummyOutput($input, $output);
2021-09-20 01:08:42 +02:00
return Command :: SUCCESS ;
}
2021-04-04 22:41:15 +02:00
2022-04-30 14:55:18 +02:00
protected function getTable ( OutputInterface $output , array $data ) : Table {
$rows = $this -> csv -> generateTable ( $data );
$table = new Table ( $output );
$table -> setHeaders ([
'Project' , 'Hours' , 'Rate' , 'Price' ,
]);
foreach ( $rows as $key => $row ) {
if ( ! $row ) {
$rows [ $key ] = new TableSeparator ();
}
elseif ( is_array ( $row ) && is_null ( $row [ 1 ]) && is_null ( $row [ 0 ])) {
// Check which elements in array are null.
$rows [ $key ] = [ new TableCell ( $row [ 2 ], [ 'colspan' => 3 ]), $row [ 3 ]];
}
}
$table -> setRows ( $rows );
return $table ;
}
2021-04-08 19:23:19 +02:00
/**
* Create table from data that is already inline with configuration .
2022-04-30 14:55:18 +02:00
*
* @ deprecated
* This method was almost exact copy of CsvReport :: arangeDataForDefaultPdfExport
2021-04-08 19:23:19 +02:00
*/
2021-09-20 01:08:42 +02:00
protected function generateTable ( OutputInterface $output , array $data ) : Table
{
$table = new Table ( $output );
$table -> setHeaders ([
'Project' , 'Hours' , 'Rate' , 'Price' ,
]);
[ $rows , $totalHours , $totalPrice ] = [[], 0 , 0 ];
$projectsConfig = $this -> configuration -> get ( 'projects' );
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 ;
}
2022-04-30 14:55:18 +02:00
$price = $hours * ( float ) $config [ 'price' ];
2021-09-20 01:08:42 +02:00
$row = [
$config [ 'name' ],
$hours ,
$config [ 'price' ],
$hours * $config [ 'price' ],
];
$rows [] = $row ;
$totalHours += $hours ;
$totalPrice += $price ;
2022-04-30 14:55:18 +02:00
unset ( $data [ $name ]);
}
if ( ! empty ( $data )) {
foreach ( $data as $name => $value ) {
if ( strpos ( strtolower ( $name ), 'expanses' ) !== FALSE ) {
}
}
2021-09-20 01:08:42 +02:00
}
2022-04-30 14:55:18 +02:00
2021-09-20 01:08:42 +02:00
$rows [] = new TableSeparator ();
// @TODO Check rate in final result.
// $rows[] = [$this->translator->trans('Sum'), $totalHours, $config['price'], $totalPrice];
$rows [] = [ 'Sum' , $totalHours , $config [ 'price' ], $totalPrice ];
$table -> setRows ( $rows );
return $table ;
2021-04-05 16:23:06 +02:00
}
/**
* Dummy output for testing .
*/
2021-09-20 01:08:42 +02:00
protected function dummyOutput ( InputInterface $input , OutputInterface $output ) : void
{
// $txt = $this->translator->trans('From [start-date] to [end-date].', [], 'rprt', 'sl_SI');
// $output->writeln($txt);
$table = new Table ( $output );
$table -> setHeaders ([ 'Project' , 'Hours' , 'Price' ]);
$table -> setRows ([
[ 'LDP' , 100 , 2600 ],
[ 'WV' , 50 , 1300 ],
new TableSeparator (),
[ 'Zusamen' , 150 , 3900 ],
]);
// $table->setStyle('borderless');
$table -> render ();
}
2022-04-30 14:55:18 +02:00
/**
* Gets the expenses array .
*
* @ return Expenses []
*/
protected function getExpenses ( $expenses ) {
$output = [];
if ( is_string ( $expenses )) {
foreach ( explode ( ';' , $expenses ) as $expense ) {
[ $name , $value ] = explode ( '=' , $expense );
$output [] = new Expenses ( $name , ( float ) $value );
}
}
else {
$continue = TRUE ;
while ( $continue ) {
$name = readline ( 'Enter expenses name or leave empty to stop: ' );
$value = ( float ) readline ( 'Enter expenses value: ' );
if ( ! empty ( $name )) {
$output [] = new Expenses ( $name , $value );
}
else {
$continue = FALSE ;
}
}
}
return $output ;
}
protected function getCustomWorkOrExpenses ( $custom , $type ) {
$output = [];
if ( is_string ( $custom )) {
foreach ( explode ( ';' , $custom ) as $item ) {
[ $name , $value ] = explode ( '=' , $item );
$output [] = $this -> createInvoiceElement ( $name , ( float ) $value , $type );
}
} else {
$continue = TRUE ;
if ( $type == self :: TYPE_WORK ) {
$message_name = 'Enter project name or leave empty to stop: ' ;
$message_value = 'Enter time spent of project: ' ;
} elseif ( $type == self :: TYPE_EXPENSE ) {
$message_name = 'Enter expenses name or leave empty to stop: ' ;
$message_value = 'Enter expenses value: ' ;
}
while ( $continue ) {
$name = readline ( $message_name );
$value = ( float ) readline ( $message_value );
if ( ! empty ( $name )) {
$output [] = $this -> createInvoiceElement ( $name , $value , $type );
} else {
$continue = FALSE ;
}
}
}
return $output ;
}
protected function createInvoiceElement ( string $name , float $value , int $type ) {
if ( $type == self :: TYPE_WORK ) {
return new WorkInvoiceElement ( $name , ( float ) $value );
} elseif ( $type == self :: TYPE_EXPENSE ) {
return new Expenses ( $name , ( float ) $value );
}
throw new \Exception ( 'Unkown invoice element type.' );
}
2021-04-04 22:41:15 +02:00
}