Release 2.29 (#5325)
* bump composer packages * fixes #5329 quotes for ANSI_MODE * improve year selection * improve year selection via dropdown * added range selector in month-picker * fix week number if week starts with sunday * fix first day of month in URL * predefined options for week chooser * z-index issue with sticky table header * replace duplicated translations * add logout button to allow user switch without having to re-login in "remember me" login * new flag to detect if invoice entry is a fixed rate * improve export column lengths
This commit is contained in:
@@ -17,11 +17,11 @@ final class Constants
|
||||
/**
|
||||
* The current release version
|
||||
*/
|
||||
public const VERSION = '2.28.0';
|
||||
public const VERSION = '2.29.0';
|
||||
/**
|
||||
* The current release: major * 10000 + minor * 100 + patch
|
||||
*/
|
||||
public const VERSION_ID = 22800;
|
||||
public const VERSION_ID = 22900;
|
||||
/**
|
||||
* The software name
|
||||
*/
|
||||
|
||||
@@ -61,11 +61,9 @@ final class ReportUsersYearController extends AbstractController
|
||||
$dateTimeFactory = $this->getDateTimeFactory();
|
||||
|
||||
$defaultDate = $dateTimeFactory->createStartOfYear();
|
||||
$isFinancialYear = false;
|
||||
|
||||
if (null !== ($financialYear = $systemConfiguration->getFinancialYearStart())) {
|
||||
$defaultDate = $this->getDateTimeFactory()->createStartOfFinancialYear($financialYear);
|
||||
$isFinancialYear = true;
|
||||
}
|
||||
|
||||
$values = new YearlyUserList();
|
||||
@@ -74,7 +72,6 @@ final class ReportUsersYearController extends AbstractController
|
||||
$form = $this->createFormForGetRequest(YearlyUserListForm::class, $values, [
|
||||
'timezone' => $dateTimeFactory->getTimezone()->getName(),
|
||||
'start_date' => $values->getDate(),
|
||||
'show_range' => $isFinancialYear,
|
||||
]);
|
||||
|
||||
$form->submit($request->query->all(), false);
|
||||
|
||||
@@ -294,6 +294,8 @@ final class SystemConfigurationController extends AbstractController
|
||||
|
||||
return [
|
||||
(new SystemConfigurationModel('timesheet'))
|
||||
->setTranslation('time_tracking')
|
||||
->setTranslationDomain('messages')
|
||||
->setConfiguration([
|
||||
(new Configuration('timesheet.mode'))
|
||||
->setType(TrackingModeType::class)
|
||||
|
||||
@@ -61,11 +61,11 @@ final class CustomerSubscriber extends AbstractActionsSubscriber
|
||||
}
|
||||
|
||||
if ($this->isGranted('view_project') || $this->isGranted('view_teamlead_project') || $this->isGranted('view_team_project')) {
|
||||
$event->addActionToSubmenu('filter', 'project', ['title' => 'project.filter', 'url' => $this->path('admin_project', ['customers[]' => $customer->getId()])]);
|
||||
$event->addActionToSubmenu('filter', 'project', ['title' => 'projects', 'url' => $this->path('admin_project', ['customers[]' => $customer->getId()])]);
|
||||
}
|
||||
|
||||
if ($this->isGranted('view_activity')) {
|
||||
$event->addActionToSubmenu('filter', 'activity', ['title' => 'activity.filter', 'url' => $this->path('admin_activity', ['customers[]' => $customer->getId()])]);
|
||||
$event->addActionToSubmenu('filter', 'activity', ['title' => 'activities', 'url' => $this->path('admin_activity', ['customers[]' => $customer->getId()])]);
|
||||
}
|
||||
|
||||
if ($this->isGranted('view_other_timesheet')) {
|
||||
|
||||
@@ -51,7 +51,7 @@ final class ProjectSubscriber extends AbstractActionsSubscriber
|
||||
}
|
||||
|
||||
if ($this->isGranted('view_activity')) {
|
||||
$event->addActionToSubmenu('filter', 'activity', ['title' => 'activity.filter', 'url' => $this->path('admin_activity', ['customers[]' => $customer->getId(), 'projects[]' => $project->getId()])]);
|
||||
$event->addActionToSubmenu('filter', 'activity', ['title' => 'activities', 'url' => $this->path('admin_activity', ['customers[]' => $customer->getId(), 'projects[]' => $project->getId()])]);
|
||||
}
|
||||
|
||||
if ($this->isGranted('view_other_timesheet')) {
|
||||
|
||||
@@ -35,7 +35,13 @@ final class UserSubscriber extends AbstractActionsSubscriber
|
||||
{
|
||||
$payload = $event->getPayload();
|
||||
|
||||
if (($user = $payload['user']) === null || !$user instanceof User || $user->getId() === null) {
|
||||
if (!\array_key_exists('user', $payload)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$user = $payload['user'];
|
||||
|
||||
if (!$user instanceof User || $user->getId() === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -19,12 +19,16 @@ use App\Repository\Query\TimesheetQuery;
|
||||
use OpenSpout\Writer\CSV\Options;
|
||||
use OpenSpout\Writer\CSV\Writer;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
final class CsvRenderer implements RendererInterface, TimesheetExportInterface
|
||||
{
|
||||
use ExportTrait;
|
||||
|
||||
public function __construct(private readonly SpreadsheetRenderer $spreadsheetRenderer)
|
||||
public function __construct(
|
||||
private readonly SpreadsheetRenderer $spreadsheetRenderer,
|
||||
private readonly TranslatorInterface $translator
|
||||
)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -63,7 +67,7 @@ final class CsvRenderer implements RendererInterface, TimesheetExportInterface
|
||||
$options = new Options();
|
||||
$options->SHOULD_ADD_BOM = false;
|
||||
|
||||
$spreadsheet = new SpoutSpreadsheet(new Writer($options));
|
||||
$spreadsheet = new SpoutSpreadsheet(new Writer($options), $this->translator);
|
||||
$spreadsheet->open($filename);
|
||||
|
||||
$this->spreadsheetRenderer->registerFormatter('date', new DateStringFormatter());
|
||||
|
||||
@@ -27,6 +27,7 @@ use App\Export\Package\CellFormatter\RateFormatter;
|
||||
use App\Export\Package\CellFormatter\TextFormatter;
|
||||
use App\Export\Package\CellFormatter\TimeFormatter;
|
||||
use App\Export\Package\Column;
|
||||
use App\Export\Package\ColumnWidth;
|
||||
use App\Export\Package\SpreadsheetPackage;
|
||||
use App\Repository\Query\ActivityQuery;
|
||||
use App\Repository\Query\CustomerQuery;
|
||||
@@ -34,7 +35,6 @@ use App\Repository\Query\ProjectQuery;
|
||||
use App\Repository\Query\TimesheetQuery;
|
||||
use Psr\EventDispatcher\EventDispatcherInterface;
|
||||
use Symfony\Bundle\SecurityBundle\Security;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
/**
|
||||
* @internal means no BC promise whatsoever!
|
||||
@@ -47,7 +47,6 @@ final class SpreadsheetRenderer
|
||||
private array $formatter = [];
|
||||
|
||||
public function __construct(
|
||||
protected TranslatorInterface $translator,
|
||||
protected EventDispatcherInterface $dispatcher,
|
||||
protected Security $voter
|
||||
) {
|
||||
@@ -83,12 +82,7 @@ final class SpreadsheetRenderer
|
||||
public function writeSpreadsheet(SpreadsheetPackage $spreadsheetPackage, array $exportItems, TimesheetQuery $query): void
|
||||
{
|
||||
$columns = $this->getColumns($query);
|
||||
|
||||
$headerRow = [];
|
||||
foreach ($columns as $column) {
|
||||
$headerRow[] = $this->translator->trans($column->getHeader());
|
||||
}
|
||||
$spreadsheetPackage->setHeader($headerRow);
|
||||
$spreadsheetPackage->setColumns($columns);
|
||||
|
||||
$currentRow = 1;
|
||||
foreach ($exportItems as $exportItem) {
|
||||
@@ -150,26 +144,26 @@ final class SpreadsheetRenderer
|
||||
$columns = [];
|
||||
|
||||
$columns[] = (new Column('date', $this->getFormatter('date')))->withExtractor(fn (ExportableItem $exportableItem) => $exportableItem->getBegin());
|
||||
$columns[] = (new Column('begin', $this->getFormatter('time')))->withExtractor(fn (ExportableItem $exportableItem) => $exportableItem->getBegin());
|
||||
$columns[] = (new Column('end', $this->getFormatter('time')))->withExtractor(fn (ExportableItem $exportableItem) => $exportableItem->getEnd());
|
||||
$columns[] = (new Column('duration', $this->getFormatter('duration')))->withExtractor(fn (ExportableItem $exportableItem) => $exportableItem->getDuration());
|
||||
$columns[] = (new Column('begin', $this->getFormatter('time')))->withExtractor(fn (ExportableItem $exportableItem) => $exportableItem->getBegin())->withColumnWidth(ColumnWidth::SMALL);
|
||||
$columns[] = (new Column('end', $this->getFormatter('time')))->withExtractor(fn (ExportableItem $exportableItem) => $exportableItem->getEnd())->withColumnWidth(ColumnWidth::SMALL);
|
||||
$columns[] = (new Column('duration', $this->getFormatter('duration')))->withExtractor(fn (ExportableItem $exportableItem) => $exportableItem->getDuration())->withColumnWidth(ColumnWidth::SMALL);
|
||||
|
||||
if ($showRates) {
|
||||
$columns[] = (new Column('currency', $this->getFormatter('default')))->withExtractor(fn (ExportableItem $exportableItem) => $exportableItem->getProject()?->getCustomer()?->getCurrency());
|
||||
$columns[] = (new Column('currency', $this->getFormatter('default')))->withExtractor(fn (ExportableItem $exportableItem) => $exportableItem->getProject()?->getCustomer()?->getCurrency())->withColumnWidth(ColumnWidth::SMALL);
|
||||
$columns[] = (new Column('rate', new RateFormatter()))->withExtractor(fn (ExportableItem $exportableItem) => $exportableItem->getRate());
|
||||
$columns[] = (new Column('internalRate', new RateFormatter()))->withExtractor(fn (ExportableItem $exportableItem) => $exportableItem->getInternalRate());
|
||||
$columns[] = (new Column('hourlyRate', new RateFormatter()))->withExtractor(fn (ExportableItem $exportableItem) => $exportableItem->getHourlyRate());
|
||||
$columns[] = (new Column('fixedRate', new RateFormatter()))->withExtractor(fn (ExportableItem $exportableItem) => $exportableItem->getFixedRate());
|
||||
}
|
||||
|
||||
$columns[] = (new Column('username', $this->getFormatter('default')))->withExtractor(fn (ExportableItem $exportableItem) => $exportableItem->getUser()?->getDisplayName());
|
||||
$columns[] = (new Column('username', $this->getFormatter('default')))->withExtractor(fn (ExportableItem $exportableItem) => $exportableItem->getUser()?->getDisplayName())->withColumnWidth(ColumnWidth::MEDIUM);
|
||||
$columns[] = (new Column('account_number', $this->getFormatter('default')))->withExtractor(fn (ExportableItem $exportableItem) => $exportableItem->getUser()?->getAccountNumber());
|
||||
$columns[] = (new Column('customer', $this->getFormatter('default')))->withExtractor(fn (ExportableItem $exportableItem) => $exportableItem->getProject()?->getCustomer()?->getName());
|
||||
$columns[] = (new Column('project', $this->getFormatter('default')))->withExtractor(fn (ExportableItem $exportableItem) => $exportableItem->getProject()?->getName());
|
||||
$columns[] = (new Column('activity', $this->getFormatter('default')))->withExtractor(fn (ExportableItem $exportableItem) => $exportableItem->getActivity()?->getName());
|
||||
$columns[] = (new Column('description', new TextFormatter(true)))->withExtractor(fn (ExportableItem $exportableItem) => $exportableItem->getDescription());
|
||||
$columns[] = (new Column('customer', $this->getFormatter('default')))->withExtractor(fn (ExportableItem $exportableItem) => $exportableItem->getProject()?->getCustomer()?->getName())->withColumnWidth(ColumnWidth::MEDIUM);
|
||||
$columns[] = (new Column('project', $this->getFormatter('default')))->withExtractor(fn (ExportableItem $exportableItem) => $exportableItem->getProject()?->getName())->withColumnWidth(ColumnWidth::MEDIUM);
|
||||
$columns[] = (new Column('activity', $this->getFormatter('default')))->withExtractor(fn (ExportableItem $exportableItem) => $exportableItem->getActivity()?->getName())->withColumnWidth(ColumnWidth::MEDIUM);
|
||||
$columns[] = (new Column('description', new TextFormatter(true)))->withExtractor(fn (ExportableItem $exportableItem) => $exportableItem->getDescription())->withColumnWidth(ColumnWidth::LARGE);
|
||||
//$columns[] = (new Column('exported', new BooleanFormatter()))->withExtractor(fn(ExportableItem $exportableItem) => $exportableItem->isExported());
|
||||
$columns[] = (new Column('billable', new BooleanFormatter()))->withExtractor(fn (ExportableItem $exportableItem) => $exportableItem->isBillable());
|
||||
$columns[] = (new Column('billable', new BooleanFormatter()))->withExtractor(fn (ExportableItem $exportableItem) => $exportableItem->isBillable())->withColumnWidth(ColumnWidth::SMALL);
|
||||
$columns[] = (new Column('tags', new ArrayFormatter()))->withExtractor(fn (ExportableItem $exportableItem) => $exportableItem->getTagsAsArray());
|
||||
$columns[] = (new Column('type', $this->getFormatter('default')))->withExtractor(fn (ExportableItem $exportableItem) => $exportableItem->getType());
|
||||
$columns[] = (new Column('category', $this->getFormatter('default')))->withExtractor(fn (ExportableItem $exportableItem) => $exportableItem->getCategory());
|
||||
|
||||
@@ -17,12 +17,16 @@ use App\Export\TimesheetExportInterface;
|
||||
use App\Repository\Query\TimesheetQuery;
|
||||
use OpenSpout\Writer\XLSX\Writer;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
final class XlsxRenderer implements RendererInterface, TimesheetExportInterface
|
||||
{
|
||||
use ExportTrait;
|
||||
|
||||
public function __construct(private readonly SpreadsheetRenderer $spreadsheetRenderer)
|
||||
public function __construct(
|
||||
private readonly SpreadsheetRenderer $spreadsheetRenderer,
|
||||
private readonly TranslatorInterface $translator,
|
||||
)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -58,7 +62,7 @@ final class XlsxRenderer implements RendererInterface, TimesheetExportInterface
|
||||
throw new \Exception('Could not open temporary file');
|
||||
}
|
||||
|
||||
$spreadsheet = new SpoutSpreadsheet(new Writer());
|
||||
$spreadsheet = new SpoutSpreadsheet(new Writer(), $this->translator);
|
||||
$spreadsheet->open($filename);
|
||||
|
||||
$this->spreadsheetRenderer->writeSpreadsheet($spreadsheet, $exportItems, $query);
|
||||
|
||||
@@ -16,6 +16,7 @@ class Column
|
||||
{
|
||||
private ?string $header = null;
|
||||
private \Closure|null $extractor = null;
|
||||
private ColumnWidth $columnWidth = ColumnWidth::DEFAULT;
|
||||
|
||||
public function __construct(private readonly string $name, private readonly CellFormatterInterface $formatter)
|
||||
{
|
||||
@@ -33,6 +34,18 @@ class Column
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function withColumnWidth(ColumnWidth $columnWidth): Column
|
||||
{
|
||||
$this->columnWidth = $columnWidth;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getColumnWidth(): ColumnWidth
|
||||
{
|
||||
return $this->columnWidth;
|
||||
}
|
||||
|
||||
public function withExtractor(\Closure $extractor): Column
|
||||
{
|
||||
$this->extractor = $extractor;
|
||||
|
||||
19
src/Export/Package/ColumnWidth.php
Normal file
19
src/Export/Package/ColumnWidth.php
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Kimai time-tracking app.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace App\Export\Package;
|
||||
|
||||
enum ColumnWidth
|
||||
{
|
||||
case EXTRA_SMALL;
|
||||
case SMALL;
|
||||
case DEFAULT;
|
||||
case MEDIUM;
|
||||
case LARGE;
|
||||
}
|
||||
@@ -15,6 +15,7 @@ use PhpOffice\PhpSpreadsheet\Spreadsheet;
|
||||
use PhpOffice\PhpSpreadsheet\Style\Alignment;
|
||||
use PhpOffice\PhpSpreadsheet\Style\Border;
|
||||
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
final class PhpOfficeSpreadsheet implements SpreadsheetPackage
|
||||
{
|
||||
@@ -23,7 +24,7 @@ final class PhpOfficeSpreadsheet implements SpreadsheetPackage
|
||||
private int $currentRow = 1;
|
||||
private ?string $filename = null;
|
||||
|
||||
public function __construct()
|
||||
public function __construct(private readonly TranslatorInterface $translator)
|
||||
{
|
||||
$this->spreadsheet = new Spreadsheet();
|
||||
$this->worksheet = $this->spreadsheet->getActiveSheet();
|
||||
@@ -82,9 +83,9 @@ final class PhpOfficeSpreadsheet implements SpreadsheetPackage
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string> $columns
|
||||
* @param array<Column> $columns
|
||||
*/
|
||||
public function setHeader(array $columns): void
|
||||
public function setColumns(array $columns): void
|
||||
{
|
||||
if ($this->worksheet === null) {
|
||||
throw new \Exception('Cannot re-use spreadsheet after calling save()');
|
||||
@@ -92,8 +93,9 @@ final class PhpOfficeSpreadsheet implements SpreadsheetPackage
|
||||
|
||||
$counter = 1;
|
||||
foreach ($columns as $column) {
|
||||
$title = $this->translator->trans($column->getHeader());
|
||||
$pos = CellAddress::fromColumnAndRow($counter, 1);
|
||||
$this->worksheet->setCellValue($pos, $column);
|
||||
$this->worksheet->setCellValue($pos, $title);
|
||||
$style = $this->worksheet->getStyle($pos);
|
||||
$style->getBorders()->getBottom()->setBorderStyle(Border::BORDER_THIN);
|
||||
$style->getFont()->setBold(true);
|
||||
|
||||
@@ -17,28 +17,40 @@ use OpenSpout\Common\Entity\Style\BorderPart;
|
||||
use OpenSpout\Common\Entity\Style\Color;
|
||||
use OpenSpout\Common\Entity\Style\Style;
|
||||
use OpenSpout\Writer\AbstractWriterMultiSheets;
|
||||
use OpenSpout\Writer\CSV\Writer;
|
||||
use OpenSpout\Writer\AutoFilter;
|
||||
use OpenSpout\Writer\CSV\Writer as CSVWriter;
|
||||
use OpenSpout\Writer\WriterInterface;
|
||||
use OpenSpout\Writer\XLSX\Entity\SheetView;
|
||||
use OpenSpout\Writer\XLSX\Writer as XLSXWriter;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
class SpoutSpreadsheet implements SpreadsheetPackage
|
||||
{
|
||||
private Style $dateStyle;
|
||||
|
||||
public function __construct(private readonly WriterInterface $writer)
|
||||
public function __construct(
|
||||
private readonly WriterInterface $writer,
|
||||
private readonly TranslatorInterface $translator
|
||||
)
|
||||
{
|
||||
$this->writer->setCreator(Constants::SOFTWARE);
|
||||
$this->dateStyle = (new Style())->setFormat('yyyy-mm-dd');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string> $columns
|
||||
* @param array<Column> $columns
|
||||
*/
|
||||
public function setHeader(array $columns): void
|
||||
public function setColumns(array $columns): void
|
||||
{
|
||||
$columnCounter = \count($columns);
|
||||
if ($columnCounter === 0) {
|
||||
throw new \InvalidArgumentException('At least one column is required');
|
||||
}
|
||||
|
||||
$tmp = [];
|
||||
foreach ($columns as $column) {
|
||||
$tmp[] = Cell::fromValue($column);
|
||||
$title = $this->translator->trans($column->getHeader());
|
||||
$tmp[] = Cell::fromValue($title);
|
||||
}
|
||||
|
||||
$style = new Style();
|
||||
@@ -48,6 +60,24 @@ class SpoutSpreadsheet implements SpreadsheetPackage
|
||||
$style->setBorder(new Border(new BorderPart(Border::BOTTOM, Color::BLACK, Border::WIDTH_THIN, Border::STYLE_SOLID)));
|
||||
$style->setFontBold();
|
||||
|
||||
if ($this->writer instanceof XLSXWriter) {
|
||||
$autoFilter = new AutoFilter(0, 1, $columnCounter - 1, 1);
|
||||
$this->writer->getCurrentSheet()->setAutoFilter($autoFilter);
|
||||
|
||||
$options = $this->writer->getOptions();
|
||||
|
||||
$i = 1;
|
||||
foreach ($columns as $column) {
|
||||
$width = match($column->getColumnWidth()) {
|
||||
ColumnWidth::SMALL => 10,
|
||||
ColumnWidth::MEDIUM => 30,
|
||||
ColumnWidth::LARGE => 50,
|
||||
default => 15,
|
||||
};
|
||||
$options->setColumnWidth($width, $i++);
|
||||
}
|
||||
}
|
||||
|
||||
$this->writer->addRow(new Row($tmp, $style));
|
||||
}
|
||||
|
||||
@@ -62,7 +92,7 @@ class SpoutSpreadsheet implements SpreadsheetPackage
|
||||
$style->setShouldShrinkToFit(true);
|
||||
|
||||
if (\array_key_exists('totals', $options) && $options['totals'] === true) {
|
||||
if ($this->writer instanceof Writer) {
|
||||
if ($this->writer instanceof CSVWriter) {
|
||||
return;
|
||||
}
|
||||
$style->setBorder(new Border(new BorderPart(Border::TOP, Color::BLACK, Border::WIDTH_THIN, Border::STYLE_SOLID)));
|
||||
|
||||
@@ -19,9 +19,9 @@ interface SpreadsheetPackage
|
||||
public function save(): void;
|
||||
|
||||
/**
|
||||
* @param array<string> $columns
|
||||
* @param array<Column> $columns
|
||||
*/
|
||||
public function setHeader(array $columns): void;
|
||||
public function setColumns(array $columns): void;
|
||||
|
||||
/**
|
||||
* @param array<int, mixed> $columns
|
||||
|
||||
@@ -28,7 +28,7 @@ final class Configuration
|
||||
*/
|
||||
private array $constraints = [];
|
||||
|
||||
public function __construct(private string $name)
|
||||
public function __construct(private readonly string $name)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -28,22 +28,41 @@ final class MonthPickerType extends AbstractType
|
||||
'widget' => 'single_text',
|
||||
'html5' => false,
|
||||
'format' => DateType::HTML5_FORMAT,
|
||||
'start_date' => new \DateTime(),
|
||||
'start_date' => new \DateTimeImmutable(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function buildView(FormView $view, FormInterface $form, array $options): void
|
||||
{
|
||||
/** @var \DateTime|null $date */
|
||||
if (!$options['start_date'] instanceof \DateTimeInterface) {
|
||||
throw new \InvalidArgumentException('Start date needs to be a DateTime object');
|
||||
}
|
||||
|
||||
/** @var \DateTimeInterface|null $date */
|
||||
$date = $form->getData();
|
||||
|
||||
if (null === $date) {
|
||||
/** @var \DateTimeImmutable $date */
|
||||
$date = $options['start_date'];
|
||||
}
|
||||
|
||||
$date = \DateTimeImmutable::createFromInterface($date);
|
||||
$start = \DateTimeImmutable::createFromInterface($options['start_date']);
|
||||
$start = $start->setDate((int) $start->format('Y'), (int) $start->format('m'), 1);
|
||||
|
||||
$range = [];
|
||||
$end = $start->modify('-2 year');
|
||||
$end = $end->setDate((int) $end->format('Y'), 1, 1);
|
||||
$i = 1;
|
||||
while ($i++ < 36 && $end <= $start) {
|
||||
$range[] = $start;
|
||||
$start = $start->modify('- 1 month');
|
||||
}
|
||||
|
||||
$view->vars['month'] = $date;
|
||||
$view->vars['previousMonth'] = (clone $date)->modify('-1 month');
|
||||
$view->vars['nextMonth'] = (clone $date)->modify('+1 month');
|
||||
$view->vars['range'] = $range;
|
||||
$view->vars['previousMonth'] = $date->modify('-1 month');
|
||||
$view->vars['nextMonth'] = $date->modify('+1 month');
|
||||
}
|
||||
|
||||
public function getParent(): string
|
||||
|
||||
@@ -34,6 +34,10 @@ final class WeekPickerType extends AbstractType
|
||||
|
||||
public function buildView(FormView $view, FormInterface $form, array $options): void
|
||||
{
|
||||
if (!$options['start_date'] instanceof \DateTimeInterface) {
|
||||
throw new \InvalidArgumentException('Start date needs to be a DateTime object');
|
||||
}
|
||||
|
||||
/** @var \DateTimeInterface|null $date */
|
||||
$date = $form->getData();
|
||||
|
||||
@@ -42,11 +46,31 @@ final class WeekPickerType extends AbstractType
|
||||
$date = $options['start_date'];
|
||||
}
|
||||
|
||||
$date = \DateTime::createFromInterface($date);
|
||||
$date = \DateTimeImmutable::createFromInterface($date);
|
||||
$start = \DateTimeImmutable::createFromInterface($options['start_date']);
|
||||
|
||||
$view->vars['week'] = $date;
|
||||
$view->vars['previousWeek'] = (clone $date)->modify('-1 week');
|
||||
$view->vars['nextWeek'] = (clone $date)->modify('+1 week');
|
||||
$week = $date;
|
||||
if ($date->format('N') === '7') {
|
||||
$week = $date->modify('+1 day');
|
||||
}
|
||||
if ($start->format('N') === '7') {
|
||||
$start = $start->modify('+1 day');
|
||||
}
|
||||
|
||||
$range = [];
|
||||
$end = $start->modify('-1 year');
|
||||
$end = $end->setDate((int) $end->format('Y'), 1, 1);
|
||||
$i = 1;
|
||||
while ($i++ < 106 && $end <= $start) {
|
||||
$range[] = $start;
|
||||
$start = $start->modify('- 1 week');
|
||||
}
|
||||
|
||||
$view->vars['range'] = $range;
|
||||
$view->vars['weekNumber'] = $week->format('W');
|
||||
$view->vars['week'] = $week;
|
||||
$view->vars['previousWeek'] = $week->modify('-1 week');
|
||||
$view->vars['nextWeek'] = $week->modify('+1 week');
|
||||
}
|
||||
|
||||
public function getParent(): string
|
||||
|
||||
@@ -29,7 +29,6 @@ final class YearPickerType extends AbstractType
|
||||
'html5' => false,
|
||||
'format' => DateType::HTML5_FORMAT,
|
||||
'start_date' => new \DateTimeImmutable(),
|
||||
'show_range' => false,
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -43,12 +42,19 @@ final class YearPickerType extends AbstractType
|
||||
$date = $options['start_date'];
|
||||
}
|
||||
|
||||
$date = \DateTime::createFromInterface($date);
|
||||
$date = \DateTimeImmutable::createFromInterface($date);
|
||||
|
||||
$range = [];
|
||||
$start = new \DateTimeImmutable();
|
||||
$start = $start->setDate((int) $start->format('Y'), (int) $date->format('m'), (int) $date->format('d'));
|
||||
for ($i = 0; $i < 6; $i++) {
|
||||
$range[] = $start->modify('-' . $i . ' year');
|
||||
}
|
||||
|
||||
$view->vars['year'] = $date;
|
||||
$view->vars['show_range'] = $options['show_range'];
|
||||
$view->vars['previousYear'] = (clone $date)->modify('-1 year');
|
||||
$view->vars['nextYear'] = (clone $date)->modify('+1 year');
|
||||
$view->vars['range'] = $range;
|
||||
$view->vars['previousYear'] = $date->modify('-1 year');
|
||||
$view->vars['nextYear'] = $date->modify('+1 year');
|
||||
}
|
||||
|
||||
public function getParent(): string
|
||||
|
||||
@@ -66,6 +66,7 @@ final class InvoiceItemDefaultHydrator implements InvoiceItemHydrator
|
||||
'entry.rate_internal' => $formatter->getFormattedMoney($internalRate, $currency),
|
||||
'entry.rate_internal_nc' => $formatter->getFormattedMoney($internalRate, $currency, false),
|
||||
'entry.rate_internal_plain' => $internalRate,
|
||||
'entry.rate_fixed' => ($item->isFixedRate() ? $item->getFixedRate() : null),
|
||||
'entry.total' => $formatter->getFormattedMoney($rate, $currency),
|
||||
'entry.total_nc' => $formatter->getFormattedMoney($rate, $currency, false),
|
||||
'entry.total_plain' => $rate,
|
||||
|
||||
@@ -28,7 +28,6 @@ final class YearlyUserListForm extends AbstractType
|
||||
'model_timezone' => $options['timezone'],
|
||||
'view_timezone' => $options['timezone'],
|
||||
'start_date' => $options['start_date'],
|
||||
'show_range' => $options['show_range'],
|
||||
]);
|
||||
$builder->add('team', TeamType::class, [
|
||||
'multiple' => false,
|
||||
@@ -51,7 +50,6 @@ final class YearlyUserListForm extends AbstractType
|
||||
'start_date' => new \DateTime(),
|
||||
'csrf_protection' => false,
|
||||
'method' => 'GET',
|
||||
'show_range' => false,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user