@@ -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:
|
||||||
|
|||||||
@@ -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:**
|
||||||
|
|||||||
@@ -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');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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', [
|
||||||
|
|||||||
@@ -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');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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');
|
||||||
|
|
||||||
|
|||||||
@@ -69,6 +69,14 @@ class Activity
|
|||||||
*/
|
*/
|
||||||
private $timesheets;
|
private $timesheets;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Timesheet[]
|
||||||
|
*/
|
||||||
|
public function getTimesheets(): array
|
||||||
|
{
|
||||||
|
return $this->timesheets;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return Project
|
* @return Project
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
92
src/Migrations/Version20180730044139.php
Normal file
92
src/Migrations/Version20180730044139.php
Normal 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)');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
22
templates/admin/user_delete.html.twig
Normal file
22
templates/admin/user_delete.html.twig
Normal 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 %}
|
||||||
@@ -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
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -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',
|
||||||
|
]
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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'));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
88
tests/DataFixtures/ActivityFixtures.php
Normal file
88
tests/DataFixtures/ActivityFixtures.php
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
89
tests/DataFixtures/CustomerFixtures.php
Normal file
89
tests/DataFixtures/CustomerFixtures.php
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
88
tests/DataFixtures/ProjectFixtures.php
Normal file
88
tests/DataFixtures/ProjectFixtures.php
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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,
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user