Added delete user feature #225 (#249)

This commit is contained in:
Kevin Papst
2018-07-31 13:16:40 +02:00
committed by GitHub
parent 0996eca3c5
commit a8a8424ced
29 changed files with 879 additions and 49 deletions

View File

@@ -42,7 +42,7 @@ Our roadmap is open for changes and input from the community, please [sent us](i
There are [further infos about installation](var/docs/installation.md) if you have to use FTP or want to develop with Kimai. There are [further infos about installation](var/docs/installation.md) if you have to use FTP or want to develop with Kimai.
If you want to install Kimai v2 in your production environment, then SSH into your server and change to your webserevr root. If you want to install Kimai 2 in your production environment, then SSH into your server and change to your webserver root.
You need to install Git and [Composer](https://getcomposer.org/doc/00-intro.md) if you haven't already. You need to install Git and [Composer](https://getcomposer.org/doc/00-intro.md) if you haven't already.
First clone this repo: First clone this repo:

View File

@@ -1,5 +1,10 @@
# Upgrading Kimai 2 # Upgrading Kimai 2
Database upgrades are currently ONLY provided for MySQL/MariaDB and SQLite.
If you plan on using e.g. PostgreSQL, please read more about the `bin/console doctrine:migrations:diff` and
`bin/console doctrine:migrations:migrate` commands and contact us, so we can integrate them into the official releases.
## [0.3](https://github.com/kevinpapst/kimai2/releases/tag/0.3) (2018-07-22) ## [0.3](https://github.com/kevinpapst/kimai2/releases/tag/0.3) (2018-07-22)
**Update from 0.2:** **Update from 0.2:**

View File

@@ -122,7 +122,7 @@ class ActivityController extends AbstractController
$this->flashSuccess('action.deleted_successfully'); $this->flashSuccess('action.deleted_successfully');
return $this->redirectToRoute('admin_activity', ['id' => $activity->getId()]); return $this->redirectToRoute('admin_activity');
} }
return $this->render( return $this->render(
@@ -160,7 +160,7 @@ class ActivityController extends AbstractController
$editForm->get('create_more')->setData(true); $editForm->get('create_more')->setData(true);
$activity = $newActivity; $activity = $newActivity;
} else { } else {
return $this->redirectToRoute('admin_activity', ['id' => $activity->getId()]); return $this->redirectToRoute('admin_activity');
} }
} }

View File

@@ -101,7 +101,7 @@ class CustomerController extends AbstractController
$this->flashSuccess('action.updated_successfully'); $this->flashSuccess('action.updated_successfully');
return $this->redirectToRoute('admin_customer', ['id' => $customer->getId()]); return $this->redirectToRoute('admin_customer');
} }
return $this->render('admin/customer_edit.html.twig', [ return $this->render('admin/customer_edit.html.twig', [
@@ -139,7 +139,7 @@ class CustomerController extends AbstractController
$this->flashSuccess('action.deleted_successfully'); $this->flashSuccess('action.deleted_successfully');
return $this->redirectToRoute('admin_customer', ['id' => $customer->getId()]); return $this->redirectToRoute('admin_customer');
} }
return $this->render('admin/customer_delete.html.twig', [ return $this->render('admin/customer_delete.html.twig', [

View File

@@ -116,7 +116,7 @@ class ProjectController extends AbstractController
$this->flashSuccess('action.deleted_successfully'); $this->flashSuccess('action.deleted_successfully');
return $this->redirectToRoute('admin_project', ['id' => $project->getId()]); return $this->redirectToRoute('admin_project');
} }
return $this->render('admin/project_delete.html.twig', [ return $this->render('admin/project_delete.html.twig', [
@@ -151,7 +151,7 @@ class ProjectController extends AbstractController
$editForm->get('create_more')->setData(true); $editForm->get('create_more')->setData(true);
$project = $newProject; $project = $newProject;
} else { } else {
return $this->redirectToRoute('admin_project', ['id' => $project->getId()]); return $this->redirectToRoute('admin_project');
} }
} }

View File

@@ -10,6 +10,7 @@
namespace App\Controller\Admin; namespace App\Controller\Admin;
use App\Controller\AbstractController; use App\Controller\AbstractController;
use App\Entity\Timesheet;
use App\Entity\User; use App\Entity\User;
use App\Form\Toolbar\UserToolbarForm; use App\Form\Toolbar\UserToolbarForm;
use App\Form\UserCreateType; use App\Form\UserCreateType;
@@ -19,6 +20,7 @@ use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security; use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
/** /**
* Controller used to manage users in the admin part of the site. * Controller used to manage users in the admin part of the site.
@@ -29,6 +31,27 @@ use Symfony\Component\HttpFoundation\Request;
*/ */
class UserController extends AbstractController class UserController extends AbstractController
{ {
/**
* @var UserPasswordEncoderInterface
*/
protected $encoder;
/**
* @param UserPasswordEncoderInterface $encoder
*/
public function __construct(UserPasswordEncoderInterface $encoder)
{
$this->encoder = $encoder;
}
/**
* @return \App\Repository\UserRepository
*/
protected function getRepository()
{
return $this->getDoctrine()->getRepository(User::class);
}
/** /**
* @Route("/", defaults={"page": 1}, name="admin_user") * @Route("/", defaults={"page": 1}, name="admin_user")
* @Route("/page/{page}", requirements={"page": "[1-9]\d*"}, name="admin_user_paginated") * @Route("/page/{page}", requirements={"page": "[1-9]\d*"}, name="admin_user_paginated")
@@ -47,7 +70,7 @@ class UserController extends AbstractController
} }
/* @var $entries Pagerfanta */ /* @var $entries Pagerfanta */
$entries = $this->getDoctrine()->getRepository(User::class)->findByQuery($query); $entries = $this->getRepository()->findByQuery($query);
return $this->render('admin/user.html.twig', [ return $this->render('admin/user.html.twig', [
'entries' => $entries, 'entries' => $entries,
@@ -69,8 +92,7 @@ class UserController extends AbstractController
$editForm->handleRequest($request); $editForm->handleRequest($request);
if ($editForm->isSubmitted() && $editForm->isValid()) { if ($editForm->isSubmitted() && $editForm->isValid()) {
$password = $this->get('security.password_encoder') $password = $this->encoder->encodePassword($user, $user->getPlainPassword());
->encodePassword($user, $user->getPlainPassword());
$user->setPassword($password); $user->setPassword($password);
$user->setEnabled(true); $user->setEnabled(true);
$user->setRoles([User::DEFAULT_ROLE]); $user->setRoles([User::DEFAULT_ROLE]);
@@ -99,6 +121,49 @@ class UserController extends AbstractController
); );
} }
/**
* The route to delete an existing user.
*
* @Route("/{id}/delete", name="admin_user_delete")
* @Method({"GET", "POST"})
* @Security("is_granted('delete', userToDelete)")
*
* @param User $userToDelete
* @param Request $request
* @return \Symfony\Component\HttpFoundation\RedirectResponse|\Symfony\Component\HttpFoundation\Response
* @throws \Doctrine\ORM\NonUniqueResultException
*/
public function deleteAction(User $userToDelete, Request $request)
{
$stats = $this->getDoctrine()->getRepository(Timesheet::class)->getUserStatistics($userToDelete);
$deleteForm = $this->createFormBuilder()
->setAction($this->generateUrl('admin_user_delete', ['id' => $userToDelete->getId()]))
->setMethod('POST')
->getForm();
$deleteForm->handleRequest($request);
if (0 == $stats->getRecordsTotal() || ($deleteForm->isSubmitted() && $deleteForm->isValid())) {
$entityManager = $this->getDoctrine()->getManager();
$entityManager->remove($userToDelete);
$entityManager->flush();
$this->flashSuccess('action.deleted_successfully');
return $this->redirectToRoute('admin_user');
}
return $this->render(
'admin/user_delete.html.twig',
[
'user' => $userToDelete,
'stats' => $stats,
'form' => $deleteForm->createView(),
]
);
}
/** /**
* @param UserQuery $query * @param UserQuery $query
* @return \Symfony\Component\Form\FormInterface * @return \Symfony\Component\Form\FormInterface

View File

@@ -22,6 +22,7 @@ use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security; use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
use Symfony\Component\Form\Form; use Symfony\Component\Form\Form;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
/** /**
* User profile controller * User profile controller
@@ -31,6 +32,19 @@ use Symfony\Component\HttpFoundation\Request;
*/ */
class ProfileController extends AbstractController class ProfileController extends AbstractController
{ {
/**
* @var UserPasswordEncoderInterface
*/
protected $encoder;
/**
* @param UserPasswordEncoderInterface $encoder
*/
public function __construct(UserPasswordEncoderInterface $encoder)
{
$this->encoder = $encoder;
}
/** /**
* @Route("/{username}", name="user_profile") * @Route("/{username}", name="user_profile")
* @Method("GET") * @Method("GET")
@@ -75,8 +89,7 @@ class ProfileController extends AbstractController
$form->handleRequest($request); $form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) { if ($form->isSubmitted() && $form->isValid()) {
$password = $this->get('security.password_encoder') $password = $this->encoder->encodePassword($profile, $profile->getPlainPassword());
->encodePassword($profile, $profile->getPlainPassword());
$profile->setPassword($password); $profile->setPassword($password);
$entityManager = $this->getDoctrine()->getManager(); $entityManager = $this->getDoctrine()->getManager();
@@ -147,11 +160,9 @@ class ProfileController extends AbstractController
} }
} }
foreach ($preferences as $preference) { $profile->setPreferences($preferences);
$preference->setUser($profile); $entityManager->persist($profile);
$entityManager->persist($preference); $entityManager->flush();
$entityManager->flush();
}
$this->flashSuccess('action.updated_successfully'); $this->flashSuccess('action.updated_successfully');

View File

@@ -69,6 +69,14 @@ class Activity
*/ */
private $timesheets; private $timesheets;
/**
* @return Timesheet[]
*/
public function getTimesheets(): array
{
return $this->timesheets;
}
/** /**
* @return Project * @return Project
*/ */

View File

@@ -64,7 +64,7 @@ class Timesheet
* @var User * @var User
* *
* @ORM\ManyToOne(targetEntity="App\Entity\User") * @ORM\ManyToOne(targetEntity="App\Entity\User")
* @ORM\JoinColumn(name="user", referencedColumnName="id") * @ORM\JoinColumn(name="user", referencedColumnName="id", onDelete="CASCADE")
* @Assert\NotNull() * @Assert\NotNull()
*/ */
private $user; private $user;

View File

@@ -191,12 +191,13 @@ class User extends BaseUser implements UserInterface
* @param UserPreference[]|Collection<UserPreference> $preferences * @param UserPreference[]|Collection<UserPreference> $preferences
* @return User * @return User
*/ */
public function setPreferences(array $preferences) public function setPreferences($preferences)
{ {
if (!($preferences instanceof Collection) && is_array($preferences)) { $this->preferences = new ArrayCollection();
$preferences = new ArrayCollection($preferences);
foreach ($preferences as $preference) {
$this->addPreference($preference);
} }
$this->preferences = $preferences;
return $this; return $this;
} }
@@ -238,6 +239,7 @@ class User extends BaseUser implements UserInterface
public function addPreference(UserPreference $preference) public function addPreference(UserPreference $preference)
{ {
$this->preferences->add($preference); $this->preferences->add($preference);
$preference->setUser($this);
return $this; return $this;
} }

View File

@@ -0,0 +1,92 @@
<?php
declare(strict_types=1);
/*
* 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 DoctrineMigrations;
use App\Doctrine\AbstractMigration;
use Doctrine\DBAL\Schema\Index;
use Doctrine\DBAL\Schema\Schema;
/**
* Add constraints for the "delete user" feature.
*/
final class Version20180730044139 extends AbstractMigration
{
/**
* @var Index[]
*/
protected $indexesOld = [];
/**
* @param Schema $schema
* @throws \Doctrine\DBAL\DBALException
* @throws \Doctrine\DBAL\Migrations\AbortMigrationException
*/
public function up(Schema $schema): void
{
$platform = $this->getPlatform();
if (!in_array($platform, ['sqlite', 'mysql'])) {
$this->abortIf(true, 'Unsupported database platform: ' . $platform);
}
$timesheet = $this->getTableName('timesheet');
$user = $this->getTableName('users');
$activity = $this->getTableName('activities');
if ($platform === 'sqlite') {
$this->addSql('DROP INDEX IDX_4F60C6B181C06096');
$this->addSql('DROP INDEX IDX_4F60C6B18D93D649');
$this->addSql('CREATE TEMPORARY TABLE __temp__' . $timesheet . ' AS SELECT id, user, activity_id, start_time, end_time, duration, description, rate FROM ' . $timesheet);
$this->addSql('DROP TABLE ' . $timesheet);
$this->addSql('CREATE TABLE ' . $timesheet . ' (id INTEGER NOT NULL, user INTEGER DEFAULT NULL, activity_id INTEGER DEFAULT NULL, start_time DATETIME NOT NULL, end_time DATETIME DEFAULT NULL, duration INTEGER DEFAULT NULL, description CLOB DEFAULT NULL COLLATE BINARY, rate NUMERIC(10, 2) NOT NULL, PRIMARY KEY(id), CONSTRAINT FK_4F60C6B18D93D649 FOREIGN KEY (user) REFERENCES ' . $user . ' (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_4F60C6B181C06096 FOREIGN KEY (activity_id) REFERENCES ' . $activity . ' (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE)');
$this->addSql('INSERT INTO ' . $timesheet . ' (id, user, activity_id, start_time, end_time, duration, description, rate) SELECT id, user, activity_id, start_time, end_time, duration, description, rate FROM __temp__' . $timesheet);
$this->addSql('DROP TABLE __temp__' . $timesheet);
$this->addSql('CREATE INDEX IDX_4F60C6B181C06096 ON ' . $timesheet . ' (activity_id)');
$this->addSql('CREATE INDEX IDX_4F60C6B18D93D649 ON ' . $timesheet . ' (user)');
} else {
$this->addSql('ALTER TABLE ' . $timesheet . ' DROP FOREIGN KEY FK_4F60C6B18D93D649');
$this->addSql('ALTER TABLE ' . $timesheet . ' ADD CONSTRAINT FK_4F60C6B18D93D649 FOREIGN KEY (user) REFERENCES ' . $user . ' (id) ON DELETE CASCADE');
}
}
/**
* @param Schema $schema
* @throws \Doctrine\DBAL\DBALException
* @throws \Doctrine\DBAL\Migrations\AbortMigrationException
*/
public function down(Schema $schema): void
{
$platform = $this->getPlatform();
if (!in_array($platform, ['sqlite', 'mysql'])) {
$this->abortIf(true, 'Unsupported database platform: ' . $platform);
}
$timesheet = $this->getTableName('timesheet');
$user = $this->getTableName('user');
if ($platform === 'sqlite') {
$this->addSql('DROP INDEX IDX_4F60C6B18D93D649');
$this->addSql('DROP INDEX IDX_4F60C6B181C06096');
$this->addSql('CREATE TEMPORARY TABLE __temp__' . $timesheet . ' AS SELECT id, user, activity_id, start_time, end_time, duration, description, rate FROM ' . $timesheet);
$this->addSql('DROP TABLE ' . $timesheet);
$this->addSql('CREATE TABLE ' . $timesheet . ' (id INTEGER NOT NULL, user INTEGER DEFAULT NULL, activity_id INTEGER DEFAULT NULL, start_time DATETIME NOT NULL, end_time DATETIME DEFAULT NULL, duration INTEGER DEFAULT NULL, description CLOB DEFAULT NULL, rate NUMERIC(10, 2) NOT NULL, PRIMARY KEY(id))');
$this->addSql('INSERT INTO ' . $timesheet . ' (id, user, activity_id, start_time, end_time, duration, description, rate) SELECT id, user, activity_id, start_time, end_time, duration, description, rate FROM __temp__' . $timesheet);
$this->addSql('DROP TABLE __temp__' . $timesheet);
$this->addSql('CREATE INDEX IDX_4F60C6B18D93D649 ON ' . $timesheet . ' (user)');
$this->addSql('CREATE INDEX IDX_4F60C6B181C06096 ON ' . $timesheet . ' (activity_id)');
} else {
$this->addSql('ALTER TABLE ' . $timesheet . ' DROP FOREIGN KEY FK_4F60C6B18D93D649');
$this->addSql('ALTER TABLE ' . $timesheet . ' ADD CONSTRAINT FK_4F60C6B18D93D649 FOREIGN KEY (user) REFERENCES ' . $user . ' (id)');
}
}
}

View File

@@ -36,6 +36,10 @@ class TimesheetStatistic
* @var \DateTime * @var \DateTime
*/ */
protected $firstEntry; protected $firstEntry;
/**
* @var int
*/
protected $recordsTotal = 0;
/** /**
* @return int * @return int
@@ -116,4 +120,23 @@ class TimesheetStatistic
{ {
$this->firstEntry = $firstEntry; $this->firstEntry = $firstEntry;
} }
/**
* @return int
*/
public function getRecordsTotal(): int
{
return $this->recordsTotal;
}
/**
* @param int $recordsTotal
* @return TimesheetStatistic
*/
public function setRecordsTotal(int $recordsTotal)
{
$this->recordsTotal = $recordsTotal;
return $this;
}
} }

View File

@@ -121,6 +121,10 @@ class TimesheetRepository extends AbstractRepository
->createQuery('SELECT SUM(t.duration) FROM ' . Timesheet::class . ' t WHERE t.user = :user') ->createQuery('SELECT SUM(t.duration) FROM ' . Timesheet::class . ' t WHERE t.user = :user')
->setParameter('user', $user) ->setParameter('user', $user)
->getSingleScalarResult(); ->getSingleScalarResult();
$recordsTotal = $this->getEntityManager()
->createQuery('SELECT COUNT(t.id) FROM ' . Timesheet::class . ' t WHERE t.user = :user')
->setParameter('user', $user)
->getSingleScalarResult();
$rateTotal = $this->getEntityManager() $rateTotal = $this->getEntityManager()
->createQuery('SELECT SUM(t.rate) FROM ' . Timesheet::class . ' t WHERE t.user = :user') ->createQuery('SELECT SUM(t.rate) FROM ' . Timesheet::class . ' t WHERE t.user = :user')
->setParameter('user', $user) ->setParameter('user', $user)
@@ -142,6 +146,7 @@ class TimesheetRepository extends AbstractRepository
$stats->setAmountThisMonth($amountMonth); $stats->setAmountThisMonth($amountMonth);
$stats->setDurationThisMonth($durationMonth); $stats->setDurationThisMonth($durationMonth);
$stats->setFirstEntry(new DateTime($firstEntry)); $stats->setFirstEntry(new DateTime($firstEntry));
$stats->setRecordsTotal($recordsTotal);
return $stats; return $stats;
} }
@@ -203,6 +208,9 @@ class TimesheetRepository extends AbstractRepository
$durationTotal = $this->getEntityManager() $durationTotal = $this->getEntityManager()
->createQuery('SELECT SUM(t.duration) FROM ' . Timesheet::class . ' t') ->createQuery('SELECT SUM(t.duration) FROM ' . Timesheet::class . ' t')
->getSingleScalarResult(); ->getSingleScalarResult();
$recordsTotal = $this->getEntityManager()
->createQuery('SELECT COUNT(t.id) FROM ' . Timesheet::class . ' t')
->getSingleScalarResult();
$rateTotal = $this->getEntityManager() $rateTotal = $this->getEntityManager()
->createQuery('SELECT SUM(t.rate) FROM ' . Timesheet::class . ' t') ->createQuery('SELECT SUM(t.rate) FROM ' . Timesheet::class . ' t')
->getSingleScalarResult(); ->getSingleScalarResult();
@@ -228,6 +236,7 @@ class TimesheetRepository extends AbstractRepository
$stats->setActiveThisMonth($activeMonth); $stats->setActiveThisMonth($activeMonth);
$stats->setAmountThisMonth($amountMonth); $stats->setAmountThisMonth($amountMonth);
$stats->setDurationThisMonth($durationMonth); $stats->setDurationThisMonth($durationMonth);
$stats->setRecordsTotal($recordsTotal);
return $stats; return $stats;
} }

View File

@@ -93,7 +93,7 @@ class UserVoter extends AbstractVoter
*/ */
protected function canEditPreferences(User $profile, User $user, TokenInterface $token) protected function canEditPreferences(User $profile, User $user, TokenInterface $token)
{ {
return $profile->getId() == $user->getId(); return $profile->getId() === $user->getId();
} }
/** /**
@@ -107,7 +107,7 @@ class UserVoter extends AbstractVoter
return true; return true;
} }
return $profile->getId() == $user->getId(); return $profile->getId() === $user->getId();
} }
/** /**
@@ -121,7 +121,7 @@ class UserVoter extends AbstractVoter
return true; return true;
} }
return $profile->getId() == $user->getId(); return $profile->getId() === $user->getId();
} }
/** /**
@@ -131,7 +131,11 @@ class UserVoter extends AbstractVoter
*/ */
protected function canDelete(User $profile, User $user, TokenInterface $token) protected function canDelete(User $profile, User $user, TokenInterface $token)
{ {
return false; if (!$this->canAdminUsers($token)) {
return false;
}
return $profile->getId() !== $user->getId();
} }
/** /**
@@ -140,6 +144,6 @@ class UserVoter extends AbstractVoter
*/ */
protected function canAdminUsers(TokenInterface $token) protected function canAdminUsers(TokenInterface $token)
{ {
return $this->isFullyAuthenticated($token) && $this->hasRole('ROLE_SUPER_ADMIN', $token); return $this->isFullyAuthenticated($token) && $this->hasRole(User::ROLE_SUPER_ADMIN, $token);
} }
} }

View File

@@ -50,7 +50,7 @@
{% set actionButtons = {'edit': path('user_profile', {'username' : entry.username})}|merge(actionButtons) %} {% set actionButtons = {'edit': path('user_profile', {'username' : entry.username})}|merge(actionButtons) %}
{% endif %} {% endif %}
{% if is_granted('delete', entry) %} {% if is_granted('delete', entry) %}
{% set actionButtons = actionButtons|merge({'trash': '#'}) %} {% set actionButtons = actionButtons|merge({'trash': path('admin_user_delete', {'id': entry.id})}) %}
{% endif %} {% endif %}
{{ widgets.button_group(actionButtons) }} {{ widgets.button_group(actionButtons) }}
</td> </td>

View File

@@ -0,0 +1,22 @@
{% extends 'base.html.twig' %}
{% import "macros/widgets.html.twig" as widgets %}
{% import "macros/datatables.html.twig" as tables %}
{% block page_title %}{{ 'admin_user.title'|trans }}{% endblock %}
{% block page_subtitle %}{{ 'admin_user.subtitle'|trans }}{% endblock %}
{% block main %}
{% set params = {
'%user%': '<strong>' ~ widgets.username(user) ~ '</strong>',
'%records%': '<strong>' ~ stats.recordsTotal ~ '</strong>',
'%duration%': '<strong>' ~ stats.durationTotal|duration ~ '</strong>'
} %}
{{ include('default/_form_delete.html.twig', {
'message': "admin_user.delete_confirm"|trans(params)|raw,
'form': form,
'back': path('admin_activity')
}) }}
{% endblock %}

View File

@@ -11,6 +11,8 @@ namespace App\Tests\Controller\Admin;
use App\Entity\User; use App\Entity\User;
use App\Tests\Controller\ControllerBaseTest; use App\Tests\Controller\ControllerBaseTest;
use App\Tests\DataFixtures\ProjectFixtures;
use App\Tests\DataFixtures\TimesheetFixtures;
/** /**
* @coversDefaultClass \App\Controller\Admin\ActivityController * @coversDefaultClass \App\Controller\Admin\ActivityController
@@ -44,7 +46,7 @@ class ActivityControllerTest extends ControllerBaseTest
'name' => 'Test 2', 'name' => 'Test 2',
] ]
]); ]);
$this->assertTrue($client->getResponse()->isRedirect()); $this->assertIsRedirect($client, $this->createUrl('/admin/activity/'));
$client->followRedirect(); $client->followRedirect();
$this->assertHasDataTable($client); $this->assertHasDataTable($client);
} }
@@ -52,14 +54,26 @@ class ActivityControllerTest extends ControllerBaseTest
public function testCreateActionWithCreateMore() public function testCreateActionWithCreateMore()
{ {
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN); $client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
$em = $client->getContainer()->get('doctrine.orm.entity_manager');
$fixture = new ProjectFixtures();
$fixture->setAmount(10);
$this->importFixture($em, $fixture);
$this->assertAccessIsGranted($client, '/admin/activity/create'); $this->assertAccessIsGranted($client, '/admin/activity/create');
$form = $client->getCrawler()->filter('form[name=activity_edit_form]')->form(); $form = $client->getCrawler()->filter('form[name=activity_edit_form]')->form();
$this->assertTrue($form->has('activity_edit_form[create_more]')); $this->assertTrue($form->has('activity_edit_form[create_more]'));
/** @var \Symfony\Component\DomCrawler\Field\ChoiceFormField $project */
$project = $form->get('activity_edit_form[project]');
$options = $project->availableOptionValues();
$selectedProject = $options[array_rand($options)];
$client->submit($form, [ $client->submit($form, [
'activity_edit_form' => [ 'activity_edit_form' => [
'name' => 'Test create more', 'name' => 'Test create more',
'create_more' => true, 'create_more' => true,
// TODO select random project 'project' => $selectedProject,
] ]
]); ]);
$this->assertFalse($client->getResponse()->isRedirect()); $this->assertFalse($client->getResponse()->isRedirect());
@@ -67,7 +81,7 @@ class ActivityControllerTest extends ControllerBaseTest
$form = $client->getCrawler()->filter('form[name=activity_edit_form]')->form(); $form = $client->getCrawler()->filter('form[name=activity_edit_form]')->form();
$this->assertTrue($form->has('activity_edit_form[create_more]')); $this->assertTrue($form->has('activity_edit_form[create_more]'));
$this->assertEquals(1, $form->get('activity_edit_form[create_more]')->getValue()); $this->assertEquals(1, $form->get('activity_edit_form[create_more]')->getValue());
// TODO test that project is pre-selected $this->assertEquals($selectedProject, $form->get('activity_edit_form[project]')->getValue());
} }
public function testEditAction() public function testEditAction()
@@ -80,7 +94,7 @@ class ActivityControllerTest extends ControllerBaseTest
$client->submit($form, [ $client->submit($form, [
'activity_edit_form' => ['name' => 'Test 2'] 'activity_edit_form' => ['name' => 'Test 2']
]); ]);
$this->assertTrue($client->getResponse()->isRedirect()); $this->assertIsRedirect($client, $this->createUrl('/admin/activity/'));
$client->followRedirect(); $client->followRedirect();
$this->assertHasDataTable($client); $this->assertHasDataTable($client);
$this->request($client, '/admin/activity/1/edit'); $this->request($client, '/admin/activity/1/edit');
@@ -88,6 +102,48 @@ class ActivityControllerTest extends ControllerBaseTest
$this->assertEquals('Test 2', $editForm->get('activity_edit_form[name]')->getValue()); $this->assertEquals('Test 2', $editForm->get('activity_edit_form[name]')->getValue());
} }
public function testDeleteAction()
{
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
$this->request($client, '/admin/activity/1/edit');
$this->assertTrue($client->getResponse()->isSuccessful());
$this->request($client, '/admin/activity/1/delete');
$this->assertIsRedirect($client, $this->createUrl('/admin/activity/'));
$client->followRedirect();
$this->assertHasDataTable($client);
$this->assertHasFlashSuccess($client);
$this->request($client, '/admin/activity/1/edit');
$this->assertFalse($client->getResponse()->isSuccessful());
}
public function testDeleteActionWithTimesheetEntries()
{
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
$em = $client->getContainer()->get('doctrine.orm.entity_manager');
$fixture = new TimesheetFixtures();
$fixture->setUser($this->getUserByRole($em, User::ROLE_USER));
$fixture->setAmount(10);
$this->importFixture($em, $fixture);
$this->request($client, '/admin/activity/1/delete');
$this->assertTrue($client->getResponse()->isSuccessful());
$form = $client->getCrawler()->filter('form[name=form]')->form();
$this->assertStringEndsWith($this->createUrl('/admin/activity/1/delete'), $form->getUri());
$client->submit($form);
$this->assertIsRedirect($client, $this->createUrl('/admin/activity/'));
$client->followRedirect();
$this->assertHasDataTable($client);
$this->assertHasFlashSuccess($client);
$this->request($client, '/admin/activity/1/edit');
$this->assertFalse($client->getResponse()->isSuccessful());
}
/** /**
* @dataProvider getValidationTestData * @dataProvider getValidationTestData
*/ */

View File

@@ -11,10 +11,13 @@ namespace App\Tests\Controller\Admin;
use App\Entity\User; use App\Entity\User;
use App\Tests\Controller\ControllerBaseTest; use App\Tests\Controller\ControllerBaseTest;
use App\Tests\DataFixtures\CustomerFixtures;
use App\Tests\DataFixtures\TimesheetFixtures;
/** /**
* @coversDefaultClass \App\Controller\Admin\CustomerController * @coversDefaultClass \App\Controller\Admin\CustomerController
* @group integration * @group integration
* @group legacy
*/ */
class CustomerControllerTest extends ControllerBaseTest class CustomerControllerTest extends ControllerBaseTest
{ {
@@ -30,4 +33,127 @@ class CustomerControllerTest extends ControllerBaseTest
$this->assertAccessIsGranted($client, '/admin/customer/'); $this->assertAccessIsGranted($client, '/admin/customer/');
$this->assertHasDataTable($client); $this->assertHasDataTable($client);
} }
public function testCreateAction()
{
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
$this->assertAccessIsGranted($client, '/admin/customer/create');
$form = $client->getCrawler()->filter('form[name=customer_edit_form]')->form();
$client->submit($form, [
'customer_edit_form' => [
'name' => 'Test Customer',
]
]);
$this->assertIsRedirect($client, $this->createUrl('/admin/customer/'));
$client->followRedirect();
$this->assertHasDataTable($client);
$this->assertHasFlashSuccess($client);
}
public function testEditAction()
{
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
$this->assertAccessIsGranted($client, '/admin/customer/1/edit');
$form = $client->getCrawler()->filter('form[name=customer_edit_form]')->form();
$this->assertFalse($form->has('customer_edit_form[create_more]'));
$this->assertEquals('Test', $form->get('customer_edit_form[name]')->getValue());
$client->submit($form, [
'customer_edit_form' => [
'name' => 'Test Customer 2'
]
]);
$this->assertIsRedirect($client, $this->createUrl('/admin/customer/'));
$client->followRedirect();
$this->assertHasDataTable($client);
$this->request($client, '/admin/customer/1/edit');
$editForm = $client->getCrawler()->filter('form[name=customer_edit_form]')->form();
$this->assertEquals('Test Customer 2', $editForm->get('customer_edit_form[name]')->getValue());
}
public function testDeleteAction()
{
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
$em = $client->getContainer()->get('doctrine.orm.entity_manager');
$fixture = new CustomerFixtures();
$fixture->setAmount(1);
$this->importFixture($em, $fixture);
$this->request($client, '/admin/customer/2/edit');
$this->assertTrue($client->getResponse()->isSuccessful());
$this->request($client, '/admin/customer/2/delete');
$this->assertIsRedirect($client, $this->createUrl('/admin/customer/'));
$client->followRedirect();
$this->assertHasDataTable($client);
$this->assertHasFlashSuccess($client);
$this->request($client, '/admin/customer/2/edit');
$this->assertFalse($client->getResponse()->isSuccessful());
}
public function testDeleteActionWithTimesheetEntries()
{
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
$em = $client->getContainer()->get('doctrine.orm.entity_manager');
$fixture = new TimesheetFixtures();
$fixture->setUser($this->getUserByRole($em, User::ROLE_USER));
$fixture->setAmount(10);
$this->importFixture($em, $fixture);
$this->request($client, '/admin/customer/1/delete');
$this->assertTrue($client->getResponse()->isSuccessful());
$form = $client->getCrawler()->filter('form[name=form]')->form();
$this->assertStringEndsWith($this->createUrl('/admin/customer/1/delete'), $form->getUri());
$client->submit($form);
$this->assertIsRedirect($client, $this->createUrl('/admin/customer/'));
$client->followRedirect();
$this->assertHasDataTable($client);
$this->assertHasFlashSuccess($client);
$this->request($client, '/admin/customer/1/edit');
$this->assertFalse($client->getResponse()->isSuccessful());
}
/**
* @dataProvider getValidationTestData
*/
public function testValidationForCreateAction(array $formData, array $validationFields)
{
$this->assertFormHasValidationError(
User::ROLE_ADMIN,
'/admin/customer/create',
'form[name=customer_edit_form]',
$formData,
$validationFields
);
}
public function getValidationTestData()
{
return [
[
[
'customer_edit_form' => [
'name' => '',
'visible' => 3,
'country' => '00', // TODO why does it not fail?
'currency' => '00', // TODO why does it not fail?
'timezone' => 'XXX'
]
],
[
'#customer_edit_form_name',
'#customer_edit_form_visible',
//'#customer_edit_form_country',
//'#customer_edit_form_currency',
'#customer_edit_form_timezone',
]
],
];
}
} }

View File

@@ -11,6 +11,9 @@ namespace App\Tests\Controller\Admin;
use App\Entity\User; use App\Entity\User;
use App\Tests\Controller\ControllerBaseTest; use App\Tests\Controller\ControllerBaseTest;
use App\Tests\DataFixtures\CustomerFixtures;
use App\Tests\DataFixtures\ProjectFixtures;
use App\Tests\DataFixtures\TimesheetFixtures;
/** /**
* @coversDefaultClass \App\Controller\Admin\ProjectController * @coversDefaultClass \App\Controller\Admin\ProjectController
@@ -44,22 +47,35 @@ class ProjectControllerTest extends ControllerBaseTest
'name' => 'Test 2', 'name' => 'Test 2',
] ]
]); ]);
$this->assertTrue($client->getResponse()->isRedirect()); $this->assertIsRedirect($client, $this->createUrl('/admin/project/'));
$client->followRedirect(); $client->followRedirect();
$this->assertHasDataTable($client); $this->assertHasDataTable($client);
$this->assertHasFlashSuccess($client);
} }
public function testCreateActionWithCreateMore() public function testCreateActionWithCreateMore()
{ {
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN); $client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
$em = $client->getContainer()->get('doctrine.orm.entity_manager');
$fixture = new CustomerFixtures();
$fixture->setAmount(10);
$this->importFixture($em, $fixture);
$this->assertAccessIsGranted($client, '/admin/project/create'); $this->assertAccessIsGranted($client, '/admin/project/create');
$form = $client->getCrawler()->filter('form[name=project_edit_form]')->form(); $form = $client->getCrawler()->filter('form[name=project_edit_form]')->form();
$this->assertTrue($form->has('project_edit_form[create_more]')); $this->assertTrue($form->has('project_edit_form[create_more]'));
/** @var \Symfony\Component\DomCrawler\Field\ChoiceFormField $customer */
$customer = $form->get('project_edit_form[customer]');
$options = $customer->availableOptionValues();
$selectedCustomer = $options[array_rand($options)];
$client->submit($form, [ $client->submit($form, [
'project_edit_form' => [ 'project_edit_form' => [
'name' => 'Test create more', 'name' => 'Test create more',
'create_more' => true, 'create_more' => true,
// TODO select random customer 'customer' => $selectedCustomer
] ]
]); ]);
$this->assertFalse($client->getResponse()->isRedirect()); $this->assertFalse($client->getResponse()->isRedirect());
@@ -67,7 +83,7 @@ class ProjectControllerTest extends ControllerBaseTest
$form = $client->getCrawler()->filter('form[name=project_edit_form]')->form(); $form = $client->getCrawler()->filter('form[name=project_edit_form]')->form();
$this->assertTrue($form->has('project_edit_form[create_more]')); $this->assertTrue($form->has('project_edit_form[create_more]'));
$this->assertEquals(1, $form->get('project_edit_form[create_more]')->getValue()); $this->assertEquals(1, $form->get('project_edit_form[create_more]')->getValue());
// TODO test that customer is pre-selected $this->assertEquals($selectedCustomer, $form->get('project_edit_form[customer]')->getValue());
} }
public function testEditAction() public function testEditAction()
@@ -80,7 +96,7 @@ class ProjectControllerTest extends ControllerBaseTest
$client->submit($form, [ $client->submit($form, [
'project_edit_form' => ['name' => 'Test 2'] 'project_edit_form' => ['name' => 'Test 2']
]); ]);
$this->assertTrue($client->getResponse()->isRedirect()); $this->assertIsRedirect($client, $this->createUrl('/admin/project/'));
$client->followRedirect(); $client->followRedirect();
$this->assertHasDataTable($client); $this->assertHasDataTable($client);
$this->request($client, '/admin/project/1/edit'); $this->request($client, '/admin/project/1/edit');
@@ -88,6 +104,54 @@ class ProjectControllerTest extends ControllerBaseTest
$this->assertEquals('Test 2', $editForm->get('project_edit_form[name]')->getValue()); $this->assertEquals('Test 2', $editForm->get('project_edit_form[name]')->getValue());
} }
public function testDeleteAction()
{
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
$em = $client->getContainer()->get('doctrine.orm.entity_manager');
$fixture = new ProjectFixtures();
$fixture->setAmount(1);
$this->importFixture($em, $fixture);
$this->request($client, '/admin/project/2/edit');
$this->assertTrue($client->getResponse()->isSuccessful());
$this->request($client, '/admin/project/2/delete');
$this->assertIsRedirect($client, $this->createUrl('/admin/project/'));
$client->followRedirect();
$this->assertHasDataTable($client);
$this->assertHasFlashSuccess($client);
$this->request($client, '/admin/project/2/edit');
$this->assertFalse($client->getResponse()->isSuccessful());
}
public function testDeleteActionWithTimesheetEntries()
{
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
$em = $client->getContainer()->get('doctrine.orm.entity_manager');
$fixture = new TimesheetFixtures();
$fixture->setUser($this->getUserByRole($em, User::ROLE_USER));
$fixture->setAmount(10);
$this->importFixture($em, $fixture);
$this->request($client, '/admin/project/1/delete');
$this->assertTrue($client->getResponse()->isSuccessful());
$form = $client->getCrawler()->filter('form[name=form]')->form();
$this->assertStringEndsWith($this->createUrl('/admin/project/1/delete'), $form->getUri());
$client->submit($form);
$this->assertIsRedirect($client, $this->createUrl('/admin/project/'));
$client->followRedirect();
$this->assertHasDataTable($client);
$this->assertHasFlashSuccess($client);
$this->request($client, '/admin/project/1/edit');
$this->assertFalse($client->getResponse()->isSuccessful());
}
/** /**
* @dataProvider getValidationTestData * @dataProvider getValidationTestData
*/ */

View File

@@ -34,6 +34,7 @@ class UserControllerTest extends ControllerBaseTest
public function testCreateAction() public function testCreateAction()
{ {
$username = '亚历山德拉';
$client = $this->getClientForAuthenticatedUser(User::ROLE_SUPER_ADMIN); $client = $this->getClientForAuthenticatedUser(User::ROLE_SUPER_ADMIN);
$this->assertAccessIsGranted($client, '/admin/user/create'); $this->assertAccessIsGranted($client, '/admin/user/create');
$form = $client->getCrawler()->filter('form[name=user_create]')->form(); $form = $client->getCrawler()->filter('form[name=user_create]')->form();
@@ -41,15 +42,30 @@ class UserControllerTest extends ControllerBaseTest
$this->assertNull($form->get('user_create[create_more]')->getValue()); $this->assertNull($form->get('user_create[create_more]')->getValue());
$client->submit($form, [ $client->submit($form, [
'user_create' => [ 'user_create' => [
'username' => 'foobar@example.com', 'username' => $username,
'alias' => $username,
'plainPassword' => ['first' => 'abcdef', 'second' => 'abcdef'], 'plainPassword' => ['first' => 'abcdef', 'second' => 'abcdef'],
'email' => 'foobar@example.com', 'email' => 'foobar@example.com',
'enabled' => 1, 'enabled' => 1,
] ]
]); ]);
$this->assertTrue($client->getResponse()->isRedirect($this->createUrl('/profile/foobar@example.com/edit'))); $this->assertIsRedirect($client, $this->createUrl('/profile/' . urlencode($username) . '/edit'));
$client->followRedirect(); $client->followRedirect();
// TODO test that this is the users profile
$tabs = $client->getCrawler()->filter('div.nav-tabs-custom ul.nav-tabs li');
$this->assertEquals(4, $tabs->count());
$expectedTabs = ['#charts', '#settings', '#password', '#roles'];
$foundTabs = [];
foreach ($tabs->filter('a') as $tab) {
$name = $tab->getAttribute('href');
if (in_array($name, $expectedTabs)) {
$foundTabs[] = $name;
}
}
$this->assertEmpty(array_diff($expectedTabs, $foundTabs));
$form = $client->getCrawler()->filter('form[name=user_edit]')->form();
$this->assertEquals($username, $form->get('user_edit[alias]')->getValue());
} }
public function testCreateActionWithCreateMore() public function testCreateActionWithCreateMore()

View File

@@ -258,4 +258,28 @@ abstract class ControllerBaseTest extends WebTestCase
return $em->getRepository(User::class)->findOneBy(['username' => $name]); return $em->getRepository(User::class)->findOneBy(['username' => $name]);
} }
/**
* @param Client $client
*/
protected function assertHasFlashSuccess(Client $client)
{
$node = $client->getCrawler()->filter('div.alert.alert-success.alert-dismissible');
$this->assertNotEmpty($node->text());
}
/**
* @param Client $client
* @param string $url
*/
protected function assertIsRedirect(Client $client, $url = null)
{
$this->assertTrue($client->getResponse()->isRedirect());
if (null === $url) {
return;
}
$this->assertTrue($client->getResponse()->headers->has('Location'));
$this->assertStringEndsWith($url, $client->getResponse()->headers->get('Location'));
}
} }

View File

@@ -27,6 +27,18 @@ class ProfileControllerTest extends ControllerBaseTest
$client = $this->getClientForAuthenticatedUser(); $client = $this->getClientForAuthenticatedUser();
$this->request($client, '/profile/' . UserFixtures::USERNAME_USER); $this->request($client, '/profile/' . UserFixtures::USERNAME_USER);
$this->assertTrue($client->getResponse()->isSuccessful()); $this->assertTrue($client->getResponse()->isSuccessful());
$tabs = $client->getCrawler()->filter('div.nav-tabs-custom ul.nav-tabs li');
$this->assertEquals(4, $tabs->count());
$expectedTabs = ['#charts', '#settings', '#password', '#preferences'];
$foundTabs = [];
foreach ($tabs->filter('a') as $tab) {
$name = $tab->getAttribute('href');
if (in_array($name, $expectedTabs)) {
$foundTabs[] = $name;
}
}
$this->assertEmpty(array_diff($expectedTabs, $foundTabs));
} }
public function testIndexActionWithDifferentUsername() public function testIndexActionWithDifferentUsername()

View File

@@ -0,0 +1,88 @@
<?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\DataFixtures;
use App\Entity\Activity;
use App\Entity\Project;
use App\Entity\User;
use Doctrine\Bundle\FixturesBundle\Fixture;
use Doctrine\Common\Persistence\ObjectManager;
use Faker\Factory;
/**
* Defines the sample data to load in during controller tests.
*/
class ActivityFixtures extends Fixture
{
/**
* @var int
*/
protected $amount = 0;
/**
* @return int
*/
public function getAmount(): int
{
return $this->amount;
}
/**
* @param int $amount
* @return ActivityFixtures
*/
public function setAmount(int $amount)
{
$this->amount = $amount;
return $this;
}
/**
* {@inheritdoc}
*/
public function load(ObjectManager $manager)
{
$projects = $this->getAllProjects($manager);
$faker = Factory::create();
// random amount of timesheet entries for every user
for ($i = 0; $i < $this->amount; $i++) {
$visible = 0 != $i % 3;
$entity = new Activity();
$entity
->setProject($projects[array_rand($projects)])
->setName($faker->bs . ($visible ? '' : ' (x)'))
->setComment($faker->text)
->setVisible($visible)
;
$manager->persist($entity);
}
$manager->flush();
}
/**
* @param ObjectManager $manager
* @return Project[]
*/
protected function getAllProjects(ObjectManager $manager)
{
$all = [];
/* @var User[] $entries */
$entries = $manager->getRepository(Project::class)->findAll();
foreach ($entries as $temp) {
$all[$temp->getId()] = $temp;
}
return $all;
}
}

View File

@@ -0,0 +1,89 @@
<?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\DataFixtures;
use App\Entity\Customer;
use App\Entity\User;
use Doctrine\Bundle\FixturesBundle\Fixture;
use Doctrine\Common\Persistence\ObjectManager;
use Faker\Factory;
/**
* Defines the sample data to load in during controller tests.
*/
class CustomerFixtures extends Fixture
{
/**
* @var int
*/
protected $amount = 0;
/**
* @return int
*/
public function getAmount(): int
{
return $this->amount;
}
/**
* @param int $amount
* @return CustomerFixtures
*/
public function setAmount(int $amount)
{
$this->amount = $amount;
return $this;
}
/**
* {@inheritdoc}
*/
public function load(ObjectManager $manager)
{
$faker = Factory::create();
for ($i = 0; $i < $this->amount; $i++) {
$visible = 0 != $i % 3;
$entity = new Customer();
$entity
->setCurrency($faker->currencyCode)
->setName($faker->company . ($visible ? '' : ' (x)'))
->setAddress($faker->address)
->setComment($faker->text)
->setNumber('C-' . $faker->ean8)
->setCountry($faker->countryCode)
->setTimezone($faker->timezone)
->setVisible($visible)
;
$manager->persist($entity);
}
$manager->flush();
}
/**
* @param ObjectManager $manager
* @return Customer[]
*/
protected function getAllCustomers(ObjectManager $manager)
{
$all = [];
/* @var User[] $entries */
$entries = $manager->getRepository(Customer::class)->findAll();
foreach ($entries as $temp) {
$all[$temp->getId()] = $temp;
}
return $all;
}
}

View File

@@ -0,0 +1,88 @@
<?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\DataFixtures;
use App\Entity\Customer;
use App\Entity\Project;
use App\Entity\User;
use Doctrine\Bundle\FixturesBundle\Fixture;
use Doctrine\Common\Persistence\ObjectManager;
use Faker\Factory;
/**
* Defines the sample data to load in during controller tests.
*/
class ProjectFixtures extends Fixture
{
/**
* @var int
*/
protected $amount = 0;
/**
* @return int
*/
public function getAmount(): int
{
return $this->amount;
}
/**
* @param int $amount
* @return ProjectFixtures
*/
public function setAmount(int $amount)
{
$this->amount = $amount;
return $this;
}
/**
* {@inheritdoc}
*/
public function load(ObjectManager $manager)
{
$customers = $this->getAllCustomers($manager);
$faker = Factory::create();
for ($i = 0; $i < $this->amount; $i++) {
$visible = 0 != $i % 3;
$entity = new Project();
$entity
->setName($faker->catchPhrase . ($visible ? '' : ' (x)'))
->setBudget(rand(0, 10000))
->setComment($faker->text)
->setCustomer($customers[array_rand($customers)])
->setVisible($visible)
;
$manager->persist($entity);
}
$manager->flush();
}
/**
* @param ObjectManager $manager
* @return Customer[]
*/
protected function getAllCustomers(ObjectManager $manager)
{
$all = [];
/* @var User[] $entries */
$entries = $manager->getRepository(Customer::class)->findAll();
foreach ($entries as $temp) {
$all[$temp->getId()] = $temp;
}
return $all;
}
}

View File

@@ -23,20 +23,21 @@ use Faker\Factory;
class TimesheetFixtures extends Fixture class TimesheetFixtures extends Fixture
{ {
/** /**
* @var * @var User
*/ */
protected $user; protected $user;
/** /**
* @var int * @var int
*/ */
protected $amount = 0; protected $amount = 0;
/** /**
* @var int * @var int
*/ */
protected $running = 0; protected $running = 0;
/**
* @var Activity[]
*/
protected $activities = [];
/** /**
* @var string * @var string
*/ */
@@ -74,16 +75,27 @@ class TimesheetFixtures extends Fixture
$this->user = $user; $this->user = $user;
} }
/**
* @param Activity[] $activities
*/
public function setActivities(array $activities)
{
$this->activities = $activities;
}
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
public function load(ObjectManager $manager) public function load(ObjectManager $manager)
{ {
$activities = $this->getAllActivities($manager); $activities = $this->activities;
if (empty($activities)) {
$activities = $this->getAllActivities($manager);
}
$faker = Factory::create(); $faker = Factory::create();
$user = $this->user; $user = $this->user;
// random amount of timesheet entries for every user
for ($i = 0; $i < $this->amount; $i++) { for ($i = 0; $i < $this->amount; $i++) {
$entry = $this->createTimesheetEntry( $entry = $this->createTimesheetEntry(
$user, $user,

View File

@@ -531,6 +531,13 @@
<source>label.roles</source> <source>label.roles</source>
<target>Rolle</target> <target>Rolle</target>
</trans-unit> </trans-unit>
<trans-unit id="admin_user.delete_confirm">
<source>admin_user.delete_confirm</source>
<target>
Momentan existieren für den Benutzer %user% insgesamt %records% Zeiteinträge, welche sich auf eine Gesamtdauer von %duration% belaufen.
Diese Zeiteinträge werden ebenfalls mit gelöscht!
</target>
</trans-unit>
<!-- <!--
ROLES ROLES

View File

@@ -539,6 +539,13 @@
<source>label.roles</source> <source>label.roles</source>
<target>Role</target> <target>Role</target>
</trans-unit> </trans-unit>
<trans-unit id="admin_user.delete_confirm">
<source>admin_user.delete_confirm</source>
<target>
Currently the user %user% has %records% time-records which count up to a total of %duration%.
These time-records will be deleted as well!
</target>
</trans-unit>
<!-- <!--
ROLES ROLES

View File

@@ -81,7 +81,7 @@ bin/console doctrine:database:create
bin/console doctrine:schema:create bin/console doctrine:schema:create
``` ```
Lets bootstrap your environment by executing this commands (which is only available in dev environment): Lets bootstrap your environment by executing this command (which is only available in dev environment):
```bash ```bash
bin/console kimai:reset-dev bin/console kimai:reset-dev
``` ```
@@ -100,15 +100,15 @@ You can now login with these accounts:
| susan_super | kitten | Super-Administrator | | susan_super | kitten | Super-Administrator |
Demo data can always be deleted by dropping the schema and re-creating it. Demo data can always be deleted by dropping the schema and re-creating it.
ATTENTION - this will erase all your data: The `kimai:reset-dev` command can always be executed later on to reset your dev database and cache.
ATTENTION - if you don't want the test data, then erase it and create a empty schema:
```bash ```bash
bin/console doctrine:schema:drop --force bin/console doctrine:schema:drop --force
bin/console doctrine:schema:create bin/console doctrine:schema:create
``` ```
The `kimai:reset-dev` command can always be executed later on to reset your dev database and cache.
There is no need to configure a virtual host in your web server to access the application for testing. There is no need to configure a virtual host in your web server to access the application for testing.
Just use the built-in web server for your first tests: Just use the built-in web server for your first tests: