RprtCli/app/src/Utils/CsvReport/ReportCsv.php

259 lines
8.5 KiB
PHP

<?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}
*/
public function getInvoiceData(string $filePath) : array
{
$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 = [
$config['name'] ?? $project,
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) {
// @TODO replace separators with constants for normal separating.
$rows[] = null;
$rows[] = ['Gesamt netto', number_format($totalHours, 2, ',', '.'), ' ', number_format($totalPrice, 2, ',', '.')];
$add_separator = FALSE;
}
foreach ($data as $invoice_element) {
if ($invoice_element instanceof ExpensesInterface) {
if (!isset($added_expenses)) {
// @TODO - separator 0: Make next line bold and centered.
$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;
}
public function generateReportTable(string $filePath) {
// ticket-id, ticket-name, time-spent
$data = $this->parseReportData($filePath);
[$previous, $time_sum, $project_time, $table, $all_projects] = [$data[0]['id'], 0, 0, [], []];
foreach ($data as $line) {
$project = explode('-', $line['id'])[0];
$previous_project = explode('-', $previous)[0];
$project_time += (float) $line['time'];
$table[] = array_values($line);
if ($project !== $previous_project) {
// When project changes, add a sum of time for that project.
$table[] = null;
$table[] = [null, $previous_project, null, $project_time/60];
$table[] = null;
$time_sum += (float) $project_time;
$project_time = 0;
$all_projects[] = $project;
}
$previous = $line['id'];
}
// Add sum for the last project.
$table[] = null;
$table[] = [null, $project, null, $project_time / 60];
$time_sum += (float) $project_time;
$all_projects[] = $project;
// Add a sum of time for whole day.
$table[] = null;
$table[] = [null, implode(', ', $all_projects), null, $time_sum/60];
return $table;
}
/**
* {@inheritdoc}
*/
protected function parseReportData(string $filePath): array
{
$output = [];
// @TODO replace with config service.
// $config = $this->dummyConfig()['projects'];
if ($file = fopen($filePath, 'r')) {
while (($line = fgetcsv($file)) !== false) {
if (!is_numeric($line[4])) {
// Skip header at least.
continue;
}
// @TODO validate line
$output[] = [
'id' => $line[1],
'name' => substr($line[2], 0, 80),
'time' => $line[4],
'estimation' => $line[3],
];
}
}
return $output;
}
/**
* Should be moved into test class.
*/
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
],
],
];
}
}