added multi-select for customer, project and activity (#1557)

This commit is contained in:
Kevin Papst
2020-03-16 14:06:26 +01:00
committed by GitHub
parent 6eeb01ad48
commit 4b1cfa5840
74 changed files with 1262 additions and 754 deletions

View File

@@ -68,7 +68,8 @@ class ActivityController extends BaseApiController
* @SWG\Items(ref="#/definitions/ActivityCollection")
* )
* )
* @Rest\QueryParam(name="project", requirements="\d+", strict=true, nullable=true, description="Project ID to filter activities. If none is provided, all activities will be returned.")
* @Rest\QueryParam(name="project", requirements="\d+", strict=true, nullable=true, description="Project ID to filter activities")
* @Rest\QueryParam(name="projects", requirements="[\d|,]+", strict=true, nullable=true, description="Comma separated list of project IDs to filter activities")
* @Rest\QueryParam(name="visible", requirements="1|2|3", strict=true, nullable=true, description="Visibility status to filter activities. Allowed values: 1=visible, 2=hidden, 3=all (default: 1)")
* @Rest\QueryParam(name="globals", requirements="true", strict=true, nullable=true, description="Use if you want to fetch only global activities. Allowed values: true (default: false)")
* @Rest\QueryParam(name="globalsFirst", requirements="true|false", strict=true, nullable=true, description="Deprecated parameter, value is not used any more")
@@ -99,8 +100,17 @@ class ActivityController extends BaseApiController
@trigger_error('API parameter globalsFirst is deprecated and will be removed with 2.0', E_USER_DEPRECATED);
}
if (!empty($projects = $paramFetcher->get('projects'))) {
if (!is_array($projects)) {
$projects = explode(',', $projects);
}
if (!empty($projects)) {
$query->setProjects($projects);
}
}
if (!empty($project = $paramFetcher->get('project'))) {
$query->setProject($project);
$query->addProject($project);
}
if (null !== ($visible = $paramFetcher->get('visible'))) {

View File

@@ -77,6 +77,7 @@ class ProjectController extends BaseApiController
* )
* )
* @Rest\QueryParam(name="customer", requirements="\d+", strict=true, nullable=true, description="Customer ID to filter projects")
* @Rest\QueryParam(name="customers", requirements="[\d|,]+", strict=true, nullable=true, description="Comma separated list of customer IDs to filter projects")
* @Rest\QueryParam(name="visible", requirements="\d+", strict=true, nullable=true, description="Visibility status to filter projects. Allowed values: 1=visible, 2=hidden, 3=both (default; 1)")
* @Rest\QueryParam(name="start", requirements=@Constraints\DateTime(format="Y-m-d\TH:i:s"), strict=true, nullable=true, description="Only projects that started before this date will be included. Allowed format: HTML5 (default: now, if end is also empty)")
* @Rest\QueryParam(name="end", requirements=@Constraints\DateTime(format="Y-m-d\TH:i:s"), strict=true, nullable=true, description="Only projects that ended after this date will be included. Allowed format: HTML5 (default: now, if start is also empty)")
@@ -104,8 +105,17 @@ class ProjectController extends BaseApiController
$query->setOrderBy($orderBy);
}
if (!empty($customers = $paramFetcher->get('customers'))) {
if (!is_array($customers)) {
$customers = explode(',', $customers);
}
if (!empty($customers)) {
$query->setCustomers($customers);
}
}
if (!empty($customer = $paramFetcher->get('customer'))) {
$query->setCustomer($customer);
$query->addCustomer($customer);
}
if (null !== ($visible = $paramFetcher->get('visible'))) {

View File

@@ -127,9 +127,12 @@ class TimesheetController extends BaseApiController
* )
*
* @Rest\QueryParam(name="user", requirements="\d+|all", strict=true, nullable=true, description="User ID to filter timesheets. Needs permission 'view_other_timesheet', pass 'all' to fetch data for all user (default: current user)")
* @Rest\QueryParam(name="customer", requirements="\d+", strict=true, nullable=true, description="Customer ID to filter timesheets")
* @Rest\QueryParam(name="project", requirements="\d+", strict=true, nullable=true, description="Project ID to filter timesheets")
* @Rest\QueryParam(name="activity", requirements="\d+", strict=true, nullable=true, description="Activity ID to filter timesheets")
* @Rest\QueryParam(name="customer", requirements="\d+", strict=true, nullable=true, description="DEPRECATED: Customer ID to filter timesheets (will be removed with 2.0)")
* @Rest\QueryParam(name="customers", requirements="[\d|,]+", strict=true, nullable=true, description="Comma separated list of customer IDs to filter timesheets")
* @Rest\QueryParam(name="project", requirements="\d+", strict=true, nullable=true, description="DEPRECATED: Project ID to filter timesheets (will be removed with 2.0)")
* @Rest\QueryParam(name="projects", requirements="[\d|,]+", strict=true, nullable=true, description="Comma separated list of project IDs to filter timesheets")
* @Rest\QueryParam(name="activity", requirements="\d+", strict=true, nullable=true, description="DEPRECATED: Activity ID to filter timesheets (will be removed with 2.0)")
* @Rest\QueryParam(name="activities", requirements="[\d|,]+", strict=true, nullable=true, description="Comma separated list of activity IDs to filter timesheets")
* @Rest\QueryParam(name="page", requirements="\d+", strict=true, nullable=true, description="The page to display, renders a 404 if not found (default: 1)")
* @Rest\QueryParam(name="size", requirements="\d+", strict=true, nullable=true, description="The amount of entries for each page (default: 50)")
* @Rest\QueryParam(name="tags", strict=true, nullable=true, description="The name of tags which are in the datasets")
@@ -159,16 +162,40 @@ class TimesheetController extends BaseApiController
$query->setUser($user);
}
if (!empty($customer = $paramFetcher->get('customer'))) {
$query->setCustomer($customer);
if (!empty($customers = $paramFetcher->get('customers'))) {
if (!is_array($customers)) {
$customers = explode(',', $customers);
}
if (!empty($customers)) {
$query->setCustomers($customers);
}
} elseif (!empty($customer = $paramFetcher->get('customer'))) {
@trigger_error('Timesheet API parameter "customer" is deprecated and will be removed with 2.0, use "customers" instead', E_USER_DEPRECATED);
$query->addCustomer($customer);
}
if (!empty($project = $paramFetcher->get('project'))) {
$query->setProject($project);
if (!empty($projects = $paramFetcher->get('projects'))) {
if (!is_array($projects)) {
$projects = explode(',', $projects);
}
if (!empty($projects)) {
$query->setProjects($projects);
}
} elseif (!empty($project = $paramFetcher->get('project'))) {
@trigger_error('Timesheet API parameter "project" is deprecated and will be removed with 2.0, use "projects" instead', E_USER_DEPRECATED);
$query->addProject($project);
}
if (!empty($activity = $paramFetcher->get('activity'))) {
$query->setActivity($activity);
if (!empty($activities = $paramFetcher->get('activities'))) {
if (!is_array($activities)) {
$activities = explode(',', $activities);
}
if (!empty($activities)) {
$query->setActivities($activities);
}
} elseif (!empty($activity = $paramFetcher->get('activity'))) {
@trigger_error('Timesheet API parameter "activity" is deprecated and will be removed with 2.0, use "activities" instead', E_USER_DEPRECATED);
$query->addActivity($activity);
}
if (null !== ($page = $paramFetcher->get('page'))) {

View File

@@ -241,7 +241,7 @@ final class ActivityController extends AbstractController
'label' => 'label.activity',
'query_builder' => function (ActivityRepository $repo) use ($activity) {
$query = new ActivityFormTypeQuery();
$query->setProject($activity->getProject());
$query->addProject($activity->getProject());
$query->setActivityToIgnore($activity);
return $repo->getQueryBuilderForFormType($query);

View File

@@ -244,7 +244,7 @@ final class CustomerController extends AbstractController
$query->setCurrentUser($this->getUser());
$query->setPage($page);
$query->setPageSize(5);
$query->setCustomer($customer);
$query->addCustomer($customer);
/* @var $entries Pagerfanta */
$entries = $projectRepository->getPagerfantaForQuery($query);

View File

@@ -313,7 +313,7 @@ final class InvoiceController extends AbstractController
protected function getEntries(InvoiceQuery $query): array
{
// customer needs to be defined, as we need the currency for the invoice
if (null === $query->getCustomer()) {
if (!$query->hasCustomers()) {
return [];
}

View File

@@ -249,7 +249,7 @@ final class ProjectController extends AbstractController
$query->setCurrentUser($this->getUser());
$query->setPage($page);
$query->setPageSize(5);
$query->setProject($project);
$query->addProject($project);
$query->setExcludeGlobals(true);
/* @var $entries Pagerfanta */
@@ -405,7 +405,7 @@ final class ProjectController extends AbstractController
'label' => 'label.project',
'query_builder' => function (ProjectRepository $repo) use ($project) {
$query = new ProjectFormTypeQuery();
$query->setCustomer($project->getCustomer());
$query->addCustomer($project->getCustomer());
$query->setProjectToIgnore($project);
$query->setUser($this->getUser());

View File

@@ -9,6 +9,7 @@
namespace App\Form\Toolbar;
use App\Entity\Activity;
use App\Form\Type\ActivityType;
use App\Form\Type\CustomerType;
use App\Form\Type\DateRangeType;
@@ -70,25 +71,47 @@ abstract class AbstractToolbarForm extends AbstractType
]);
}
protected function addCustomerChoice(FormBuilderInterface $builder, array $options = [])
protected function addCustomerChoice(FormBuilderInterface $builder, array $options = [], bool $multiProject = false)
{
$this->addCustomerSelect($builder, $options, false, $multiProject);
}
protected function addCustomerMultiChoice(FormBuilderInterface $builder, array $options = [], bool $multiProject = false)
{
$this->addCustomerSelect($builder, $options, true, $multiProject);
}
private function addCustomerSelect(FormBuilderInterface $builder, array $options, bool $multiCustomer, bool $multiProject)
{
$name = 'customer';
if ($multiCustomer) {
$name = 'customers';
}
// just a fake field for having this field at the right position in the frontend
$builder->add('customer', HiddenType::class);
$builder->add($name, HiddenType::class);
$builder->addEventListener(
FormEvents::PRE_SUBMIT,
function (FormEvent $event) use ($builder, $options) {
function (FormEvent $event) use ($builder, $options, $name, $multiCustomer, $multiProject) {
$data = $event->getData();
$event->getForm()->add('customer', CustomerType::class, array_merge([
$event->getForm()->add($name, CustomerType::class, array_merge([
'multiple' => $multiCustomer,
'required' => false,
'project_enabled' => true,
'project_enabled' => $multiCustomer ? 'customers' : 'customer',
'project_select' => $multiProject ? 'projects' : 'project',
'end_date_param' => '%daterange%',
'start_date_param' => '%daterange%',
'query_builder' => function (CustomerRepository $repo) use ($builder, $data) {
'query_builder' => function (CustomerRepository $repo) use ($builder, $data, $name, $multiCustomer) {
$query = new CustomerFormTypeQuery();
$query->setUser($builder->getOption('user'));
if (isset($data['customer']) && !empty($data['customer'])) {
$query->setCustomer($data['customer']);
if (isset($data[$name]) && !empty($data[$name])) {
if ($multiCustomer) {
$query->setCustomers($data[$name]);
} else {
$query->addCustomer($data[$name]);
}
}
return $repo->getQueryBuilderForFormType($query);
@@ -131,28 +154,56 @@ abstract class AbstractToolbarForm extends AbstractType
]);
}
protected function addProjectChoice(FormBuilderInterface $builder, array $options = [])
protected function addProjectChoice(FormBuilderInterface $builder, array $options = [], bool $multiCustomer = false, bool $multiActivity = false)
{
$this->addProjectSelect($builder, $options, false, $multiCustomer, $multiActivity);
}
protected function addProjectMultiChoice(FormBuilderInterface $builder, array $options = [], bool $multiCustomer = false, bool $multiActivity = false)
{
$this->addProjectSelect($builder, $options, true, $multiCustomer, $multiActivity);
}
private function addProjectSelect(FormBuilderInterface $builder, array $options, bool $multiProject, bool $multiCustomer, bool $multiActivity)
{
$name = 'project';
if ($multiProject) {
$name = 'projects';
}
// just a fake field for having this field at the right position in the frontend
$builder->add('project', HiddenType::class);
$builder->add($name, HiddenType::class);
$builder->addEventListener(
FormEvents::PRE_SUBMIT,
function (FormEvent $event) use ($builder, $options) {
function (FormEvent $event) use ($builder, $options, $name, $multiCustomer, $multiProject, $multiActivity) {
$data = $event->getData();
$event->getForm()->add('project', ProjectType::class, array_merge([
$event->getForm()->add($name, ProjectType::class, array_merge([
'multiple' => $multiProject,
'required' => false,
'activity_enabled' => true,
'query_builder' => function (ProjectRepository $repo) use ($builder, $data, $options) {
'activity_enabled' => $multiProject ? 'projects' : 'project',
'activity_select' => $multiActivity ? 'activities' : 'activity',
'query_builder' => function (ProjectRepository $repo) use ($builder, $data, $options, $multiCustomer, $multiProject) {
$query = new ProjectFormTypeQuery();
$query->setUser($builder->getOption('user'));
if (isset($data['customer']) && !empty($data['customer'])) {
$query->setCustomer($data['customer']);
$name = $multiCustomer ? 'customers' : 'customer';
if (isset($data[$name]) && !empty($data[$name])) {
if (is_array($data[$name])) {
$query->setCustomers($data[$name]);
} else {
$query->addCustomer($data[$name]);
}
}
if (isset($data['project']) && !empty($data['project'])) {
$query->setProject($data['project']);
$name = $multiProject ? 'projects' : 'project';
if (isset($data[$name]) && !empty($data[$name])) {
if (is_array($data[$name])) {
$query->setProjects($data[$name]);
} else {
$query->addProject($data[$name]);
}
}
if (isset($options['ignore_date']) && true === $options['ignore_date']) {
$query->setIgnoreDate(true);
}
@@ -164,30 +215,62 @@ abstract class AbstractToolbarForm extends AbstractType
);
}
protected function addActivityChoice(FormBuilderInterface $builder)
protected function addActivityChoice(FormBuilderInterface $builder, array $options = [], bool $multiProject = false)
{
$this->addActivitySelect($builder, $options, false, $multiProject);
}
protected function addActivityMultiChoice(FormBuilderInterface $builder, array $options = [], bool $multiProject = false)
{
$this->addActivitySelect($builder, $options, true, $multiProject);
}
private function addActivitySelect(FormBuilderInterface $builder, array $options = [], bool $multiActivity = false, bool $multiProject = false)
{
$name = 'activity';
if ($multiActivity) {
$name = 'activities';
}
// just a fake field for having this field at the right position in the frontend
$builder->add('activity', HiddenType::class);
$builder->add($name, HiddenType::class);
$builder->addEventListener(
FormEvents::PRE_SUBMIT,
function (FormEvent $event) {
function (FormEvent $event) use ($name, $multiActivity, $multiProject) {
$data = $event->getData();
$event->getForm()->add('activity', ActivityType::class, [
$event->getForm()->add($name, ActivityType::class, [
'multiple' => $multiActivity,
'required' => false,
'query_builder' => function (ActivityRepository $repo) use ($data) {
'query_builder' => function (ActivityRepository $repo) use ($data, $multiActivity, $multiProject) {
$query = new ActivityFormTypeQuery();
if (isset($data['activity']) && !empty($data['activity'])) {
$activity = $data['activity'];
if (is_string($data['activity'])) {
$activity = $repo->find($data['activity']);
$name = $multiActivity ? 'activities' : 'activity';
if (isset($data[$name]) && !empty($data[$name])) {
// we need to pre-fetch the activities to see if they are global, see ActivityFormTypeQuery::isGlobalsOnly()
$activities = $data[$name];
if (!is_array($activities)) {
$activities = [$activities];
}
foreach ($activities as $activity) {
if ($activity instanceof Activity) {
$query->addActivity($activity);
} elseif ($activity !== null) {
$tmp = $repo->find($activity);
if (null !== $tmp) {
$query->addActivity($tmp);
}
}
}
$query->setActivity($activity);
}
if (isset($data['project']) && !empty($data['project'])) {
$query->setProject($data['project']);
$name = $multiProject ? 'projects' : 'project';
if (isset($data[$name]) && !empty($data[$name])) {
if ($multiProject) {
$query->setProjects($data[$name]);
} else {
$query->addProject($data[$name]);
}
}
return $repo->getQueryBuilderForFormType($query);

View File

@@ -25,8 +25,8 @@ class ActivityToolbarForm extends AbstractToolbarForm
public function buildForm(FormBuilderInterface $builder, array $options)
{
$this->addSearchTermInputField($builder);
$this->addCustomerChoice($builder);
$this->addProjectChoice($builder);
$this->addCustomerMultiChoice($builder, [], true);
$this->addProjectMultiChoice($builder, [], true, false);
$builder->add('globalsOnly', ChoiceType::class, [
'choices' => [
'yes' => 1,

View File

@@ -31,9 +31,9 @@ class ExportToolbarForm extends AbstractToolbarForm
$this->addTimesheetStateChoice($builder);
$this->addUsersChoice($builder);
$this->addDateRangeChoice($builder);
$this->addCustomerChoice($builder, ['start_date_param' => null, 'end_date_param' => null, 'ignore_date' => true]);
$this->addProjectChoice($builder, ['ignore_date' => true]);
$this->addActivityChoice($builder);
$this->addCustomerMultiChoice($builder, ['start_date_param' => null, 'end_date_param' => null, 'ignore_date' => true], true);
$this->addProjectMultiChoice($builder, ['ignore_date' => true], true, true);
$this->addActivityMultiChoice($builder, [], true);
$this->addExportType($builder);
$this->addTagInputField($builder);
$builder->add('markAsExported', CheckboxType::class, [

View File

@@ -27,7 +27,7 @@ class InvoiceToolbarForm extends InvoiceToolbarSimpleForm
if ($options['include_user']) {
$this->addUsersChoice($builder);
}
$this->addActivityChoice($builder);
$this->addActivityMultiChoice($builder, $options, true);
$this->addTagInputField($builder);
$this->addExportStateChoice($builder);
}

View File

@@ -28,8 +28,8 @@ class InvoiceToolbarSimpleForm extends AbstractToolbarForm
{
$this->addTemplateChoice($builder);
$this->addDateRangeChoice($builder);
$this->addCustomerChoice($builder, ['required' => true, 'start_date_param' => null, 'end_date_param' => null, 'ignore_date' => true, 'placeholder' => '']);
$this->addProjectChoice($builder, ['ignore_date' => true]);
$this->addCustomerChoice($builder, ['required' => true, 'start_date_param' => null, 'end_date_param' => null, 'ignore_date' => true, 'placeholder' => ''], true);
$this->addProjectMultiChoice($builder, ['ignore_date' => true], false, true);
$builder->add('markAsExported', CheckboxType::class, [
'label' => 'label.mark_as_exported',
'required' => false,

View File

@@ -24,7 +24,7 @@ class ProjectToolbarForm extends AbstractToolbarForm
public function buildForm(FormBuilderInterface $builder, array $options)
{
$this->addSearchTermInputField($builder);
$this->addCustomerChoice($builder);
$this->addCustomerMultiChoice($builder, [], false);
$this->addVisibilityChoice($builder);
$this->addPageSizeChoice($builder);
$this->addHiddenPagination($builder);

View File

@@ -20,6 +20,7 @@ class TeamToolbarForm extends AbstractToolbarForm
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$this->addUsersChoice($builder);
$this->addSearchTermInputField($builder);
$this->addPageSizeChoice($builder);
$this->addHiddenPagination($builder);

View File

@@ -28,9 +28,9 @@ class TimesheetToolbarForm extends AbstractToolbarForm
$this->addUsersChoice($builder);
}
$this->addDateRangeChoice($builder);
$this->addCustomerChoice($builder);
$this->addProjectChoice($builder);
$this->addActivityChoice($builder);
$this->addCustomerMultiChoice($builder, [], true);
$this->addProjectMultiChoice($builder, [], true, true);
$this->addActivityMultiChoice($builder, [], true);
$this->addTagInputField($builder);
$this->addTimesheetStateChoice($builder);
$this->addExportStateChoice($builder);

View File

@@ -39,6 +39,7 @@ class CustomerType extends AbstractType
'choice_label' => 'name',
'query_builder_for_user' => true,
'project_enabled' => false,
'project_select' => 'project',
'start_date_param' => '%begin%',
'end_date_param' => '%end%',
'ignore_date' => false,
@@ -57,8 +58,9 @@ class CustomerType extends AbstractType
});
$resolver->setDefault('api_data', function (Options $options) {
if (true === $options['project_enabled']) {
$routeParams = ['customer' => '%customer%', 'visible' => $options['project_visibility']];
if (false !== $options['project_enabled']) {
$name = is_string($options['project_enabled']) ? $options['project_enabled'] : 'customer';
$routeParams = [$name => '%' . $name . '%', 'visible' => $options['project_visibility']];
$emptyRouteParams = ['visible' => $options['project_visibility']];
if (!$options['ignore_date']) {
@@ -77,7 +79,7 @@ class CustomerType extends AbstractType
}
return [
'select' => 'project',
'select' => $options['project_select'],
'route' => 'get_projects',
'route_params' => $routeParams,
'empty_route_params' => $emptyRouteParams,

View File

@@ -64,6 +64,7 @@ class ProjectType extends AbstractType
},
'query_builder_for_user' => true,
'activity_enabled' => false,
'activity_select' => 'activity',
'activity_visibility' => ActivityQuery::SHOW_VISIBLE,
'ignore_date' => false,
]);
@@ -83,11 +84,13 @@ class ProjectType extends AbstractType
});
$resolver->setDefault('api_data', function (Options $options) {
if (true === $options['activity_enabled']) {
if (false !== $options['activity_enabled']) {
$name = is_string($options['activity_enabled']) ? $options['activity_enabled'] : 'project';
return [
'select' => 'activity',
'select' => $options['activity_select'],
'route' => 'get_activities',
'route_params' => ['project' => '%project%', 'visible' => $options['activity_visibility']],
'route_params' => [$name => '%' . $name . '%', 'visible' => $options['activity_visibility']],
'empty_route_params' => ['globals' => 'true', 'visible' => $options['activity_visibility']],
];
}

View File

@@ -9,6 +9,7 @@
namespace App\Invoice\Hydrator;
use App\Entity\Activity;
use App\Invoice\InvoiceModel;
use App\Invoice\InvoiceModelHydrator;
@@ -16,24 +17,38 @@ class InvoiceModelActivityHydrator implements InvoiceModelHydrator
{
public function hydrate(InvoiceModel $model): array
{
$activity = $model->getQuery()->getActivity();
if (null === $activity) {
if (!$model->getQuery()->hasActivities()) {
return [];
}
$formatter = $model->getFormatter();
$currency = $model->getCurrency();
$values = [];
$i = 0;
foreach ($model->getQuery()->getActivities() as $activity) {
$prefix = '';
if ($i > 0) {
$prefix = $i . '.';
}
$values = array_merge($values, $this->getValuesFromActivity($activity, $prefix));
$i++;
}
return $values;
}
private function getValuesFromActivity(Activity $activity, string $prefix): array
{
$prefix = 'activity.' . $prefix;
$values = [
'activity.id' => $activity->getId(),
'activity.name' => $activity->getName(),
'activity.comment' => $activity->getComment(),
$prefix . 'id' => $activity->getId(),
$prefix . 'name' => $activity->getName(),
$prefix . 'comment' => $activity->getComment(),
];
foreach ($activity->getVisibleMetaFields() as $metaField) {
$values = array_merge($values, [
'activity.meta.' . $metaField->getName() => $metaField->getValue(),
$prefix . 'meta.' . $metaField->getName() => $metaField->getValue(),
]);
}

View File

@@ -9,6 +9,8 @@
namespace App\Invoice\Hydrator;
use App\Entity\Project;
use App\Invoice\InvoiceFormatter;
use App\Invoice\InvoiceModel;
use App\Invoice\InvoiceModelHydrator;
@@ -16,34 +18,51 @@ class InvoiceModelProjectHydrator implements InvoiceModelHydrator
{
public function hydrate(InvoiceModel $model): array
{
$project = $model->getQuery()->getProject();
if (null === $project) {
if (!$model->getQuery()->hasProjects()) {
return [];
}
$formatter = $model->getFormatter();
$currency = $model->getCurrency();
$values = [];
$i = 0;
foreach ($model->getQuery()->getProjects() as $project) {
$prefix = '';
if ($i > 0) {
$prefix = $i . '.';
}
$values = array_merge($values, $this->getValuesFromProject($project, $formatter, $currency, $prefix));
$i++;
}
return $values;
}
private function getValuesFromProject(Project $project, InvoiceFormatter $formatter, string $currency, string $prefix): array
{
$prefix = 'project.' . $prefix;
$values = [
'project.id' => $project->getId(),
'project.name' => $project->getName(),
'project.comment' => $project->getComment(),
'project.order_number' => $project->getOrderNumber(),
'project.start_date' => null !== $project->getStart() ? $formatter->getFormattedDateTime($project->getStart()) : '',
'project.end_date' => null !== $project->getEnd() ? $formatter->getFormattedDateTime($project->getEnd()) : '',
'project.order_date' => null !== $project->getOrderDate() ? $formatter->getFormattedDateTime($project->getOrderDate()) : '',
'project.budget_money' => $formatter->getFormattedMoney($project->getBudget(), $currency),
'project.budget_money_nc' => $formatter->getFormattedMoney($project->getBudget(), null),
'project.budget_money_plain' => $project->getBudget(),
'project.budget_time' => $project->getTimeBudget(),
'project.budget_time_decimal' => $formatter->getFormattedDecimalDuration($project->getTimeBudget()),
'project.budget_time_minutes' => number_format($project->getTimeBudget() / 60),
$prefix . 'id' => $project->getId(),
$prefix . 'name' => $project->getName(),
$prefix . 'comment' => $project->getComment(),
$prefix . 'order_number' => $project->getOrderNumber(),
$prefix . 'start_date' => null !== $project->getStart() ? $formatter->getFormattedDateTime($project->getStart()) : '',
$prefix . 'end_date' => null !== $project->getEnd() ? $formatter->getFormattedDateTime($project->getEnd()) : '',
$prefix . 'order_date' => null !== $project->getOrderDate() ? $formatter->getFormattedDateTime($project->getOrderDate()) : '',
$prefix . 'budget_money' => $formatter->getFormattedMoney($project->getBudget(), $currency),
$prefix . 'budget_money_nc' => $formatter->getFormattedMoney($project->getBudget(), null),
$prefix . 'budget_money_plain' => $project->getBudget(),
$prefix . 'budget_time' => $project->getTimeBudget(),
$prefix . 'budget_time_decimal' => $formatter->getFormattedDecimalDuration($project->getTimeBudget()),
$prefix . 'budget_time_minutes' => number_format($project->getTimeBudget() / 60),
];
foreach ($project->getVisibleMetaFields() as $metaField) {
$values = array_merge($values, [
'project.meta.' . $metaField->getName() => $metaField->getValue(),
$prefix . 'meta.' . $metaField->getName() => $metaField->getValue(),
]);
}

View File

@@ -154,13 +154,13 @@ class ActivityRepository extends EntityRepository
}
/**
* @deprecated since 1.1
* @deprecated since 1.1 - use getQueryBuilderForFormType() instead - will be removed with 2.0
*/
public function builderForEntityType($activity, $project)
{
$query = new ActivityFormTypeQuery();
$query->setActivity($activity);
$query->setProject($project);
$query->addActivity($activity);
$query->addProject($project);
return $this->getQueryBuilderForFormType($query);
}
@@ -212,14 +212,14 @@ class ActivityRepository extends EntityRepository
if ($query->isGlobalsOnly()) {
$where->add($qb->expr()->isNull('a.project'));
} elseif (null !== $query->getProject()) {
} elseif ($query->hasProjects()) {
$where->add(
$qb->expr()->orX(
$qb->expr()->eq('a.project', ':project'),
$qb->expr()->in('a.project', ':project'),
$qb->expr()->isNull('a.project')
)
);
$qb->setParameter('project', $query->getProject());
$qb->setParameter('project', $query->getProjects());
}
if (null !== $query->getActivityToIgnore()) {
@@ -233,10 +233,9 @@ class ActivityRepository extends EntityRepository
$or->add($where);
// this must always be the last part of the query
/* @var Activity $entity */
if (null !== $query->getActivity()) {
$or->add($qb->expr()->eq('a.id', ':activity'));
$qb->setParameter('activity', $query->getActivity());
if ($query->hasActivities()) {
$or->add($qb->expr()->in('a.id', ':activity'));
$qb->setParameter('activity', $query->getActivities());
}
if ($or->count() > 0) {
@@ -303,9 +302,9 @@ class ActivityRepository extends EntityRepository
if ($query->isGlobalsOnly()) {
$where->add($qb->expr()->isNull('a.project'));
} elseif (null !== $query->getProject()) {
} elseif ($query->hasProjects()) {
$orX = $qb->expr()->orX(
$qb->expr()->eq('a.project', ':project')
$qb->expr()->in('a.project', ':project')
);
if (!$query->isExcludeGlobals()) {
@@ -313,7 +312,7 @@ class ActivityRepository extends EntityRepository
}
$where->add($orX);
$qb->setParameter('project', $query->getProject());
$qb->setParameter('project', $query->getProjects());
} elseif (null !== $query->getCustomer()) {
$where->add('p.customer = :customer');
$qb->setParameter('customer', $query->getCustomer());

View File

@@ -163,12 +163,12 @@ class CustomerRepository extends EntityRepository
}
/**
* @deprecated since 1.1 - don't use this method, it ignores team permission checks
* @deprecated since 1.1 - use getQueryBuilderForFormType() istead - will be removed with 2.0
*/
public function builderForEntityType($customer)
{
$query = new CustomerFormTypeQuery();
$query->setCustomer($customer);
$query->addCustomer($customer);
return $this->getQueryBuilderForFormType($query);
}
@@ -190,9 +190,9 @@ class CustomerRepository extends EntityRepository
$qb->andWhere($qb->expr()->eq('c.visible', ':visible'));
$qb->setParameter('visible', true, \PDO::PARAM_BOOL);
$customer = $query->getCustomer();
if (null !== $customer) {
$qb->orWhere('c.id = :customer')->setParameter('customer', $customer);
if ($query->hasCustomers()) {
$qb->orWhere($qb->expr()->in('c.id', ':customer'))
->setParameter('customer', $query->getCustomers());
}
if (null !== $query->getCustomerToIgnore()) {

View File

@@ -152,13 +152,13 @@ class ProjectRepository extends EntityRepository
}
/**
* @deprecated since 1.1 - don't use this method, it ignores team permission checks
* @deprecated since 1.1 - use getQueryBuilderForFormType() istead - will be removed with 2.0
*/
public function builderForEntityType($project, $customer)
{
$query = new ProjectFormTypeQuery();
$query->setProject($project);
$query->setCustomer($customer);
$query->addProject($project);
$query->addCustomer($customer);
return $this->getQueryBuilderForFormType($query);
}
@@ -216,13 +216,14 @@ class ProjectRepository extends EntityRepository
$qb->setParameter('visible', true, \PDO::PARAM_BOOL);
$qb->setParameter('customer_visible', true, \PDO::PARAM_BOOL);
if (null !== $query->getProject()) {
$qb->orWhere('p.id = :project')->setParameter('project', $query->getProject());
if ($query->hasProjects()) {
$qb->orWhere($qb->expr()->in('p.id', ':project'))
->setParameter('project', $query->getProjects());
}
if (null !== $query->getCustomer()) {
$qb->andWhere('p.customer = :customer')
->setParameter('customer', $query->getCustomer());
if ($query->hasCustomers()) {
$qb->andWhere($qb->expr()->in('p.customer', ':customer'))
->setParameter('customer', $query->getCustomers());
}
if (null !== $query->getProjectToIgnore()) {
@@ -272,9 +273,9 @@ class ProjectRepository extends EntityRepository
$qb->setParameter('customer_visible', true, \PDO::PARAM_BOOL);
}
if (null !== $query->getCustomer()) {
$qb->andWhere('p.customer = :customer')
->setParameter('customer', $query->getCustomer());
if ($query->hasCustomers()) {
$qb->andWhere($qb->expr()->in('p.customer', ':customer'))
->setParameter('customer', $query->getCustomers());
}
// this is far from being perfect, possible enhancements:

View File

@@ -12,67 +12,32 @@ namespace App\Repository\Query;
use App\Entity\Activity;
use App\Entity\Project;
final class ActivityFormTypeQuery
final class ActivityFormTypeQuery extends BaseFormTypeQuery
{
/**
* @var Activity|int|null
*/
private $activity;
/**
* @var Project|int|null
*/
private $project;
/**
* @var Activity|null
*/
private $activityToIgnore;
/**
* @param Activity|int|null $activity
* @param Project|int|null $project
* @param Activity|int|array|null $activity
* @param Project|int|array|null $project
*/
public function __construct($activity = null, $project = null)
{
$this->activity = $activity;
$this->project = $project;
}
if (null !== $activity) {
if (!is_array($activity)) {
$activity = [$activity];
}
$this->setActivities($activity);
}
/**
* @return Activity|int|null
*/
public function getActivity()
{
return $this->activity;
}
/**
* @param Activity|int|null $activity
* @return ActivityFormTypeQuery
*/
public function setActivity($activity): ActivityFormTypeQuery
{
$this->activity = $activity;
return $this;
}
/**
* @return Project|int|null
*/
public function getProject()
{
return $this->project;
}
/**
* @param Project|int|null $project
* @return ActivityFormTypeQuery
*/
public function setProject($project): ActivityFormTypeQuery
{
$this->project = $project;
return $this;
if (null !== $project) {
if (!is_array($project)) {
$project = [$project];
}
$this->setProjects($project);
}
}
/**
@@ -92,12 +57,21 @@ final class ActivityFormTypeQuery
public function isGlobalsOnly(): bool
{
return
(
null === $this->activity ||
($this->activity instanceof Activity && null === $this->activity->getProject())
)
&&
null === $this->project;
if ($this->hasProjects()) {
return false;
}
if (!$this->hasActivities()) {
return true;
}
foreach ($this->getActivities() as $activity) {
// this is a potential problem, if only IDs were set
if ($activity instanceof Activity && !$activity->isGlobal()) {
return false;
}
}
return true;
}
}

View File

@@ -19,9 +19,9 @@ class ActivityQuery extends ProjectQuery
public const ACTIVITY_ORDER_ALLOWED = ['id', 'name', 'comment', 'customer', 'project'];
/**
* @var Project|int|null
* @var Project[]|int[]
*/
private $project;
private $projects = [];
/**
* @var bool
*/
@@ -72,20 +72,58 @@ class ActivityQuery extends ProjectQuery
/**
* @return Project|int|null
* @deprecated since 1.9 - use getProjects() instead - will be removed with 2.0
*/
public function getProject()
{
return $this->project;
if (count($this->projects) > 0) {
return $this->projects[0];
}
return null;
}
/**
* @param Project|int|null $project
* @return self
* @deprecated since 1.9 - use setProjects() or addProject() instead - will be removed with 2.0
*/
public function setProject($project = null): self
{
$this->project = $project;
if (null === $project) {
$this->projects = [];
} else {
$this->projects = [$project];
}
return $this;
}
/**
* @param Project|int $project
* @return self
*/
public function addProject($project): self
{
$this->projects[] = $project;
return $this;
}
public function setProjects(array $projects): self
{
$this->projects = $projects;
return $this;
}
public function getProjects(): array
{
return $this->projects;
}
public function hasProjects(): bool
{
return !empty($this->projects);
}
}

View File

@@ -0,0 +1,257 @@
<?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\Repository\Query;
use App\Entity\Activity;
use App\Entity\Customer;
use App\Entity\Project;
use App\Entity\Team;
use App\Entity\User;
abstract class BaseFormTypeQuery
{
/**
* @var array
*/
private $activities = [];
/**
* @var array
*/
private $projects = [];
/**
* @var array
*/
private $customers = [];
/**
* @var User
*/
private $user;
/**
* @var array<Team>
*/
private $teams = [];
/**
* @return Activity|int|null
* @deprecated since 1.9 - use getActivities() instead - will be removed with 2.0
*/
public function getActivity()
{
if (count($this->activities) > 0) {
return $this->activities[0];
}
return null;
}
/**
* @param Activity|int|null $activity
* @return self
* @deprecated since 1.9 - use setActivities() or addActivity() instead - will be removed with 2.0
*/
public function setActivity($activity): self
{
if (null === $activity) {
$this->activities = [];
} else {
$this->activities = [$activity];
}
return $this;
}
/**
* @param Activity|int $activity
* @return self
*/
public function addActivity($activity): self
{
if (null !== $activity) {
$this->activities[] = $activity;
}
return $this;
}
/**
* @param Activity[]|int[] $activities
* @return self
*/
public function setActivities(array $activities): self
{
$this->activities = $activities;
return $this;
}
public function getActivities(): array
{
return $this->activities;
}
public function hasActivities(): bool
{
return !empty($this->activities);
}
/**
* @return Project|int|null
* @deprecated since 1.9 - use getProjects() instead - will be removed with 2.0
*/
public function getProject()
{
if (count($this->projects) > 0) {
return $this->projects[0];
}
return null;
}
/**
* @param Project|int|null $project
* @return self
* @deprecated since 1.9 - use addProject() instead - will be removed with 2.0
*/
public function setProject($project): self
{
if (null === $project) {
$this->projects = [];
} else {
$this->projects = [$project];
}
return $this;
}
/**
* @param Project|int $project
* @return self
*/
public function addProject($project): self
{
if (null !== $project) {
$this->projects[] = $project;
}
return $this;
}
/**
* @param Project[]|int[] $projects
* @return self
*/
public function setProjects(array $projects): self
{
$this->projects = $projects;
return $this;
}
/**
* @return array
*/
public function getProjects(): array
{
return $this->projects;
}
public function hasProjects(): bool
{
return !empty($this->projects);
}
/**
* @return Customer|int|null
* @deprecated since 1.9 - use getCustomers() instead - will be removed with 2.0
*/
public function getCustomer()
{
if (count($this->customers) > 0) {
return $this->customers[0];
}
return null;
}
/**
* @param Customer|int|null $customer
* @return self
* @deprecated since 1.9 - use addCustomer() instead - will be removed with 2.0
*/
public function setCustomer($customer): self
{
if (null === $customer) {
$this->customers = [];
} else {
$this->customers = [$customer];
}
return $this;
}
/**
* @param Customer[]|int[] $customers
* @return self
*/
public function setCustomers(array $customers): self
{
$this->customers = $customers;
return $this;
}
/**
* @param Customer|int $customer
* @return self
*/
public function addCustomer($customer): self
{
$this->customers[] = $customer;
return $this;
}
public function getCustomers(): array
{
return $this->customers;
}
public function hasCustomers(): bool
{
return !empty($this->customers);
}
public function getUser(): ?User
{
return $this->user;
}
public function setUser(User $user): self
{
$this->user = $user;
return $this;
}
public function addTeam(Team $team): self
{
$this->teams[$team->getId()] = $team;
return $this;
}
/**
* @return Team[]
*/
public function getTeams(): array
{
return array_values($this->teams);
}
}

View File

@@ -10,83 +10,28 @@
namespace App\Repository\Query;
use App\Entity\Customer;
use App\Entity\Team;
use App\Entity\User;
/**
* Can be used for advanced queries with the: CustomerRepository
*/
final class CustomerFormTypeQuery
final class CustomerFormTypeQuery extends BaseFormTypeQuery
{
/**
* @var Customer|int|null
*/
private $customer;
/**
* @var Customer|null
*/
private $customerToIgnore;
/**
* @var User
*/
private $user;
/**
* @var array<Team>
*/
private $teams = [];
/**
* @param Customer|int|null $customer
*/
public function __construct($customer = null)
{
$this->customer = $customer;
}
public function addTeam(Team $team): CustomerFormTypeQuery
{
$this->teams[$team->getId()] = $team;
return $this;
}
/**
* @return Team[]
*/
public function getTeams(): array
{
return array_values($this->teams);
}
public function getUser(): ?User
{
return $this->user;
}
public function setUser(User $user): CustomerFormTypeQuery
{
$this->user = $user;
return $this;
}
/**
* @return Customer|int|null
*/
public function getCustomer()
{
return $this->customer;
}
/**
* @param Customer|int|null $customer
* @return $this
*/
public function setCustomer($customer): CustomerFormTypeQuery
{
$this->customer = $customer;
return $this;
if (null !== $customer) {
if (!is_array($customer)) {
$customer = [$customer];
}
$this->setCustomers($customer);
}
}
/**

View File

@@ -11,31 +11,13 @@ namespace App\Repository\Query;
use App\Entity\Customer;
use App\Entity\Project;
use App\Entity\Team;
use App\Entity\User;
final class ProjectFormTypeQuery
final class ProjectFormTypeQuery extends BaseFormTypeQuery
{
/**
* @var Customer|int|null
*/
private $customer;
/**
* @var Project|int|null
*/
private $project;
/**
* @var Project|null
*/
private $projectToIgnore;
/**
* @var User
*/
private $user;
/**
* @var array<Team>
*/
private $teams = [];
/**
* @var bool
*/
@@ -47,73 +29,19 @@ final class ProjectFormTypeQuery
*/
public function __construct($project = null, $customer = null)
{
$this->project = $project;
$this->customer = $customer;
}
if (null !== $project) {
if (!is_array($project)) {
$project = [$project];
}
$this->setProjects($project);
}
public function addTeam(Team $team): ProjectFormTypeQuery
{
$this->teams[$team->getId()] = $team;
return $this;
}
/**
* @return Team[]
*/
public function getTeams(): array
{
return array_values($this->teams);
}
public function getUser(): ?User
{
return $this->user;
}
public function setUser(User $user): ProjectFormTypeQuery
{
$this->user = $user;
return $this;
}
/**
* @return Customer|int|null
*/
public function getCustomer()
{
return $this->customer;
}
/**
* @param Customer|int|null $customer
* @return $this
*/
public function setCustomer($customer): ProjectFormTypeQuery
{
$this->customer = $customer;
return $this;
}
/**
* @return Project|int|null
*/
public function getProject()
{
return $this->project;
}
/**
* @param Project|int|null $project
* @return ProjectFormTypeQuery
*/
public function setProject($project): ProjectFormTypeQuery
{
$this->project = $project;
return $this;
if (null !== $customer) {
if (!is_array($customer)) {
$customer = [$customer];
}
$this->setCustomers($customer);
}
}
/**

View File

@@ -21,9 +21,9 @@ class ProjectQuery extends BaseQuery implements VisibilityInterface
public const PROJECT_ORDER_ALLOWED = ['id', 'name', 'comment', 'customer', 'orderNumber', 'projectStart', 'projectEnd'];
/**
* @var Customer|int|null
* @var array
*/
private $customer;
private $customers = [];
/**
* @var \DateTime
*/
@@ -42,23 +42,61 @@ class ProjectQuery extends BaseQuery implements VisibilityInterface
/**
* @return Customer|int|null
* @deprecated since 1.9 - use getCustomers() instead - will be removed with 2.0
*/
public function getCustomer()
{
return $this->customer;
if (count($this->customers) > 0) {
return $this->customers[0];
}
return null;
}
/**
* @param Customer|int|null $customer
* @return $this
* @deprecated since 1.9 - use setCustomers() or addCustomer() instead - will be removed with 2.0
*/
public function setCustomer($customer = null)
{
$this->customer = $customer;
if (null === $customer) {
$this->customers = [];
} else {
$this->customers = [$customer];
}
return $this;
}
/**
* @param Customer|int $customer
* @return $this
*/
public function addCustomer($customer)
{
$this->customers[] = $customer;
return $this;
}
public function setCustomers(array $customers): self
{
$this->customers = $customers;
return $this;
}
public function getCustomers(): array
{
return $this->customers;
}
public function hasCustomers(): bool
{
return !empty($this->customers);
}
public function getProjectStart(): ?\DateTime
{
return $this->projectStart;

View File

@@ -9,24 +9,6 @@
namespace App\Repository\Query;
use App\Entity\User;
final class TagFormTypeQuery
final class TagFormTypeQuery extends BaseFormTypeQuery
{
/**
* @var User
*/
private $user;
public function getUser(): ?User
{
return $this->user;
}
public function setUser(User $user): TagFormTypeQuery
{
$this->user = $user;
return $this;
}
}

View File

@@ -9,14 +9,50 @@
namespace App\Repository\Query;
use App\Entity\User;
class TeamQuery extends BaseQuery
{
public const TEAM_ORDER_ALLOWED = ['id', 'name', 'teamlead'];
/**
* @var User[]
*/
private $users = [];
public function __construct()
{
$this->setDefaults([
'orderBy' => 'name',
]);
}
public function hasUsers(): bool
{
return !empty($this->users);
}
public function addUser(User $user): self
{
$this->users[$user->getId()] = $user;
return $this;
}
public function removeUser(User $user): self
{
if (isset($this->users[$user->getId()])) {
unset($this->users[$user->getId()]);
}
return $this;
}
/**
* @return User[]
*/
public function getUsers(): array
{
return array_values($this->users);
}
}

View File

@@ -32,9 +32,9 @@ class TimesheetQuery extends ActivityQuery
*/
protected $timesheetUser;
/**
* @var Activity|null
* @var array
*/
protected $activity;
private $activities = [];
/**
* @var int
*/
@@ -114,26 +114,66 @@ class TimesheetQuery extends ActivityQuery
}
/**
* Activity overwrites: setProject() and setCustomer()
*
* @return Activity|null
* @return Activity|int|null
* @deprecated since 1.9 - use getProjects() instead - will be removed with 2.0
*/
public function getActivity()
{
return $this->activity;
if (count($this->activities) > 0) {
return $this->activities[0];
}
return null;
}
public function getActivities(): array
{
return $this->activities;
}
/**
* @param Activity|int|null $activity
* @return TimesheetQuery
* @return $this
* @deprecated since 1.9 - use setActivities() or addActivity() instead - will be removed with 2.0
*/
public function setActivity($activity = null)
public function setActivity($activity)
{
$this->activity = $activity;
if (null === $activity) {
$this->activities = [];
} else {
$this->activities = [$activity];
}
return $this;
}
/**
* @param Activity|int $activity
* @return $this
*/
public function addActivity($activity)
{
$this->activities[] = $activity;
return $this;
}
/**
* @param Activity[]|int[] $activities
* @return $this
*/
public function setActivities(array $activities)
{
$this->activities = $activities;
return $this;
}
public function hasActivities(): bool
{
return !empty($this->activities);
}
/**
* @return int
*/

View File

@@ -9,47 +9,9 @@
namespace App\Repository\Query;
use App\Entity\Team;
use App\Entity\User;
/**
* Can be used for pre-filling form types with the: UserRepository
*/
final class UserFormTypeQuery
final class UserFormTypeQuery extends BaseFormTypeQuery
{
/**
* @var User
*/
private $user;
/**
* @var array<Team>
*/
private $teams = [];
public function addTeam(Team $team): UserFormTypeQuery
{
$this->teams[$team->getId()] = $team;
return $this;
}
/**
* @return Team[]
*/
public function getTeams(): array
{
return array_values($this->teams);
}
public function getUser(): ?User
{
return $this->user;
}
public function setUser(User $user): UserFormTypeQuery
{
$this->user = $user;
return $this;
}
}

View File

@@ -137,6 +137,14 @@ class TeamRepository extends EntityRepository
break;
}
if ($query->hasUsers()) {
$qb->orWhere(
$qb->expr()->in('t.teamlead', ':user'),
$qb->expr()->isMemberOf(':user', 't.users')
)
->setParameter('user', $query->getUsers());
}
$qb->addOrderBy($orderBy, $query->getOrder());
if (!empty($query->getSearchTerm())) {

View File

@@ -9,7 +9,6 @@
namespace App\Repository;
use App\Entity\Activity;
use App\Entity\ActivityRate;
use App\Entity\CustomerRate;
use App\Entity\ProjectRate;
@@ -712,19 +711,17 @@ class TimesheetRepository extends EntityRepository
$qb->andWhere('t.exported = :exported')->setParameter('exported', false, \PDO::PARAM_BOOL);
}
if (null !== $query->getActivity()) {
$qb->andWhere('t.activity = :activity')
->setParameter('activity', $query->getActivity());
if ($query->hasActivities()) {
$qb->andWhere($qb->expr()->in('t.activity', ':activity'))
->setParameter('activity', $query->getActivities());
}
if (null === $query->getActivity() || ($query->getActivity() instanceof Activity && null === $query->getActivity()->getProject())) {
if (null !== $query->getProject()) {
$qb->andWhere('t.project = :project')
->setParameter('project', $query->getProject());
} elseif (null !== $query->getCustomer()) {
$qb->andWhere('p.customer = :customer')
->setParameter('customer', $query->getCustomer());
}
if ($query->hasProjects()) {
$qb->andWhere($qb->expr()->in('t.project', ':project'))
->setParameter('project', $query->getProjects());
} elseif ($query->hasCustomers()) {
$qb->andWhere($qb->expr()->in('p.customer', ':customer'))
->setParameter('customer', $query->getCustomers());
}
$tags = $query->getTags();