Release 2.0.29 (#4178)
- show button title if delete is used in page actions - fix invoice due date depends on invoice date, replace DateTime with DateTimeI… - lowercase all font names in PDFs, otherwise they fail loading - hide empty fieldset (work-contract page) - activate contract_other_profile by default for admin and super-admin - deactivate rule to check "maximum duration of entries" by default - allow to deactivate presets in DateRange Picker (for Devs)
This commit is contained in:
@@ -17,11 +17,11 @@ class Constants
|
||||
/**
|
||||
* The current release version
|
||||
*/
|
||||
public const VERSION = '2.0.28';
|
||||
public const VERSION = '2.0.29';
|
||||
/**
|
||||
* The current release: major * 10000 + minor * 100 + patch
|
||||
*/
|
||||
public const VERSION_ID = 20028;
|
||||
public const VERSION_ID = 20029;
|
||||
/**
|
||||
* The software name
|
||||
*/
|
||||
|
||||
@@ -305,7 +305,7 @@ final class Configuration implements ConfigurationInterface
|
||||
->defaultValue(0)
|
||||
->end()
|
||||
->integerNode('long_running_duration')
|
||||
->defaultValue(600)
|
||||
->defaultValue(0)
|
||||
->end()
|
||||
->booleanNode('require_activity')
|
||||
->defaultTrue()
|
||||
|
||||
@@ -208,7 +208,7 @@ class Invoice implements EntityWithMetaFields
|
||||
$this->currency = $model->getCurrency();
|
||||
|
||||
$createdAt = $model->getInvoiceDate();
|
||||
$this->createdAt = $createdAt;
|
||||
$this->createdAt = \DateTime::createFromInterface($createdAt);
|
||||
$this->timezone = $createdAt->getTimezone()->getName();
|
||||
|
||||
$this->dueDays = $template->getDueDays();
|
||||
|
||||
@@ -175,9 +175,9 @@ class PageActionsEvent extends ThemeEvent
|
||||
public function addDelete(string $url, bool $remoteConfirm = true): void
|
||||
{
|
||||
if ($remoteConfirm) {
|
||||
$this->addAction('trash', ['url' => $url, 'class' => 'modal-ajax-form text-red', 'translation_domain' => 'actions']);
|
||||
$this->addAction('trash', ['url' => $url, 'class' => 'modal-ajax-form text-red', 'translation_domain' => 'actions', 'title' => 'trash']);
|
||||
} else {
|
||||
$this->addAction('trash', ['url' => $url, 'class' => 'confirmation-link text-red', 'attr' => ['data-question' => 'confirm.delete'], 'translation_domain' => 'actions']);
|
||||
$this->addAction('trash', ['url' => $url, 'class' => 'confirmation-link text-red', 'attr' => ['data-question' => 'confirm.delete'], 'translation_domain' => 'actions', 'title' => 'trash']);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -49,6 +49,7 @@ final class DateRangeType extends AbstractType
|
||||
'format' => $formFormat,
|
||||
'separator' => self::DATE_SPACER,
|
||||
'allow_empty' => true,
|
||||
'with_presets' => true,
|
||||
'attr' => [
|
||||
'pattern' => $pattern . self::DATE_SPACER . $pattern
|
||||
],
|
||||
@@ -61,26 +62,28 @@ final class DateRangeType extends AbstractType
|
||||
$user = $options['user'];
|
||||
$factory = DateTimeFactory::createByUser($user);
|
||||
|
||||
$ranges = [
|
||||
'daterangepicker.allTime' => [null, null],
|
||||
'daterangepicker.today' => [$factory->createDateTime('00:00:00'), $factory->createDateTime('23:59:59')],
|
||||
'daterangepicker.yesterday' => [$factory->createDateTime('-1 day 00:00:00'), $factory->createDateTime('-1 day 23:59:59')],
|
||||
'daterangepicker.thisWeek' => [$factory->getStartOfWeek(), $factory->getEndOfWeek()],
|
||||
'daterangepicker.lastWeek' => [$factory->getStartOfWeek('-1 week'), $factory->getEndOfWeek('-1 week')],
|
||||
'daterangepicker.thisMonth' => [$factory->getStartOfMonth(), $factory->getEndOfMonth()],
|
||||
'daterangepicker.lastMonth' => [$factory->getStartOfLastMonth(), $factory->getEndOfLastMonth()],
|
||||
'daterangepicker.thisYearUntilNow' => [$factory->createStartOfYear(), $factory->createDateTime('23:59:59')],
|
||||
];
|
||||
if ($options['with_presets']) {
|
||||
$ranges = [
|
||||
'daterangepicker.allTime' => [null, null],
|
||||
'daterangepicker.today' => [$factory->createDateTime('00:00:00'), $factory->createDateTime('23:59:59')],
|
||||
'daterangepicker.yesterday' => [$factory->createDateTime('-1 day 00:00:00'), $factory->createDateTime('-1 day 23:59:59')],
|
||||
'daterangepicker.thisWeek' => [$factory->getStartOfWeek(), $factory->getEndOfWeek()],
|
||||
'daterangepicker.lastWeek' => [$factory->getStartOfWeek('-1 week'), $factory->getEndOfWeek('-1 week')],
|
||||
'daterangepicker.thisMonth' => [$factory->getStartOfMonth(), $factory->getEndOfMonth()],
|
||||
'daterangepicker.lastMonth' => [$factory->getStartOfLastMonth(), $factory->getEndOfLastMonth()],
|
||||
'daterangepicker.thisYearUntilNow' => [$factory->createStartOfYear(), $factory->createDateTime('23:59:59')],
|
||||
];
|
||||
|
||||
$thisYear = (int) $factory->createStartOfYear()->format('Y');
|
||||
for ($i = 0; $i < 3; $i++) {
|
||||
$year = $thisYear - $i;
|
||||
$ranges[$year] = [$year . '-01-01', $year . '-12-31'];
|
||||
$thisYear = (int) $factory->createStartOfYear()->format('Y');
|
||||
for ($i = 0; $i < 3; $i++) {
|
||||
$year = $thisYear - $i;
|
||||
$ranges[$year] = [$year . '-01-01', $year . '-12-31'];
|
||||
}
|
||||
|
||||
$view->vars['ranges'] = $ranges;
|
||||
$view->vars['rangeFormat'] = $options['format'];
|
||||
}
|
||||
|
||||
$view->vars['ranges'] = $ranges;
|
||||
$view->vars['rangeFormat'] = $options['format'];
|
||||
|
||||
$view->vars['attr'] = array_merge($view->vars['attr'], [
|
||||
'data-separator' => $options['separator'],
|
||||
]);
|
||||
|
||||
@@ -32,17 +32,17 @@ final class DefaultInvoiceFormatter implements InvoiceFormatter
|
||||
return $this->formatter;
|
||||
}
|
||||
|
||||
public function getFormattedDateTime(\DateTime $date): string
|
||||
public function getFormattedDateTime(\DateTimeInterface $date): string
|
||||
{
|
||||
return (string) $this->getFormatter()->dateShort($date);
|
||||
}
|
||||
|
||||
public function getFormattedTime(\DateTime $date): string
|
||||
public function getFormattedTime(\DateTimeInterface $date): string
|
||||
{
|
||||
return (string) $this->getFormatter()->time($date);
|
||||
}
|
||||
|
||||
public function getFormattedMonthName(\DateTime $date): string
|
||||
public function getFormattedMonthName(\DateTimeInterface $date): string
|
||||
{
|
||||
return $this->getFormatter()->monthName($date);
|
||||
}
|
||||
|
||||
@@ -71,8 +71,6 @@ final class InvoiceModelDefaultHydrator implements InvoiceModelHydrator
|
||||
$query = $model->getQuery();
|
||||
if ($query !== null) {
|
||||
$begin = $query->getBegin();
|
||||
$end = $query->getEnd();
|
||||
|
||||
if ($begin !== null) {
|
||||
$values = array_merge($values, [
|
||||
'query.day' => $begin->format('d'),
|
||||
@@ -88,6 +86,12 @@ final class InvoiceModelDefaultHydrator implements InvoiceModelHydrator
|
||||
'query.begin_month' => $formatter->getFormattedMonthName($begin),
|
||||
'query.begin_month_number' => $begin->format('m'),
|
||||
'query.begin_year' => $begin->format('Y'),
|
||||
]);
|
||||
}
|
||||
|
||||
$end = $query->getEnd();
|
||||
if ($end !== null) {
|
||||
$values = array_merge($values, [
|
||||
'query.end' => $formatter->getFormattedDateTime($end),
|
||||
// since 1.9
|
||||
'query.end_day' => $end->format('d'),
|
||||
@@ -97,7 +101,6 @@ final class InvoiceModelDefaultHydrator implements InvoiceModelHydrator
|
||||
'query.end_month_number' => $end->format('m'),
|
||||
// since 1.9
|
||||
'query.end_year' => $end->format('Y'),
|
||||
// since 1.9
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
namespace App\Invoice;
|
||||
|
||||
use DateTime;
|
||||
use DateTimeInterface;
|
||||
|
||||
/**
|
||||
* @internal this is subject to change
|
||||
@@ -20,15 +20,15 @@ interface InvoiceFormatter
|
||||
|
||||
public function setLocale(string $locale): void;
|
||||
|
||||
public function getFormattedDateTime(DateTime $date): string;
|
||||
public function getFormattedDateTime(DateTimeInterface $date): string;
|
||||
|
||||
public function getFormattedTime(DateTime $date): string;
|
||||
public function getFormattedTime(DateTimeInterface $date): string;
|
||||
|
||||
public function getFormattedAmount(float $amount): string;
|
||||
|
||||
public function getFormattedMoney(float $amount, ?string $currency, bool $withCurrency = true): string;
|
||||
|
||||
public function getFormattedMonthName(DateTime $date): string;
|
||||
public function getFormattedMonthName(DateTimeInterface $date): string;
|
||||
|
||||
public function getFormattedDuration(int $seconds): string;
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ final class InvoiceModel
|
||||
private ?InvoiceTemplate $template = null;
|
||||
private ?CalculatorInterface $calculator = null;
|
||||
private ?NumberGeneratorInterface $generator = null;
|
||||
private \DateTime $invoiceDate;
|
||||
private \DateTimeInterface $invoiceDate;
|
||||
private ?User $user = null;
|
||||
private InvoiceFormatter $formatter;
|
||||
/**
|
||||
@@ -58,7 +58,7 @@ final class InvoiceModel
|
||||
*/
|
||||
public function __construct(InvoiceFormatter $formatter, CustomerStatisticService $customerStatistic, ProjectStatisticService $projectStatistic, ActivityStatisticService $activityStatistic)
|
||||
{
|
||||
$this->invoiceDate = new \DateTime();
|
||||
$this->invoiceDate = new \DateTimeImmutable();
|
||||
$this->formatter = $formatter;
|
||||
$this->addModelHydrator(new InvoiceModelDefaultHydrator());
|
||||
$this->addModelHydrator(new InvoiceModelCustomerHydrator($customerStatistic));
|
||||
@@ -73,11 +73,9 @@ final class InvoiceModel
|
||||
return $this->query;
|
||||
}
|
||||
|
||||
public function setQuery(InvoiceQuery $query): InvoiceModel
|
||||
public function setQuery(InvoiceQuery $query): void
|
||||
{
|
||||
$this->query = $query;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -124,11 +122,9 @@ final class InvoiceModel
|
||||
return $this->template;
|
||||
}
|
||||
|
||||
public function setTemplate(InvoiceTemplate $template): InvoiceModel
|
||||
public function setTemplate(InvoiceTemplate $template): void
|
||||
{
|
||||
$this->template = $template;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getCustomer(): ?Customer
|
||||
@@ -136,32 +132,34 @@ final class InvoiceModel
|
||||
return $this->customer;
|
||||
}
|
||||
|
||||
public function setCustomer(?Customer $customer): InvoiceModel
|
||||
public function setCustomer(Customer $customer): void
|
||||
{
|
||||
$this->customer = $customer;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getDueDate(): ?\DateTime
|
||||
/**
|
||||
* Requires the template and invoice date to be set
|
||||
*/
|
||||
public function getDueDate(): \DateTimeInterface
|
||||
{
|
||||
if (null === $this->getTemplate()) {
|
||||
return null;
|
||||
$date = \DateTimeImmutable::createFromInterface($this->getInvoiceDate());
|
||||
|
||||
$dueDays = 14;
|
||||
if ($this->getTemplate() !== null) {
|
||||
$dueDays = $this->getTemplate()->getDueDays();
|
||||
}
|
||||
|
||||
return new \DateTime('+' . $this->getTemplate()->getDueDays() . ' days');
|
||||
return $date->add(new \DateInterval('P' . $dueDays . 'D'));
|
||||
}
|
||||
|
||||
public function getInvoiceDate(): \DateTime
|
||||
public function getInvoiceDate(): \DateTimeInterface
|
||||
{
|
||||
return $this->invoiceDate;
|
||||
}
|
||||
|
||||
public function setInvoiceDate(\DateTime $date): InvoiceModel
|
||||
public function setInvoiceDate(\DateTimeInterface $date): void
|
||||
{
|
||||
$this->invoiceDate = $date;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getInvoiceNumber(): string
|
||||
|
||||
@@ -11,7 +11,10 @@ namespace App\Invoice;
|
||||
|
||||
use App\Activity\ActivityStatisticService;
|
||||
use App\Customer\CustomerStatisticService;
|
||||
use App\Entity\Customer;
|
||||
use App\Entity\InvoiceTemplate;
|
||||
use App\Project\ProjectStatisticService;
|
||||
use App\Repository\Query\InvoiceQuery;
|
||||
|
||||
final class InvoiceModelFactory
|
||||
{
|
||||
@@ -22,8 +25,14 @@ final class InvoiceModelFactory
|
||||
) {
|
||||
}
|
||||
|
||||
public function createModel(InvoiceFormatter $formatter): InvoiceModel
|
||||
public function createModel(InvoiceFormatter $formatter, Customer $customer, InvoiceTemplate $template, InvoiceQuery $query): InvoiceModel
|
||||
{
|
||||
return new InvoiceModel($formatter, $this->customerStatisticService, $this->projectStatisticService, $this->activityStatisticService);
|
||||
$model = new InvoiceModel($formatter, $this->customerStatisticService, $this->projectStatisticService, $this->activityStatisticService);
|
||||
|
||||
$model->setCustomer($customer);
|
||||
$model->setTemplate($template);
|
||||
$model->setQuery($query);
|
||||
|
||||
return $model;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -410,12 +410,12 @@ final class ServiceInvoice
|
||||
|
||||
$formatter = new DefaultInvoiceFormatter($this->formatter, $template->getLanguage());
|
||||
|
||||
$model = $this->invoiceModelFactory->createModel($formatter);
|
||||
$model
|
||||
->setCustomer($customer)
|
||||
->setTemplate($template)
|
||||
->setQuery($query)
|
||||
;
|
||||
$model = $this->invoiceModelFactory->createModel(
|
||||
$formatter,
|
||||
$customer,
|
||||
$template,
|
||||
$query
|
||||
);
|
||||
|
||||
if ($query->getInvoiceDate() !== null) {
|
||||
$model->setInvoiceDate($query->getInvoiceDate());
|
||||
|
||||
@@ -128,8 +128,14 @@ final class MPdfConverter implements HtmlToPdfConverter
|
||||
$defaultFontConfig = (new FontVariables())->getDefaults();
|
||||
$fontData = $defaultFontConfig['fontdata'];
|
||||
|
||||
// lowercase all font names, otherwise they cannot be loaded
|
||||
// see https://github.com/kimai/www.kimai.org/issues/280
|
||||
if (\array_key_exists('fonts', $options)) {
|
||||
$fontData = array_merge($fontData, $options['fonts']);
|
||||
$fonts = [];
|
||||
foreach ($options['fonts'] as $name => $values) {
|
||||
$fonts[strtolower($name)] = $values;
|
||||
}
|
||||
$fontData = array_merge($fontData, $fonts);
|
||||
}
|
||||
|
||||
return $fontData;
|
||||
|
||||
@@ -91,24 +91,27 @@ class InvoiceRepository extends EntityRepository
|
||||
return $result['counter'];
|
||||
}
|
||||
|
||||
public function getCounterForDay(\DateTime $date, ?Customer $customer = null, ?User $user = null): int
|
||||
public function getCounterForDay(\DateTimeInterface $date, ?Customer $customer = null, ?User $user = null): int
|
||||
{
|
||||
$date = \DateTime::createFromInterface($date);
|
||||
$start = (clone $date)->setTime(0, 0, 0);
|
||||
$end = (clone $date)->setTime(23, 59, 59);
|
||||
|
||||
return $this->getCounterFor($start, $end, $customer, $user);
|
||||
}
|
||||
|
||||
public function getCounterForMonth(\DateTime $date, ?Customer $customer = null, ?User $user = null): int
|
||||
public function getCounterForMonth(\DateTimeInterface $date, ?Customer $customer = null, ?User $user = null): int
|
||||
{
|
||||
$date = \DateTime::createFromInterface($date);
|
||||
$start = (clone $date)->setDate((int) $date->format('Y'), (int) $date->format('n'), 1)->setTime(0, 0, 0);
|
||||
$end = (clone $date)->setDate((int) $date->format('Y'), (int) $date->format('n'), (int) $date->format('t'))->setTime(23, 59, 59);
|
||||
|
||||
return $this->getCounterFor($start, $end, $customer, $user);
|
||||
}
|
||||
|
||||
public function getCounterForYear(\DateTime $date, ?Customer $customer = null, ?User $user = null): int
|
||||
public function getCounterForYear(\DateTimeInterface $date, ?Customer $customer = null, ?User $user = null): int
|
||||
{
|
||||
$date = \DateTime::createFromInterface($date);
|
||||
$start = (clone $date)->setDate((int) $date->format('Y'), 1, 1)->setTime(0, 0, 0);
|
||||
$end = (clone $date)->setDate((int) $date->format('Y'), 12, 31)->setTime(23, 59, 59);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user