2022-04-30 14:55:18 +02:00
|
|
|
<?php
|
|
|
|
|
|
|
|
declare(strict_types=1);
|
|
|
|
|
|
|
|
namespace RprtCli\Utils\CsvReport;
|
|
|
|
|
|
|
|
use RprtCli\Utils\Configuration\ConfigurationInterface;
|
|
|
|
use RprtCli\ValueObjects\ExpensesInterface;
|
|
|
|
use RprtCli\ValueObjects\WorkInvoiceElement;
|
|
|
|
use RprtCli\ValueObjects\WorkInvoiceElementInterface;
|
|
|
|
|
|
|
|
use function array_key_first;
|
|
|
|
use function array_keys;
|
|
|
|
use function fgetcsv;
|
|
|
|
use function fopen;
|
|
|
|
use function preg_match;
|
|
|
|
use function reset;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Creates a report of projects and hours.
|
|
|
|
*
|
|
|
|
* Uses value objects instead of arrays.
|
|
|
|
*/
|
|
|
|
class ReportCsv implements ReportCsvInterface
|
|
|
|
{
|
|
|
|
/**
|
|
|
|
* A configuration service.
|
|
|
|
*
|
|
|
|
* @var ConfigurationInterface
|
|
|
|
*/
|
|
|
|
protected $configurationService;
|
|
|
|
|
|
|
|
public function __construct(ConfigurationInterface $config)
|
|
|
|
{
|
|
|
|
$this->configurationService = $config;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* {@inheritdoc}
|
|
|
|
*/
|
2022-05-11 19:43:05 +02:00
|
|
|
public function getInvoiceData(string $filePath) : array
|
2022-04-30 14:55:18 +02:00
|
|
|
{
|
|
|
|
$output = [];
|
|
|
|
// @TODO replace with config service.
|
|
|
|
// $config = $this->dummyConfig()['projects'];
|
|
|
|
$config = $this->configurationService->get('projects');
|
|
|
|
foreach (array_keys($config) as $key) {
|
|
|
|
$output[$key] = 0;
|
|
|
|
}
|
|
|
|
if ($file = fopen($filePath, 'r')) {
|
|
|
|
while (($line = fgetcsv($file)) !== false) {
|
|
|
|
$parsed = $this->parseCsvFile($line);
|
|
|
|
// $key = reset(array_keys($parsed));
|
|
|
|
$key = array_key_first($parsed);
|
|
|
|
if (isset($output[$key])) {
|
|
|
|
$output[$key] += (float) reset($parsed);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
$report_data = [];
|
|
|
|
foreach ($output as $project => $hours) {
|
|
|
|
$report_data[] = new WorkInvoiceElement($project, $hours);
|
|
|
|
}
|
|
|
|
return $report_data;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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
|
|
|
|
{
|
|
|
|
$config = $this->configurationService->get('projects');
|
|
|
|
foreach ($config as $key => $project) {
|
|
|
|
if (preg_match('/' . $project['pattern'] . '/', $rawData[1])) {
|
|
|
|
return [$key => $rawData[4]];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return [];
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Input is array of Work elements and expenses.
|
|
|
|
*/
|
|
|
|
public function generateTable(array $data): array {
|
|
|
|
[$rows, $totalHours, $totalPrice, $add_separator] = [[], 0, 0, FALSE];
|
|
|
|
$projectsConfig = $this->configurationService->get('projects');
|
|
|
|
// $header = $this->configurationService->get('export.labels', null);
|
|
|
|
$header = null;
|
|
|
|
if (is_array($header)) {
|
|
|
|
$rows[] = $header;
|
|
|
|
}
|
|
|
|
// First only list work invoice elements.
|
|
|
|
foreach ($data as $key => $invoice_element) {
|
|
|
|
if ($invoice_element instanceof WorkInvoiceElementInterface) {
|
|
|
|
$add_separator = TRUE;
|
|
|
|
$project = $invoice_element->getName();
|
|
|
|
$time = $invoice_element->getTime();
|
|
|
|
$config = $projectsConfig[$project];
|
|
|
|
$hours = $config['time_format'] == 'm' ? $time/60 : $time;
|
|
|
|
$price = $hours * (float) $config['price'];
|
|
|
|
$row = [
|
2022-05-02 13:42:41 +02:00
|
|
|
$config['name'] ?? $project,
|
2022-04-30 14:55:18 +02:00
|
|
|
number_format($hours, 2, ',', '.'),
|
|
|
|
number_format($config['price'], 2, ',', '.'),
|
|
|
|
number_format($price, 2, ',', '.'),
|
|
|
|
];
|
|
|
|
$totalHours += $hours;
|
|
|
|
$totalPrice += $price;
|
|
|
|
$rows[] = $row;
|
|
|
|
unset($data[$key]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if ($add_separator) {
|
2022-05-11 19:43:05 +02:00
|
|
|
// @TODO replace separators with constants for normal separating.
|
2022-04-30 14:55:18 +02:00
|
|
|
$rows[] = null;
|
|
|
|
$rows[] = ['Gesamt netto', number_format($totalHours, 2, ',', '.'), ' ', number_format($totalPrice, 2, ',', '.')];
|
|
|
|
$add_separator = FALSE;
|
|
|
|
}
|
2022-05-12 02:48:53 +02:00
|
|
|
if (empty($data)) {
|
|
|
|
$add_separator = TRUE;
|
|
|
|
}
|
2022-04-30 14:55:18 +02:00
|
|
|
foreach ($data as $invoice_element) {
|
|
|
|
if ($invoice_element instanceof ExpensesInterface) {
|
|
|
|
if (!isset($added_expenses)) {
|
2022-05-11 19:43:05 +02:00
|
|
|
// @TODO - separator 0: Make next line bold and centered.
|
2022-04-30 14:55:18 +02:00
|
|
|
$rows[] = 0;
|
|
|
|
$rows[] = [
|
|
|
|
null,
|
|
|
|
null,
|
|
|
|
'Kosten',
|
|
|
|
'EUR',
|
|
|
|
];
|
|
|
|
// Don't make next line bold. See RprtCli\PdfExport\PdfExportService::parsedDataToHtml.
|
|
|
|
$rows[] = FALSE;
|
|
|
|
$added_expenses = TRUE;
|
|
|
|
}
|
|
|
|
$add_separator = TRUE;
|
|
|
|
$rows[] = [
|
|
|
|
null,
|
|
|
|
null,
|
|
|
|
$invoice_element->getName(),
|
|
|
|
number_format($invoice_element->getValue(), 2, ',', '.'),
|
|
|
|
];
|
|
|
|
$totalPrice += $invoice_element->getValue();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if ($add_separator) {
|
|
|
|
$rows[] = null;
|
|
|
|
}
|
|
|
|
$rows[] = [null, null, 'Gessamt brutto', number_format($totalPrice, 2, ',', '.')];
|
|
|
|
return $rows;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* {@inheritdoc}
|
|
|
|
*/
|
|
|
|
public function arangeDataForDefaultPdfExport(array $data) :array {
|
|
|
|
$rows = $this->generateTable($data);
|
|
|
|
$header = $this->configurationService->get('export.labels', null);
|
|
|
|
array_unshift($rows, $header);
|
|
|
|
foreach ($rows as $key => $row) {
|
|
|
|
if (!$row) {
|
|
|
|
unset($data[$key]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return $rows;
|
|
|
|
}
|
|
|
|
|
2022-05-11 19:43:05 +02:00
|
|
|
public function generateReportTable(string $filePath) {
|
|
|
|
// ticket-id, ticket-name, time-spent
|
|
|
|
$data = $this->parseReportData($filePath);
|
2022-05-14 22:08:54 +02:00
|
|
|
if (empty($data)) {
|
|
|
|
return [];
|
|
|
|
}
|
2022-05-11 19:43:05 +02:00
|
|
|
[$previous, $time_sum, $project_time, $table, $all_projects] = [$data[0]['id'], 0, 0, [], []];
|
|
|
|
foreach ($data as $line) {
|
|
|
|
$project = explode('-', $line['id'])[0];
|
|
|
|
$previous_project = explode('-', $previous)[0];
|
|
|
|
if ($project !== $previous_project) {
|
|
|
|
// When project changes, add a sum of time for that project.
|
|
|
|
$table[] = null;
|
|
|
|
$table[] = [null, $previous_project, null, $project_time/60];
|
|
|
|
$table[] = null;
|
|
|
|
$time_sum += (float) $project_time;
|
|
|
|
$project_time = 0;
|
|
|
|
$all_projects[] = $project;
|
|
|
|
}
|
2022-05-12 02:48:53 +02:00
|
|
|
$project_time += (float) $line['time'];
|
2022-05-11 19:43:05 +02:00
|
|
|
$previous = $line['id'];
|
2022-05-12 02:48:53 +02:00
|
|
|
$table[] = array_values($line);
|
2022-05-11 19:43:05 +02:00
|
|
|
}
|
|
|
|
// Add sum for the last project.
|
|
|
|
$table[] = null;
|
|
|
|
$table[] = [null, $project, null, $project_time / 60];
|
|
|
|
$time_sum += (float) $project_time;
|
|
|
|
$all_projects[] = $project;
|
|
|
|
// Add a sum of time for whole day.
|
|
|
|
$table[] = null;
|
|
|
|
$table[] = [null, implode(', ', $all_projects), null, $time_sum/60];
|
|
|
|
return $table;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* {@inheritdoc}
|
|
|
|
*/
|
|
|
|
protected function parseReportData(string $filePath): array
|
|
|
|
{
|
|
|
|
$output = [];
|
|
|
|
// @TODO replace with config service.
|
|
|
|
// $config = $this->dummyConfig()['projects'];
|
|
|
|
if ($file = fopen($filePath, 'r')) {
|
|
|
|
while (($line = fgetcsv($file)) !== false) {
|
|
|
|
if (!is_numeric($line[4])) {
|
|
|
|
// Skip header at least.
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
// @TODO validate line
|
|
|
|
$output[] = [
|
|
|
|
'id' => $line[1],
|
2022-05-12 02:48:53 +02:00
|
|
|
'name' => substr($line[2], 0, 60),
|
2022-05-11 19:43:05 +02:00
|
|
|
'time' => $line[4],
|
|
|
|
'estimation' => $line[3],
|
|
|
|
];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return $output;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2022-04-30 14:55:18 +02:00
|
|
|
/**
|
|
|
|
* Should be moved into test class.
|
|
|
|
*/
|
|
|
|
protected function dummyConfig() : array
|
|
|
|
{
|
|
|
|
return [
|
|
|
|
'projects' => [
|
|
|
|
'LDP' => [
|
|
|
|
'name' => 'lupus.digital',
|
|
|
|
'pattern' => 'LDP-[0-9]+',
|
|
|
|
'price' => 25,
|
|
|
|
// optional specify columns
|
|
|
|
],
|
|
|
|
'WV' => [
|
|
|
|
'name' => 'Wirtschaftsverlag',
|
|
|
|
'pattern' => 'WV-[0-9]+',
|
|
|
|
'price' => 25,
|
|
|
|
// optional specify columns
|
|
|
|
],
|
|
|
|
'Other' => [
|
|
|
|
'name' => 'Other projects',
|
|
|
|
'pattern' => '(?!.\bLDP\b)(?!.\bWV\b)',
|
|
|
|
'price' => 25,
|
|
|
|
// optional specify columns
|
|
|
|
],
|
|
|
|
],
|
|
|
|
];
|
|
|
|
}
|
|
|
|
}
|