Release 2.0.4 (#3883)
* fix column data truncated * calculate internal rate from user * show internal rate in timesheet listing * Fixed: responsivenss and size of report start page icons * fix: name display in dropdowns (and added tests) * translate reload button * fix invoice date might be in the past * fail safe customer name handling * translate invoice_date and invoice_date help * prevent URLs like start=null * prevent to reload select twice
This commit is contained in:
@@ -17,11 +17,11 @@ class Constants
|
||||
/**
|
||||
* The current release version
|
||||
*/
|
||||
public const VERSION = '2.0.3';
|
||||
public const VERSION = '2.0.4';
|
||||
/**
|
||||
* The current release: major * 10000 + minor * 100 + patch
|
||||
*/
|
||||
public const VERSION_ID = 20003;
|
||||
public const VERSION_ID = 20004;
|
||||
/**
|
||||
* The software name
|
||||
*/
|
||||
|
||||
@@ -241,7 +241,14 @@ abstract class AbstractController extends BaseAbstractController implements Serv
|
||||
return substr($query->getName(), 0, 50);
|
||||
}
|
||||
|
||||
protected function handleSearch(FormInterface $form, Request $request): bool
|
||||
/**
|
||||
* @param FormInterface $form
|
||||
* @param Request $request
|
||||
* @param array<string> $filterParams parameter names, which should not be saved (neither session, nor database)
|
||||
* @return bool
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function handleSearch(FormInterface $form, Request $request, array $filterParams = []): bool
|
||||
{
|
||||
$data = $form->getData();
|
||||
if (!($data instanceof BaseQuery)) {
|
||||
@@ -339,7 +346,7 @@ abstract class AbstractController extends BaseAbstractController implements Serv
|
||||
}
|
||||
|
||||
// these should NEVER be saved
|
||||
$filter = ['setDefaultQuery', 'removeDefaultQuery', 'performSearch'];
|
||||
$filter = array_merge(['setDefaultQuery', 'removeDefaultQuery', 'performSearch'], $filterParams);
|
||||
foreach ($filter as $name) {
|
||||
if (isset($params[$name])) {
|
||||
unset($params[$name]);
|
||||
|
||||
@@ -365,11 +365,15 @@ final class CustomerController extends AbstractController
|
||||
#[IsGranted('view', 'customer')]
|
||||
public function downloadVCard(Customer $customer): Response
|
||||
{
|
||||
if ($customer->getName() === null || \strlen($customer->getName()) === 0) {
|
||||
throw new \Exception('Customer name cannot be null');
|
||||
}
|
||||
|
||||
$vcard = new VCard();
|
||||
|
||||
$contact = $customer->getContact() ?? $customer->getName();
|
||||
$contact = $customer->getContact();
|
||||
if ($contact === null || \strlen($contact) === 0) {
|
||||
$contact = 'Unknown';
|
||||
$contact = $customer->getName();
|
||||
}
|
||||
|
||||
$contact = explode(' ', $contact);
|
||||
|
||||
@@ -81,10 +81,13 @@ final class InvoiceController extends AbstractController
|
||||
$query = $this->getDefaultQuery();
|
||||
|
||||
$form = $this->getToolbarForm($query);
|
||||
if ($this->handleSearch($form, $request)) {
|
||||
if ($this->handleSearch($form, $request, ['invoiceDate'])) {
|
||||
return $this->redirectToRoute('invoice');
|
||||
}
|
||||
|
||||
// this can be deleted in the future, but for now invalid bookmarks exists, which contain an old invoice date
|
||||
$query->setInvoiceDate($this->getDateTimeFactory()->createDateTime());
|
||||
|
||||
$models = [];
|
||||
$total = 0;
|
||||
$searched = false;
|
||||
@@ -116,6 +119,8 @@ final class InvoiceController extends AbstractController
|
||||
->add('template', InvoiceTemplateType::class)
|
||||
->add('invoiceDate', DatePickerType::class, [
|
||||
'required' => true,
|
||||
'label' => 'invoice_date',
|
||||
'help' => 'invoice_date.help'
|
||||
])
|
||||
->createView();
|
||||
}
|
||||
|
||||
@@ -88,6 +88,7 @@ abstract class TimesheetAbstractController extends AbstractController
|
||||
|
||||
if ($canSeeRate) {
|
||||
$table->addColumn('hourlyRate', ['class' => 'text-end d-none text-nowrap']);
|
||||
$table->addColumn('internalRate', ['class' => 'text-end text-nowrap']);
|
||||
$table->addColumn('rate', ['class' => 'text-end text-nowrap']);
|
||||
}
|
||||
|
||||
@@ -113,6 +114,7 @@ abstract class TimesheetAbstractController extends AbstractController
|
||||
$page->setActionName($this->getActionName());
|
||||
|
||||
return $this->render('timesheet/index.html.twig', [
|
||||
'view_rate' => $canSeeRate,
|
||||
'page_setup' => $page,
|
||||
'dataTable' => $table,
|
||||
'action_single' => $this->getActionNameSingle(),
|
||||
|
||||
@@ -102,7 +102,7 @@ class User implements UserInterface, EquatableInterface, ThemeUserInterface, Pas
|
||||
* URL to the user avatar, will be auto-generated if empty
|
||||
*/
|
||||
#[ORM\Column(name: 'avatar', type: 'string', length: 255, nullable: true)]
|
||||
#[Assert\Length(max: 255)]
|
||||
#[Assert\Length(max: 255, groups: ['Profile'])]
|
||||
#[Serializer\Expose]
|
||||
#[Serializer\Groups(['User_Entity'])]
|
||||
private ?string $avatar = null;
|
||||
|
||||
@@ -46,7 +46,7 @@ final class InvoiceDocumentSubscriber extends AbstractActionsSubscriber
|
||||
$event->addAction('download', ['url' => $this->path('admin_invoice_document_download', ['document' => $document->getId()])]);
|
||||
|
||||
if ($document->isTwig()) {
|
||||
$event->addAction('Reload', ['url' => $this->path('admin_invoice_document_reload', ['document' => $document->getId()])]);
|
||||
$event->addAction('reload', ['url' => $this->path('admin_invoice_document_reload', ['document' => $document->getId()])]);
|
||||
}
|
||||
|
||||
if (!$inUse) {
|
||||
|
||||
@@ -46,8 +46,13 @@ final class ActivityHelper
|
||||
$name = str_replace(self::PATTERN_NAME, $activity->getName(), $name);
|
||||
$name = str_replace(self::PATTERN_COMMENT, $activity->getComment() ?? '', $name);
|
||||
|
||||
$name = ltrim($name, self::SPACER);
|
||||
$name = rtrim($name, self::SPACER);
|
||||
while (str_starts_with($name, self::SPACER)) {
|
||||
$name = substr($name, \strlen(self::SPACER));
|
||||
}
|
||||
|
||||
while (str_ends_with($name, self::SPACER)) {
|
||||
$name = substr($name, 0, -\strlen(self::SPACER));
|
||||
}
|
||||
|
||||
if ($name === '' || $name === self::SPACER) {
|
||||
$name = $activity->getName();
|
||||
|
||||
@@ -50,8 +50,13 @@ final class CustomerHelper
|
||||
$name = str_replace(self::PATTERN_NUMBER, $customer->getNumber() ?? '', $name);
|
||||
$name = str_replace(self::PATTERN_COMPANY, $customer->getCompany() ?? '', $name);
|
||||
|
||||
$name = ltrim($name, self::SPACER);
|
||||
$name = rtrim($name, self::SPACER);
|
||||
while (str_starts_with($name, self::SPACER)) {
|
||||
$name = substr($name, \strlen(self::SPACER));
|
||||
}
|
||||
|
||||
while (str_ends_with($name, self::SPACER)) {
|
||||
$name = substr($name, 0, -\strlen(self::SPACER));
|
||||
}
|
||||
|
||||
if ($name === '' || $name === self::SPACER) {
|
||||
$name = $customer->getName();
|
||||
|
||||
@@ -12,9 +12,10 @@ namespace App\Form\Helper;
|
||||
use App\Configuration\LocaleService;
|
||||
use App\Configuration\SystemConfiguration;
|
||||
use App\Entity\Project;
|
||||
use Symfony\Contracts\Translation\LocaleAwareInterface;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
final class ProjectHelper
|
||||
final class ProjectHelper implements LocaleAwareInterface
|
||||
{
|
||||
public const PATTERN_NAME = '{name}';
|
||||
public const PATTERN_COMMENT = '{comment}';
|
||||
@@ -30,11 +31,22 @@ final class ProjectHelper
|
||||
private ?string $pattern = null;
|
||||
private bool $showStart = false;
|
||||
private bool $showEnd = false;
|
||||
private ?string $locale = null;
|
||||
|
||||
public function __construct(private SystemConfiguration $configuration, private LocaleService $localeService, private TranslatorInterface $translator)
|
||||
{
|
||||
}
|
||||
|
||||
public function getLocale(): string
|
||||
{
|
||||
return $this->locale ?? \Locale::getDefault();
|
||||
}
|
||||
|
||||
public function setLocale(string $locale): void
|
||||
{
|
||||
$this->locale = $locale;
|
||||
}
|
||||
|
||||
public function getChoicePattern(): string
|
||||
{
|
||||
if ($this->pattern === null) {
|
||||
@@ -56,26 +68,27 @@ final class ProjectHelper
|
||||
$name = $this->getChoicePattern();
|
||||
$name = str_replace(self::PATTERN_NAME, $project->getName(), $name);
|
||||
$name = str_replace(self::PATTERN_COMMENT, $project->getComment() ?? '', $name);
|
||||
$name = str_replace(self::PATTERN_CUSTOMER, $project->getCustomer()->getName() ?? '', $name);
|
||||
$name = str_replace(self::PATTERN_CUSTOMER, $project->getCustomer()?->getName() ?? '', $name);
|
||||
$name = str_replace(self::PATTERN_ORDERNUMBER, $project->getOrderNumber() ?? '', $name);
|
||||
|
||||
if ($this->dateFormatter === null) {
|
||||
$this->showStart = stripos($name, self::PATTERN_START) !== false;
|
||||
$this->showEnd = stripos($name, self::PATTERN_END) !== false;
|
||||
$locale = $this->getLocale();
|
||||
$this->dateFormatter = new \IntlDateFormatter(
|
||||
\Locale::getDefault(),
|
||||
$locale,
|
||||
\IntlDateFormatter::MEDIUM,
|
||||
\IntlDateFormatter::MEDIUM,
|
||||
date_default_timezone_get(),
|
||||
\IntlDateFormatter::GREGORIAN,
|
||||
$this->localeService->getDateFormat(\Locale::getDefault())
|
||||
$this->localeService->getDateFormat($locale)
|
||||
);
|
||||
}
|
||||
|
||||
if ($this->showStart) {
|
||||
$start = '';
|
||||
if ($project->getStart() !== null) {
|
||||
$start = $this->translator->trans('project_start') . ': ' . $this->dateFormatter->format($project->getStart()) . ' ';
|
||||
$start = $this->translator->trans('project_start') . ': ' . $this->dateFormatter->format($project->getStart());
|
||||
}
|
||||
$name = str_replace(self::PATTERN_START, $start, $name);
|
||||
}
|
||||
@@ -83,14 +96,18 @@ final class ProjectHelper
|
||||
if ($this->showEnd) {
|
||||
$end = '';
|
||||
if ($project->getEnd() !== null) {
|
||||
$end = ' ' . $this->translator->trans('project_end') . ': ' . $this->dateFormatter->format($project->getEnd());
|
||||
$end = $this->translator->trans('project_end') . ': ' . $this->dateFormatter->format($project->getEnd());
|
||||
}
|
||||
$name = str_replace(self::PATTERN_END, $end, $name);
|
||||
}
|
||||
|
||||
$name = ltrim($name, self::SPACER);
|
||||
$name = rtrim($name, self::SPACER);
|
||||
$name = str_replace('- ?-?', '', $name);
|
||||
while (str_starts_with($name, self::SPACER)) {
|
||||
$name = substr($name, \strlen(self::SPACER));
|
||||
}
|
||||
|
||||
while (str_ends_with($name, self::SPACER)) {
|
||||
$name = substr($name, 0, -\strlen(self::SPACER));
|
||||
}
|
||||
|
||||
if ($name === '' || $name === self::SPACER) {
|
||||
$name = $project->getName();
|
||||
|
||||
@@ -39,13 +39,11 @@ final class RateService implements RateServiceInterface
|
||||
if (null !== $rate) {
|
||||
if ($rate->isFixed()) {
|
||||
$fixedRate ??= $rate->getRate();
|
||||
$fixedInternalRate = $rate->getRate();
|
||||
if (null !== $rate->getInternalRate()) {
|
||||
$fixedInternalRate = $rate->getInternalRate();
|
||||
}
|
||||
} else {
|
||||
$hourlyRate ??= $rate->getRate();
|
||||
$internalRate = $rate->getRate();
|
||||
if (null !== $rate->getInternalRate()) {
|
||||
$internalRate = $rate->getInternalRate();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user