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

@@ -13,9 +13,10 @@ Perform EACH version specific task between your version and the new one, otherwi
**New database tables and fields were created, don't forget to [run the updater](https://www.kimai.org/documentation/updates.html).**
- The directory `var/data/invoices/` will be used to store archived invoice files (check file permissions)
- The default invoice number format changed, if you want back the old one, use `{date}` as format - see [invoice documentation](https://www.kimai.org/documentation/invoices.html)
- The default invoice number format changed. If you want to use the old one: configure `{date}` as format - see [invoice documentation](https://www.kimai.org/documentation/invoices.html)
- HTML invoice templates are now treated like other files and offered as download. If you are using relative URLs for including
assets (CSS, images) you need to either inline them (see the default templates) or use absolute URLs.
assets (CSS, images) you need to either inline them (see the default templates) or use absolute URLs.
- Invoice templates that use query values `activity.X` or `project.X` should be checked, as multi-select is now possible for filtering
Permission changes:
- `history_invoice` - NEW: grants all features of the new invoice archive (by default for all admins)

View File

@@ -63,7 +63,7 @@ export default class KimaiFormSelect extends KimaiPlugin {
});
// If the option element contains all terms, return it
if(foundAll) {
if (foundAll) {
return data;
}
@@ -157,10 +157,10 @@ export default class KimaiFormSelect extends KimaiPlugin {
// if available, re-select the previous selected option (mostly usable for global activities)
select.val(selectedValue);
// if we don't trigger the change, the other selects won't be resetted
// if we don't trigger the change, the other selects won't reset
select.trigger('change');
// if the beta test kimai.theme.select_type is active, this will tell the selects to refresh
// if select2 is active, this will tell the select to refresh
if (select.hasClass('selectpicker')) {
select.trigger('change.select2');
}

View File

@@ -49,7 +49,9 @@ export default class KimaiSelectDataAPI extends KimaiPlugin {
let newApiUrl = self._buildUrlWithFormFields(this.dataset['apiUrl'], formPrefix);
if (jQuery(this).val() === '') {
const selectValue = jQuery(this).val();
if (selectValue === '' || (Array.isArray(selectValue) && selectValue.length === 0)) {
if (this.dataset['emptyUrl'] === undefined) {
self._updateSelect(targetSelect, {});
jQuery(targetSelect).attr('disabled', 'disabled');
@@ -82,7 +84,7 @@ export default class KimaiSelectDataAPI extends KimaiPlugin {
} else {
if (targetField.val() !== null) {
newValue = targetField.val();
if (newValue !== '') {
// having that special case here is far from being perfect... but for now it works ;-)
if (targetField.data('daterangepicker') !== undefined) {
@@ -95,10 +97,15 @@ export default class KimaiSelectDataAPI extends KimaiPlugin {
if (moment(newValue, targetField.data('format')).isValid()) {
newValue = moment(newValue, targetField.data('format')).format(moment.HTML5_FMT.DATETIME_LOCAL_SECONDS);
}
}
}
}
}
}
if (Array.isArray(newValue)) {
newValue = newValue.join(',');
}
newApiUrl = newApiUrl.replace(value, newValue);
}
});

File diff suppressed because one or more lines are too long

View File

@@ -5,7 +5,7 @@
"build/runtime.664a9501.js",
"build/0.f7fae2b9.js",
"build/1.c24a9c6f.js",
"build/app.c145a319.js"
"build/app.803caee0.js"
],
"css": [
"build/app.297ac462.css"
@@ -44,7 +44,7 @@
"build/runtime.664a9501.js": "sha384-xNNrNinl64G3nCUrIskgSjU0mUXXCB9lj6XCSInBTwxSKXk8uTMafnLHtdWdIGtd",
"build/0.f7fae2b9.js": "sha384-DR5A41RIECdWBd6xODR0b1hvGqcEYUyn9vNPreqK9VOCQWTF6bgxB4RVmqlOKilT",
"build/1.c24a9c6f.js": "sha384-JPoKdrVtBemSiVBoAnmSxLML7xXM9zYeuwOPYQv/kLzt/P4cmLY5r9gH8oaGRPFG",
"build/app.c145a319.js": "sha384-Pz9lT9FyrDn3Acp3DVntiPSiweHoEeOQQI+igDYb9tnT3exSuVGiMu9wZIrDBiWA",
"build/app.803caee0.js": "sha384-k4Wv+2BiVwDOlKrdKCtGi1aYU0i+YB60/wKZEYzY8u3RC8LkenF1nR8dwOsLVtij",
"build/app.297ac462.css": "sha384-ca+oUwbt6kP6yVM79YGuNwCUFwLzcK0ARuiwsQfRRltzJzQncA+c6tUjDFvlnFHw",
"build/invoice.50473330.js": "sha384-2BXic5Sgorf2tXai6zSAN4wLY2dbg06L03/xMKW6itMcszvtnRArKzfBh6DNcF3f",
"build/invoice.3764169b.css": "sha384-oILxBeIu3sNXxVnxhHmyHxqvLY2Rm5P2lzuJVtbxBIcnSdLS31SUSW6nVWNej1eg",

View File

@@ -3,7 +3,7 @@
"build/1.c24a9c6f.js": "build/1.c24a9c6f.js",
"build/2.a47ad919.js": "build/2.a47ad919.js",
"build/app.css": "build/app.297ac462.css",
"build/app.js": "build/app.c145a319.js",
"build/app.js": "build/app.803caee0.js",
"build/calendar.css": "build/calendar.8ede69b5.css",
"build/calendar.js": "build/calendar.ed1bc977.js",
"build/chart.js": "build/chart.c9943f57.js",

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();

View File

@@ -32,9 +32,9 @@
{{ form_errors(form) }}
{{ form_row(form.searchTerm) }}
{{ form_row(form.daterange) }}
{{ form_row(form.customer) }}
{{ form_row(form.project) }}
{{ form_row(form.activity) }}
{{ form_row(form.customers) }}
{{ form_row(form.projects) }}
{{ form_row(form.activities) }}
{{ form_row(form.users) }}
{{ form_row(form.tags) }}
{{ form_row(form.exported) }}

View File

@@ -38,9 +38,9 @@
{% endif %}
{{ form_row(form.daterange) }}
{{ form_row(form.customer) }}
{{ form_row(form.project) }}
{% if form.activity is defined %}
{{ form_row(form.activity) }}
{{ form_row(form.projects) }}
{% if form.activities is defined %}
{{ form_row(form.activities) }}
{% endif %}
{% if form.users is defined %}
{{ form_row(form.users) }}

View File

@@ -92,9 +92,10 @@ class ActivityControllerTest extends APIControllerBaseTest
yield ['/api/activities', ['globals' => 'true', 'visible' => '2'], [[false]]];
yield ['/api/activities', ['globals' => 'true', 'visible' => 1], [[false], [false]]];
yield ['/api/activities', ['project' => '1'], [[false], [false], [true, 1]]];
yield ['/api/activities', ['project' => '2', 'visible' => 1], [[false], [true, 2], [true, 2], [false]]];
yield ['/api/activities', ['project' => '2', 'visible' => '3'], [[false], [true, 2], [true, 2], [true, 2], [false], [false]]];
yield ['/api/activities', ['project' => '2', 'visible' => 2], [[true, 2], [false]]];
yield ['/api/activities', ['project' => '2', 'projects' => '2', 'visible' => 1], [[false], [true, 2], [true, 2], [false]]];
yield ['/api/activities', ['project' => '2', 'projects' => '2,2', 'visible' => '3'], [[false], [true, 2], [true, 2], [true, 2], [false], [false]]];
yield ['/api/activities', ['projects' => '2,2', 'visible' => 2], [[true, 2], [false]]];
yield ['/api/activities', ['projects' => '2', 'visible' => 2], [[true, 2], [false]]];
}
public function testGetCollectionWithQuery()

View File

@@ -104,8 +104,12 @@ class ProjectControllerTest extends APIControllerBaseTest
// customer is invisible, so nothing should be returned
yield ['/api/projects', ['customer' => '2', 'visible' => VisibilityInterface::SHOW_VISIBLE], []];
yield ['/api/projects', ['customer' => '2', 'visible' => VisibilityInterface::SHOW_BOTH], [[false, 2], [false, 2]]];
yield ['/api/projects', ['customer' => '2', 'customers' => '2', 'visible' => VisibilityInterface::SHOW_BOTH], [[false, 2], [false, 2]]];
yield ['/api/projects', ['customer' => '2', 'customers' => '2,2', 'visible' => VisibilityInterface::SHOW_BOTH], [[false, 2], [false, 2]]];
// customer is invisible, so nothing should be returned
yield ['/api/projects', ['customer' => '2', 'visible' => VisibilityInterface::SHOW_HIDDEN, 'start' => '2010-12-11T23:59:59', 'end' => '2030-12-11T23:59:59'], []];
yield ['/api/projects', ['customers' => '2', 'visible' => VisibilityInterface::SHOW_HIDDEN, 'start' => '2010-12-11T23:59:59', 'end' => '2030-12-11T23:59:59'], []];
yield ['/api/projects', ['customers' => '2,2', 'visible' => VisibilityInterface::SHOW_HIDDEN, 'start' => '2010-12-11T23:59:59', 'end' => '2030-12-11T23:59:59'], []];
}
public function testGetEntity()

View File

@@ -158,9 +158,40 @@ class TimesheetControllerTest extends APIControllerBaseTest
$end->setTime(23, 59, 59);
$query = [
'customer' => 1,
'project' => 1,
'activity' => 1,
'customers' => '1',
'projects' => '1',
'activities' => '1',
'page' => 2,
'size' => 5,
'order' => 'DESC',
'orderBy' => 'rate',
'active' => 0,
'begin' => $begin->format(self::DATE_FORMAT_HTML5),
'end' => $end->format(self::DATE_FORMAT_HTML5),
'exported' => 0,
];
$client = $this->getClientForAuthenticatedUser(User::ROLE_USER);
$this->assertAccessIsGranted($client, '/api/timesheets', 'GET', $query);
$result = json_decode($client->getResponse()->getContent(), true);
$this->assertIsArray($result);
$this->assertNotEmpty($result);
$this->assertEquals(5, count($result));
$this->assertDefaultStructure($result[0], false);
}
public function testGetCollectionWithDeprecatedQuery()
{
$begin = new \DateTime('first day of this month');
$begin->setTime(0, 0, 0);
$end = new \DateTime('last day of this month');
$end->setTime(23, 59, 59);
$query = [
'customer' => '1',
'project' => '1',
'activity' => '1',
'page' => 2,
'size' => 5,
'order' => 'DESC',

View File

@@ -60,6 +60,8 @@ class ActivityControllerTest extends ControllerBaseTest
'searchTerm' => 'feature:timetracking foo',
'visibility' => 1,
'pageSize' => 50,
'customers' => [1],
'projects' => [1],
'page' => 1,
]);

View File

@@ -194,7 +194,7 @@ class InvoiceControllerTest extends ControllerBaseTest
'template' => 1,
'daterange' => $dateRange,
'customer' => 1,
'project' => 1,
'projects' => [1],
'markAsExported' => 1,
]);
@@ -260,7 +260,7 @@ class InvoiceControllerTest extends ControllerBaseTest
'template' => 1,
'daterange' => $dateRange,
'customer' => 1,
'project' => 1,
'projects' => [1],
'markAsExported' => 1,
]);

View File

@@ -67,6 +67,7 @@ class ProjectControllerTest extends ControllerBaseTest
$client->submit($form, [
'searchTerm' => 'feature:timetracking foo',
'visibility' => 1,
'customers' => [1],
'pageSize' => 50,
'page' => 1,
]);

View File

@@ -69,7 +69,9 @@ class TimesheetControllerTest extends ControllerBaseTest
'state' => 1,
'pageSize' => 25,
'daterange' => $dateRange,
'customer' => null,
'customers' => [1],
'projects' => [1],
'activities' => [1],
]);
$this->assertTrue($client->getResponse()->isSuccessful());
@@ -141,7 +143,7 @@ class TimesheetControllerTest extends ControllerBaseTest
'state' => 1,
'pageSize' => 25,
'daterange' => $dateRange,
'customer' => null,
'customers' => [],
]);
$this->assertTrue($client->getResponse()->isSuccessful());

View File

@@ -74,7 +74,7 @@ class TimesheetTeamControllerTest extends ControllerBaseTest
'users' => [$user->getId()],
'pageSize' => 25,
'daterange' => $dateRange,
'customer' => null,
'customers' => [],
]);
$this->assertTrue($client->getResponse()->isSuccessful());
@@ -151,7 +151,7 @@ class TimesheetTeamControllerTest extends ControllerBaseTest
'state' => 1,
'pageSize' => 25,
'daterange' => $dateRange,
'customer' => null,
'customers' => [],
]);
$this->assertTrue($client->getResponse()->isSuccessful());

View File

@@ -167,10 +167,10 @@ abstract class AbstractRendererTest extends KernelTestCase
$entries = [$timesheet, $timesheet2, $timesheet3, $timesheet4, $timesheet5];
$query = new TimesheetQuery();
$query->setActivity($activity);
$query->setActivities([$activity]);
$query->setBegin(new \DateTime());
$query->setEnd(new \DateTime());
$query->setProject($project);
$query->setProjects([$project]);
return $renderer->render($entries, $query);
}

View File

@@ -167,10 +167,10 @@ abstract class AbstractRendererTest extends KernelTestCase
$entries = [$timesheet, $timesheet2, $timesheet3, $timesheet4, $timesheet5];
$query = new TimesheetQuery();
$query->setActivity($activity);
$query->setActivities([$activity]);
$query->setBegin(new \DateTime());
$query->setEnd(new \DateTime());
$query->setProject($project);
$query->setProjects([$project]);
return $renderer->render($entries, $query);
}

View File

@@ -73,9 +73,9 @@ abstract class AbstractCalculatorTest extends TestCase
$query = new InvoiceQuery();
if ($addProject === true) {
$query->setProject($project);
$query->setProjects([$project]);
} elseif ($addActivity === true) {
$query->setActivity($activity);
$query->setActivities([$activity]);
}
$timesheet = new Timesheet();

View File

@@ -105,7 +105,7 @@ class DateInvoiceCalculatorTest extends AbstractCalculatorTest
$entries = [$timesheet, $timesheet2, $timesheet3, $timesheet4, $timesheet5];
$query = new InvoiceQuery();
$query->setProject($project1);
$query->setProjects([$project1]);
$model = new InvoiceModel(new DebugFormatter());
$model->setCustomer($customer);

View File

@@ -105,7 +105,7 @@ class ProjectInvoiceCalculatorTest extends AbstractCalculatorTest
$entries = [$timesheet, $timesheet2, $timesheet3, $timesheet4, $timesheet5];
$query = new InvoiceQuery();
$query->setProject($project1);
$query->setProjects([$project1]);
$model = new InvoiceModel(new DebugFormatter());
$model->setCustomer($customer);

View File

@@ -29,7 +29,7 @@ class InvoiceModelActivityHydratorTest extends TestCase
$result = $sut->hydrate($model);
$this->assertModelStructure($result);
$model->getQuery()->setActivity(null);
$model->getQuery()->setActivities([]);
$result = $sut->hydrate($model);
self::assertEmpty($result);
}
@@ -41,6 +41,10 @@ class InvoiceModelActivityHydratorTest extends TestCase
'activity.name',
'activity.comment',
'activity.meta.foo-activity',
'activity.1.id',
'activity.1.name',
'activity.1.comment',
'activity.1.meta.foo-activity',
];
$givenKeys = array_keys($model);

View File

@@ -29,7 +29,7 @@ class InvoiceModelProjectHydratorTest extends TestCase
$result = $sut->hydrate($model);
$this->assertModelStructure($result);
$model->getQuery()->setProject(null);
$model->getQuery()->setProjects([]);
$result = $sut->hydrate($model);
self::assertEmpty($result);
}
@@ -51,6 +51,20 @@ class InvoiceModelProjectHydratorTest extends TestCase
'project.budget_time',
'project.budget_time_decimal',
'project.budget_time_minutes',
'project.1.id',
'project.1.name',
'project.1.comment',
'project.1.order_date',
'project.1.order_number',
'project.1.meta.foo-project',
'project.1.start_date',
'project.1.end_date',
'project.1.budget_money',
'project.1.budget_money_nc',
'project.1.budget_money_plain',
'project.1.budget_time',
'project.1.budget_time_decimal',
'project.1.budget_time_minutes',
];
$givenKeys = array_keys($model);

View File

@@ -41,7 +41,7 @@ class CsvRendererTest extends TestCase
public function getTestModel()
{
yield [$this->getInvoiceModel(), '€1,947.99', 6, 5, 1, 2, 2];
yield [$this->getInvoiceModel(), '€1,947.99', 6, 4, 1, 2, 2];
yield [$this->getInvoiceModelOneEntry(), '$293.27', 2, 1, 0, 1, 0];
}

View File

@@ -58,7 +58,7 @@ class DebugRendererTest extends TestCase
$response = $sut->render($document, $model);
$data = json_decode($response->getContent(), true);
$this->assertModelStructure($data['model'], $hasProject);
$this->assertModelStructure($data['model'], count($model->getQuery()->getProjects()), count($model->getQuery()->getActivities()));
$rows = $data['entries'];
$this->assertEquals($expectedRows, count($rows));
@@ -74,7 +74,7 @@ class DebugRendererTest extends TestCase
// TODO check values or formats?
}
protected function assertModelStructure(array $model, $hasProject = true)
protected function assertModelStructure(array $model, int $projectCounter = 0, int $activityCounter = 0)
{
$keys = [
'invoice.due_date',
@@ -133,7 +133,16 @@ class DebugRendererTest extends TestCase
'testFromModelHydrator'
];
if ($hasProject) {
if ($activityCounter > 1) {
$keys = array_merge($keys, [
'activity.1.id',
'activity.1.name',
'activity.1.comment',
'activity.1.meta.foo-activity',
]);
}
if ($projectCounter > 0) {
$keys = array_merge($keys, [
'project.id',
'project.name',
@@ -150,6 +159,24 @@ class DebugRendererTest extends TestCase
'project.budget_time_decimal',
'project.budget_time_minutes',
]);
if ($projectCounter > 1) {
$keys = array_merge($keys, [
'project.1.id',
'project.1.name',
'project.1.comment',
'project.1.order_date',
'project.1.order_number',
'project.1.meta.foo-project',
'project.1.start_date',
'project.1.end_date',
'project.1.budget_money',
'project.1.budget_money_nc',
'project.1.budget_money_plain',
'project.1.budget_time',
'project.1.budget_time_decimal',
'project.1.budget_time_minutes',
]);
}
}
$givenKeys = array_keys($model);

View File

@@ -126,6 +126,16 @@ trait RendererTestTrait
$activity->setProject($project);
$activity->setMetaField((new ActivityMeta())->setName('foo-activity')->setValue('bar-activity')->setIsVisible(true));
$project2 = new Project();
$project2->setName('project 2 name');
$project2->setCustomer($customer);
$project2->setMetaField((new ProjectMeta())->setName('foo-project')->setValue('bar-project2')->setIsVisible(true));
$activity2 = new Activity();
$activity2->setName('activity 1 description');
$activity2->setProject($project2);
$activity2->setMetaField((new ActivityMeta())->setName('foo-activity')->setValue('bar-activity2')->setIsVisible(true));
$userMethods = ['getId', 'getPreferenceValue', 'getUsername'];
$user1 = $this->getMockBuilder(User::class)->onlyMethods($userMethods)->disableOriginalConstructor()->getMock();
$user1->method('getId')->willReturn(1);
@@ -165,8 +175,8 @@ trait RendererTestTrait
->setDuration(1800)
->setRate(111.11)
->setUser($user1)
->setActivity($activity)
->setProject($project)
->setActivity($activity2)
->setProject($project2)
->setBegin(new \DateTime())
->setEnd(new \DateTime())
->setMetaField((new TimesheetMeta())->setName('foo-timesheet')->setValue('bar-timesheet1')->setIsVisible(true))
@@ -198,10 +208,10 @@ trait RendererTestTrait
$entries = [$timesheet, $timesheet2, $timesheet3, $timesheet4, $timesheet5];
$query = new InvoiceQuery();
$query->setActivity($activity);
$query->setActivities([$activity, $activity2]);
$query->setBegin(new \DateTime());
$query->setEnd(new \DateTime());
$query->setProject($project);
$query->setProjects([$project, $project2]);
$model = new InvoiceModel($this->getFormatter());
$model->setCustomer($customer);
@@ -272,7 +282,7 @@ trait RendererTestTrait
$entries = [$timesheet];
$query = new InvoiceQuery();
$query->setActivity($activity);
$query->addActivity($activity);
$query->setBegin(new \DateTime());
$query->setEnd(new \DateTime());

View File

@@ -67,6 +67,6 @@ class TwigRendererTest extends KernelTestCase
$this->assertStringContainsString('<h2 class="page-header">
<span contenteditable="true">a very *long* test invoice / template title with [special] character</span>
</h2>', $content);
$this->assertEquals(5, substr_count($content, 'activity description'));
$this->assertEquals(4, substr_count($content, 'activity description'));
}
}

View File

@@ -10,13 +10,13 @@
namespace App\Tests\Repository\Query;
use App\Entity\Activity;
use App\Entity\Project;
use App\Repository\Query\ActivityFormTypeQuery;
/**
* @covers \App\Repository\Query\ActivityFormTypeQuery
* @covers \App\Repository\Query\BaseFormTypeQuery
*/
class ActivityFormTypeQueryTest extends BaseQueryTest
class ActivityFormTypeQueryTest extends BaseFormTypeQueryTest
{
public function testQuery()
{
@@ -24,15 +24,7 @@ class ActivityFormTypeQueryTest extends BaseQueryTest
self::assertTrue($sut->isGlobalsOnly());
$project = new Project();
self::assertNull($sut->getProject());
self::assertInstanceOf(ActivityFormTypeQuery::class, $sut->setProject($project));
self::assertSame($project, $sut->getProject());
$activity = new Activity();
self::assertNull($sut->getActivity());
self::assertInstanceOf(ActivityFormTypeQuery::class, $sut->setActivity($activity));
self::assertSame($activity, $sut->getActivity());
$this->assertBaseQuery($sut);
self::assertFalse($sut->isGlobalsOnly());

View File

@@ -9,13 +9,12 @@
namespace App\Tests\Repository\Query;
use App\Entity\Customer;
use App\Entity\Project;
use App\Repository\Query\ActivityQuery;
use App\Repository\Query\VisibilityInterface;
/**
* @covers \App\Repository\Query\ActivityQuery
* @covers \App\Repository\Query\BaseQuery
*/
class ActivityQueryTest extends BaseQueryTest
{
@@ -26,27 +25,8 @@ class ActivityQueryTest extends BaseQueryTest
$this->assertBaseQuery($sut, 'name');
$this->assertInstanceOf(VisibilityInterface::class, $sut);
$this->assertNull($sut->getCustomer());
$this->assertNull($sut->getProject());
$expected = new Customer();
$expected->setName('foo-bar');
$sut->setCustomer($expected);
$this->assertEquals($expected, $sut->getCustomer());
$expected = new Project();
$expected->setName('foo-bar');
$sut->setProject($expected);
$this->assertEquals($expected, $sut->getProject());
// make sure int is allowed as well
$sut->setProject(99);
$this->assertEquals(99, $sut->getProject());
$sut->setCustomer(99);
$this->assertEquals(99, $sut->getCustomer());
$this->assertCustomer($sut);
$this->assertProject($sut);
$this->assertResetByFormError(new ActivityQuery(), 'name');
}

View File

@@ -0,0 +1,159 @@
<?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\Tests\Repository\Query;
use App\Entity\Activity;
use App\Entity\Customer;
use App\Entity\Project;
use App\Entity\Team;
use App\Entity\User;
use App\Repository\Query\BaseFormTypeQuery;
use PHPUnit\Framework\TestCase;
/**
* @covers \App\Repository\Query\BaseQuery
*/
abstract class BaseFormTypeQueryTest extends TestCase
{
protected function assertBaseQuery(BaseFormTypeQuery $sut)
{
$this->assertActivity($sut);
$this->assertProject($sut);
$this->assertCustomer($sut);
$this->assertTeams($sut);
$this->assertUser($sut);
}
protected function assertUser(BaseFormTypeQuery $sut)
{
self::assertEmpty($sut->getUser());
$user = new User();
self::assertInstanceOf(BaseFormTypeQuery::class, $sut->setUser($user));
self::assertSame($user, $sut->getUser());
}
protected function assertTeams(BaseFormTypeQuery $sut)
{
self::assertEmpty($sut->getTeams());
self::assertInstanceOf(BaseFormTypeQuery::class, $sut->addTeam(new Team()));
self::assertEquals(1, count($sut->getTeams()));
$team = new Team();
self::assertInstanceOf(BaseFormTypeQuery::class, $sut->addTeam($team));
self::assertEquals(1, count($sut->getTeams()));
self::assertSame($team, $sut->getTeams()[0]);
}
protected function assertActivity(BaseFormTypeQuery $sut)
{
$this->assertNull($sut->getActivity());
$this->assertEquals([], $sut->getActivities());
$this->assertFalse($sut->hasActivities());
$expected = new Activity();
$expected->setName('foo-bar');
$sut->setActivity($expected);
$this->assertEquals($expected, $sut->getActivity());
$sut->setActivities([]);
$this->assertEquals([], $sut->getActivities());
$sut->addActivity($expected);
$this->assertEquals([$expected], $sut->getActivities());
$this->assertTrue($sut->hasActivities());
$expected2 = new Activity();
$expected2->setName('foo-bar2');
$sut->addActivity($expected2);
$this->assertEquals([$expected, $expected2], $sut->getActivities());
$sut->setActivity(null);
$this->assertNull($sut->getActivity());
$this->assertFalse($sut->hasActivities());
// make sure int is allowed as well
$sut->setActivities([99]);
$this->assertEquals(99, $sut->getActivity());
$this->assertEquals([99], $sut->getActivities());
}
protected function assertCustomer(BaseFormTypeQuery $sut)
{
$this->assertNull($sut->getCustomer());
$this->assertEquals([], $sut->getCustomers());
$this->assertFalse($sut->hasCustomers());
$expected = new Customer();
$expected->setName('foo-bar');
$sut->setCustomer($expected);
$this->assertEquals($expected, $sut->getCustomer());
$sut->setCustomers([]);
$this->assertEquals([], $sut->getCustomers());
$sut->addCustomer($expected);
$this->assertEquals([$expected], $sut->getCustomers());
$this->assertTrue($sut->hasCustomers());
$expected2 = new Customer();
$expected2->setName('foo-bar2');
$sut->addCustomer($expected2);
$this->assertEquals([$expected, $expected2], $sut->getCustomers());
$sut->setCustomer(null);
$this->assertNull($sut->getCustomer());
$this->assertFalse($sut->hasCustomers());
// make sure int is allowed as well
$sut->setCustomers([99]);
$this->assertEquals(99, $sut->getCustomer());
$this->assertEquals([99], $sut->getCustomers());
}
protected function assertProject(BaseFormTypeQuery $sut)
{
$this->assertNull($sut->getProject());
$this->assertEquals([], $sut->getProjects());
$this->assertFalse($sut->hasProjects());
$expected = new Project();
$expected->setName('foo-bar');
$sut->setProject($expected);
$this->assertEquals($expected, $sut->getProject());
$sut->setProjects([]);
$this->assertEquals([], $sut->getProjects());
$sut->addProject($expected);
$this->assertEquals([$expected], $sut->getProjects());
$this->assertTrue($sut->hasProjects());
$expected2 = new Project();
$expected2->setName('foo-bar2');
$sut->addProject($expected2);
$this->assertEquals([$expected, $expected2], $sut->getProjects());
$sut->setProject(null);
$this->assertNull($sut->getProject());
$this->assertFalse($sut->hasProjects());
// make sure int is allowed as well
$sut->setProjects([99]);
$this->assertEquals(99, $sut->getProject());
$this->assertEquals([99], $sut->getProjects());
}
}

View File

@@ -9,8 +9,14 @@
namespace App\Tests\Repository\Query;
use App\Entity\Activity;
use App\Entity\Customer;
use App\Entity\Project;
use App\Entity\Team;
use App\Repository\Query\ActivityQuery;
use App\Repository\Query\BaseQuery;
use App\Repository\Query\ProjectQuery;
use App\Repository\Query\TimesheetQuery;
use App\Utils\SearchTerm;
use PHPUnit\Framework\TestCase;
use Symfony\Component\EventDispatcher\EventDispatcher;
@@ -164,4 +170,109 @@ class BaseQueryTest extends TestCase
self::assertEquals('foo bar', $term->getOriginalSearch());
self::assertSame($term, $sut->getSearchTerm());
}
protected function assertActivity(TimesheetQuery $sut)
{
$this->assertNull($sut->getActivity());
$this->assertEquals([], $sut->getActivities());
$this->assertFalse($sut->hasActivities());
$expected = new Activity();
$expected->setName('foo-bar');
$sut->setActivity($expected);
$this->assertEquals($expected, $sut->getActivity());
$sut->setActivities([]);
$this->assertEquals([], $sut->getActivities());
$sut->addActivity($expected);
$this->assertEquals([$expected], $sut->getActivities());
$this->assertTrue($sut->hasActivities());
$expected2 = new Activity();
$expected2->setName('foo-bar2');
$sut->addActivity($expected2);
$this->assertEquals([$expected, $expected2], $sut->getActivities());
$sut->setActivity(null);
$this->assertNull($sut->getActivity());
$this->assertFalse($sut->hasActivities());
// make sure int is allowed as well
$sut->setActivities([99]);
$this->assertEquals(99, $sut->getActivity());
$this->assertEquals([99], $sut->getActivities());
}
protected function assertCustomer(ProjectQuery $sut)
{
$this->assertNull($sut->getCustomer());
$this->assertEquals([], $sut->getCustomers());
$this->assertFalse($sut->hasCustomers());
$expected = new Customer();
$expected->setName('foo-bar');
$sut->setCustomer($expected);
$this->assertEquals($expected, $sut->getCustomer());
$sut->setCustomers([]);
$this->assertEquals([], $sut->getCustomers());
$sut->addCustomer($expected);
$this->assertEquals([$expected], $sut->getCustomers());
$this->assertTrue($sut->hasCustomers());
$expected2 = new Customer();
$expected2->setName('foo-bar2');
$sut->addCustomer($expected2);
$this->assertEquals([$expected, $expected2], $sut->getCustomers());
$sut->setCustomer(null);
$this->assertNull($sut->getCustomer());
$this->assertFalse($sut->hasCustomers());
// make sure int is allowed as well
$sut->setCustomers([99]);
$this->assertEquals(99, $sut->getCustomer());
$this->assertEquals([99], $sut->getCustomers());
}
protected function assertProject(ActivityQuery $sut)
{
$this->assertNull($sut->getProject());
$this->assertEquals([], $sut->getProjects());
$this->assertFalse($sut->hasProjects());
$expected = new Project();
$expected->setName('foo-bar');
$sut->setProject($expected);
$this->assertEquals($expected, $sut->getProject());
$sut->setProjects([]);
$this->assertEquals([], $sut->getProjects());
$sut->addProject($expected);
$this->assertEquals([$expected], $sut->getProjects());
$this->assertTrue($sut->hasProjects());
$expected2 = new Project();
$expected2->setName('foo-bar2');
$sut->addProject($expected2);
$this->assertEquals([$expected, $expected2], $sut->getProjects());
$sut->setProject(null);
$this->assertNull($sut->getProject());
$this->assertFalse($sut->hasProjects());
// make sure int is allowed as well
$sut->setProjects([99]);
$this->assertEquals(99, $sut->getProject());
$this->assertEquals([99], $sut->getProjects());
}
}

View File

@@ -10,36 +10,23 @@
namespace App\Tests\Repository\Query;
use App\Entity\Customer;
use App\Entity\Team;
use App\Entity\User;
use App\Repository\Query\CustomerFormTypeQuery;
/**
* @covers \App\Repository\Query\CustomerFormTypeQuery
* @covers \App\Repository\Query\BaseFormTypeQuery
*/
class CustomerFormTypeQueryTest extends BaseQueryTest
class CustomerFormTypeQueryTest extends BaseFormTypeQueryTest
{
public function testQuery()
{
$sut = new CustomerFormTypeQuery();
self::assertEmpty($sut->getTeams());
self::assertInstanceOf(CustomerFormTypeQuery::class, $sut->addTeam(new Team()));
self::assertCount(1, $sut->getTeams());
$customer = new Customer();
self::assertNull($sut->getCustomer());
self::assertInstanceOf(CustomerFormTypeQuery::class, $sut->setCustomer($customer));
self::assertSame($customer, $sut->getCustomer());
$this->assertBaseQuery($sut);
$customer = new Customer();
self::assertNull($sut->getCustomerToIgnore());
self::assertInstanceOf(CustomerFormTypeQuery::class, $sut->setCustomerToIgnore($customer));
self::assertSame($customer, $sut->getCustomerToIgnore());
$user = new User();
self::assertNull($sut->getUser());
self::assertInstanceOf(CustomerFormTypeQuery::class, $sut->setUser($user));
self::assertSame($user, $sut->getUser());
}
}

View File

@@ -9,16 +9,13 @@
namespace App\Tests\Repository\Query;
use App\Entity\Activity;
use App\Entity\Customer;
use App\Entity\Project;
use App\Entity\User;
use App\Repository\Query\ExportQuery;
/**
* @covers \App\Repository\Query\ExportQuery
* @covers \App\Repository\Query\TimesheetQuery
*/
class ExportQueryTest extends BaseQueryTest
class ExportQueryTest extends TimesheetQueryTest
{
public function testQuery()
{
@@ -47,80 +44,6 @@ class ExportQueryTest extends BaseQueryTest
$this->assertTrue($sut->isMarkAsExported());
}
protected function assertUser(ExportQuery $sut)
{
$this->assertNull($sut->getUser());
$expected = new User();
$expected->setUsername('foo-bar');
$sut->setUser($expected);
$this->assertEquals($expected, $sut->getUser());
}
protected function assertCustomer(ExportQuery $sut)
{
$this->assertNull($sut->getCustomer());
$expected = new Customer();
$expected->setName('foo-bar');
$sut->setCustomer($expected);
$this->assertEquals($expected, $sut->getCustomer());
}
protected function assertProject(ExportQuery $sut)
{
$this->assertNull($sut->getProject());
$expected = new Project();
$expected->setName('foo-bar');
$sut->setProject($expected);
$this->assertEquals($expected, $sut->getProject());
}
protected function assertActivity(ExportQuery $sut)
{
$this->assertNull($sut->getActivity());
$expected = new Activity();
$expected->setName('foo-bar');
$sut->setActivity($expected);
$this->assertEquals($expected, $sut->getActivity());
}
protected function assertState(ExportQuery $sut)
{
$this->assertEquals(ExportQuery::STATE_ALL, $sut->getState());
$sut->setState(PHP_INT_MAX);
$this->assertEquals(ExportQuery::STATE_ALL, $sut->getState());
$sut->setState(ExportQuery::STATE_STOPPED);
$this->assertEquals(ExportQuery::STATE_STOPPED, $sut->getState());
$sut->setState(ExportQuery::STATE_RUNNING);
$this->assertEquals(ExportQuery::STATE_RUNNING, $sut->getState());
$sut->setState(ExportQuery::STATE_ALL);
$this->assertEquals(ExportQuery::STATE_ALL, $sut->getState());
}
protected function assertExported(ExportQuery $sut)
{
$this->assertEquals(ExportQuery::STATE_ALL, $sut->getExported());
$sut->setExported(PHP_INT_MAX);
$this->assertEquals(ExportQuery::STATE_ALL, $sut->getExported());
$sut->setExported(ExportQuery::STATE_EXPORTED);
$this->assertEquals(ExportQuery::STATE_EXPORTED, $sut->getExported());
$sut->setExported(ExportQuery::STATE_NOT_EXPORTED);
$this->assertEquals(ExportQuery::STATE_NOT_EXPORTED, $sut->getExported());
$sut->setExported(ExportQuery::STATE_ALL);
$this->assertEquals(ExportQuery::STATE_ALL, $sut->getExported());
}
protected function assertType(ExportQuery $sut)
{
$this->assertNull($sut->getType());

View File

@@ -9,16 +9,13 @@
namespace App\Tests\Repository\Query;
use App\Entity\Activity;
use App\Entity\Customer;
use App\Entity\Project;
use App\Entity\User;
use App\Repository\Query\InvoiceQuery;
/**
* @covers \App\Repository\Query\InvoiceQuery
* @covers \App\Repository\Query\TimesheetQuery
*/
class InvoiceQueryTest extends BaseQueryTest
class InvoiceQueryTest extends TimesheetQueryTest
{
public function testQuery()
{
@@ -45,78 +42,4 @@ class InvoiceQueryTest extends BaseQueryTest
$sut->setMarkAsExported(true);
$this->assertTrue($sut->isMarkAsExported());
}
protected function assertUser(InvoiceQuery $sut)
{
$this->assertNull($sut->getUser());
$expected = new User();
$expected->setUsername('foo-bar');
$sut->setUser($expected);
$this->assertEquals($expected, $sut->getUser());
}
protected function assertCustomer(InvoiceQuery $sut)
{
$this->assertNull($sut->getCustomer());
$expected = new Customer();
$expected->setName('foo-bar');
$sut->setCustomer($expected);
$this->assertEquals($expected, $sut->getCustomer());
}
protected function assertProject(InvoiceQuery $sut)
{
$this->assertNull($sut->getProject());
$expected = new Project();
$expected->setName('foo-bar');
$sut->setProject($expected);
$this->assertEquals($expected, $sut->getProject());
}
protected function assertActivity(InvoiceQuery $sut)
{
$this->assertNull($sut->getActivity());
$expected = new Activity();
$expected->setName('foo-bar');
$sut->setActivity($expected);
$this->assertEquals($expected, $sut->getActivity());
}
protected function assertState(InvoiceQuery $sut)
{
$this->assertEquals(InvoiceQuery::STATE_ALL, $sut->getState());
$sut->setState(PHP_INT_MAX);
$this->assertEquals(InvoiceQuery::STATE_ALL, $sut->getState());
$sut->setState(InvoiceQuery::STATE_STOPPED);
$this->assertEquals(InvoiceQuery::STATE_STOPPED, $sut->getState());
$sut->setState(InvoiceQuery::STATE_RUNNING);
$this->assertEquals(InvoiceQuery::STATE_RUNNING, $sut->getState());
$sut->setState(InvoiceQuery::STATE_ALL);
$this->assertEquals(InvoiceQuery::STATE_ALL, $sut->getState());
}
protected function assertExported(InvoiceQuery $sut)
{
$this->assertEquals(InvoiceQuery::STATE_ALL, $sut->getExported());
$sut->setExported(PHP_INT_MAX);
$this->assertEquals(InvoiceQuery::STATE_ALL, $sut->getExported());
$sut->setExported(InvoiceQuery::STATE_EXPORTED);
$this->assertEquals(InvoiceQuery::STATE_EXPORTED, $sut->getExported());
$sut->setExported(InvoiceQuery::STATE_NOT_EXPORTED);
$this->assertEquals(InvoiceQuery::STATE_NOT_EXPORTED, $sut->getExported());
$sut->setExported(InvoiceQuery::STATE_ALL);
$this->assertEquals(InvoiceQuery::STATE_ALL, $sut->getExported());
}
}

View File

@@ -9,43 +9,24 @@
namespace App\Tests\Repository\Query;
use App\Entity\Customer;
use App\Entity\Project;
use App\Entity\Team;
use App\Entity\User;
use App\Repository\Query\ProjectFormTypeQuery;
/**
* @covers \App\Repository\Query\ProjectFormTypeQuery
* @covers \App\Repository\Query\BaseFormTypeQuery
*/
class ProjectFormTypeQueryTest extends BaseQueryTest
class ProjectFormTypeQueryTest extends BaseFormTypeQueryTest
{
public function testQuery()
{
$sut = new ProjectFormTypeQuery();
self::assertEmpty($sut->getTeams());
self::assertInstanceOf(ProjectFormTypeQuery::class, $sut->addTeam(new Team()));
self::assertCount(1, $sut->getTeams());
$project = new Project();
self::assertNull($sut->getProject());
self::assertInstanceOf(ProjectFormTypeQuery::class, $sut->setProject($project));
self::assertSame($project, $sut->getProject());
$this->assertBaseQuery($sut);
$project = new Project();
self::assertNull($sut->getProjectToIgnore());
self::assertInstanceOf(ProjectFormTypeQuery::class, $sut->setProjectToIgnore($project));
self::assertSame($project, $sut->getProjectToIgnore());
$customer = new Customer();
self::assertNull($sut->getCustomer());
self::assertInstanceOf(ProjectFormTypeQuery::class, $sut->setCustomer($customer));
self::assertSame($customer, $sut->getCustomer());
$user = new User();
self::assertNull($sut->getUser());
self::assertInstanceOf(ProjectFormTypeQuery::class, $sut->setUser($user));
self::assertSame($user, $sut->getUser());
}
}

View File

@@ -9,12 +9,12 @@
namespace App\Tests\Repository\Query;
use App\Entity\Customer;
use App\Repository\Query\ProjectQuery;
use App\Repository\Query\VisibilityInterface;
/**
* @covers \App\Repository\Query\ProjectQuery
* @covers \App\Repository\Query\BaseQuery
*/
class ProjectQueryTest extends BaseQueryTest
{
@@ -25,17 +25,7 @@ class ProjectQueryTest extends BaseQueryTest
$this->assertBaseQuery($sut, 'name');
$this->assertInstanceOf(VisibilityInterface::class, $sut);
$this->assertNull($sut->getCustomer());
$expected = new Customer();
$expected->setName('foo-bar');
$sut->setCustomer($expected);
$this->assertEquals($expected, $sut->getCustomer());
// make sure int is allowed as well
$sut->setCustomer(99);
$this->assertEquals(99, $sut->getCustomer());
$this->assertCustomer($sut);
$this->assertResetByFormError(new ProjectQuery(), 'name');

View File

@@ -9,21 +9,18 @@
namespace App\Tests\Repository\Query;
use App\Entity\User;
use App\Repository\Query\TagFormTypeQuery;
/**
* @covers \App\Repository\Query\TagFormTypeQuery
* @covers \App\Repository\Query\BaseFormTypeQuery
*/
class TagFormTypeQueryTest extends BaseQueryTest
class TagFormTypeQueryTest extends BaseFormTypeQueryTest
{
public function testQuery()
{
$sut = new TagFormTypeQuery();
$user = new User();
self::assertNull($sut->getUser());
self::assertInstanceOf(TagFormTypeQuery::class, $sut->setUser($user));
self::assertSame($user, $sut->getUser());
$this->assertBaseQuery($sut);
}
}

View File

@@ -9,6 +9,7 @@
namespace App\Tests\Repository\Query;
use App\Entity\User;
use App\Repository\Query\TeamQuery;
/**
@@ -23,6 +24,32 @@ class TeamQueryTest extends BaseQueryTest
$this->assertBaseQuery($sut, 'name');
$this->assertInstanceOf(TeamQuery::class, $sut);
$this->assertUsers($sut);
$this->assertResetByFormError(new TeamQuery(), 'name');
}
protected function assertUsers(TeamQuery $sut)
{
$this->assertEmpty($sut->getUsers());
$user = $this->createMock(User::class);
$user->method('getId')->willReturn(1);
$sut->addUser($user);
$user = $this->createMock(User::class);
$user->method('getId')->willReturn(1);
$sut->addUser($user);
$user = $this->createMock(User::class);
$user->method('getId')->willReturn(13);
$sut->addUser($user);
$user = $this->createMock(User::class);
$user->method('getId')->willReturn(27);
$sut->addUser($user);
$sut->removeUser($user);
$this->assertCount(2, $sut->getUsers());
}
}

View File

@@ -9,9 +9,6 @@
namespace App\Tests\Repository\Query;
use App\Entity\Activity;
use App\Entity\Customer;
use App\Entity\Project;
use App\Entity\User;
use App\Repository\Query\TimesheetQuery;
@@ -76,36 +73,6 @@ class TimesheetQueryTest extends BaseQueryTest
$this->assertCount(2, $sut->getUsers());
}
protected function assertCustomer(TimesheetQuery $sut)
{
$this->assertNull($sut->getCustomer());
$expected = new Customer();
$expected->setName('foo-bar');
$sut->setCustomer($expected);
$this->assertEquals($expected, $sut->getCustomer());
}
protected function assertProject(TimesheetQuery $sut)
{
$this->assertNull($sut->getProject());
$expected = new Project();
$expected->setName('foo-bar');
$sut->setProject($expected);
$this->assertEquals($expected, $sut->getProject());
}
protected function assertActivity(TimesheetQuery $sut)
{
$this->assertNull($sut->getActivity());
$expected = new Activity();
$expected->setName('foo-bar');
$sut->setActivity($expected);
$this->assertEquals($expected, $sut->getActivity());
}
protected function assertState(TimesheetQuery $sut)
{
$this->assertEquals(TimesheetQuery::STATE_ALL, $sut->getState());

View File

@@ -9,26 +9,18 @@
namespace App\Tests\Repository\Query;
use App\Entity\Team;
use App\Entity\User;
use App\Repository\Query\UserFormTypeQuery;
/**
* @covers \App\Repository\Query\UserFormTypeQuery
* @covers \App\Repository\Query\BaseFormTypeQuery
*/
class UserFormTypeQueryTest extends BaseQueryTest
class UserFormTypeQueryTest extends BaseFormTypeQueryTest
{
public function testQuery()
{
$sut = new UserFormTypeQuery();
self::assertEmpty($sut->getTeams());
self::assertInstanceOf(UserFormTypeQuery::class, $sut->addTeam(new Team()));
self::assertCount(1, $sut->getTeams());
$user = new User();
self::assertNull($sut->getUser());
self::assertInstanceOf(UserFormTypeQuery::class, $sut->setUser($user));
self::assertSame($user, $sut->getUser());
$this->assertBaseQuery($sut);
}
}