restart via API allows to copy description (#782)
This commit is contained in:
@@ -80,7 +80,11 @@ export default class KimaiAPILink extends KimaiClickHandlerReducedInTableRow {
|
||||
};
|
||||
|
||||
if (method === 'PATCH') {
|
||||
API.patch(url, {}, successHandle, errorHandle);
|
||||
let data = {};
|
||||
if (attributes.payload) {
|
||||
data = attributes.payload;
|
||||
}
|
||||
API.patch(url, data, successHandle, errorHandle);
|
||||
} else if (method === 'DELETE') {
|
||||
API.delete(url, successHandle, errorHandle);
|
||||
}
|
||||
|
||||
@@ -64,7 +64,12 @@ export default class KimaiRecentActivities extends KimaiPlugin {
|
||||
.replace('%project%', timesheet.project.name)
|
||||
.replace('%activity%', timesheet.activity.name);
|
||||
|
||||
htmlToInsert += `<li><a href="${ this.attributes['href'].replace('000', timesheet.id) }"><i class="${ this.attributes['icon'] }"></i> ${ label }</a></li>`;
|
||||
htmlToInsert +=
|
||||
`<li>` +
|
||||
`<a href="${ this.attributes['href'].replace('000', timesheet.id) }" data-event="kimai.timesheetStart kimai.timesheetUpdate" class="api-link" data-method="PATCH" data-msg-error="timesheet.start.error" data-msg-success="timesheet.start.success">` +
|
||||
`<i class="${ this.attributes['icon'] }"></i> ${ label }` +
|
||||
`</a>` +
|
||||
`</li>`;
|
||||
}
|
||||
|
||||
this.itemList.innerHTML = htmlToInsert;
|
||||
|
||||
@@ -55,5 +55,4 @@ App\Entity\Timesheet:
|
||||
getTags:
|
||||
serialized_name: tags
|
||||
exp: "object.getTagsAsArray()"
|
||||
type: array
|
||||
groups: [Default]
|
||||
type: array<string>
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"build/app.js": "./app.js?b2225d72491af37dec94",
|
||||
"build/app.js": "./app.js?f256658f0e056beff1e8",
|
||||
"build/app.css": "./app.css?52d21a4cca9a4768126cc7e11f6a78e9",
|
||||
"build/fonts/fa-solid-900.woff2": "./fonts/fa-solid-900.woff2?e8a92a29",
|
||||
"build/images/fa-solid-900.svg": "./images/fa-solid-900.svg?666a82cb",
|
||||
|
||||
@@ -13,6 +13,7 @@ namespace App\API;
|
||||
|
||||
use App\Configuration\TimesheetConfiguration;
|
||||
use App\Entity\Timesheet;
|
||||
use App\Entity\User;
|
||||
use App\Form\TimesheetEditForm;
|
||||
use App\Repository\Query\TimesheetQuery;
|
||||
use App\Repository\TagRepository;
|
||||
@@ -30,7 +31,9 @@ use Swagger\Annotations as SWG;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
|
||||
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
|
||||
use Symfony\Component\Validator\Constraints;
|
||||
use Symfony\Component\Validator\Validator\ValidatorInterface;
|
||||
|
||||
/**
|
||||
* @RouteResource("Timesheet")
|
||||
@@ -527,4 +530,86 @@ class TimesheetController extends BaseApiController
|
||||
|
||||
return $this->viewHandler->handle($view);
|
||||
}
|
||||
|
||||
/**
|
||||
* Restarts a previously stopped timesheet record for the current user
|
||||
*
|
||||
* @SWG\Response(
|
||||
* response=200,
|
||||
* description="Restarts a timesheet record for the same customer, project, activity combination. The current user will be the owner of the new record. Kimai tries to stop running records, which is expected to fail depending on the configured rules. Data will be copied from the original record if requested.",
|
||||
* @SWG\Schema(ref="#/definitions/TimesheetEntity")
|
||||
* )
|
||||
* @SWG\Parameter(
|
||||
* name="id",
|
||||
* in="path",
|
||||
* type="integer",
|
||||
* description="Timesheet record ID to restart",
|
||||
* required=true,
|
||||
* )
|
||||
*
|
||||
* @Rest\RequestParam(name="copy", requirements="all|tags|description", strict=true, nullable=true, description="Whether description and tags are copied to the new entry. Allowed values: all, tags, description (default: nothing is copied)")
|
||||
*
|
||||
* @Security("is_granted('start_own_timesheet') or is_granted('start_other_timesheet')")
|
||||
*
|
||||
* @param int $id
|
||||
* @return Response
|
||||
* @throws \App\Repository\RepositoryException
|
||||
* @throws \Doctrine\ORM\ORMException
|
||||
* @throws \Doctrine\ORM\OptimisticLockException
|
||||
*/
|
||||
public function restartAction($id, ParamFetcherInterface $paramFetcher, ValidatorInterface $validator)
|
||||
{
|
||||
/** @var Timesheet $timesheet */
|
||||
$timesheet = $this->repository->find($id);
|
||||
/** @var User $user */
|
||||
$user = $this->getUser();
|
||||
|
||||
if (null === $timesheet) {
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
if (!$this->isGranted('start', $timesheet)) {
|
||||
throw new AccessDeniedHttpException('You are not allowed to re-start this timesheet');
|
||||
}
|
||||
|
||||
$entry = new Timesheet();
|
||||
$entry
|
||||
->setBegin($this->dateTime->createDateTime())
|
||||
->setUser($user)
|
||||
->setActivity($timesheet->getActivity())
|
||||
->setProject($timesheet->getProject())
|
||||
;
|
||||
|
||||
if (null !== ($copy = $paramFetcher->get('copy'))) {
|
||||
if (in_array($copy, ['description', 'all'])) {
|
||||
$entry->setDescription($timesheet->getDescription());
|
||||
}
|
||||
|
||||
if (in_array($copy, ['tags', 'all'])) {
|
||||
foreach ($timesheet->getTags() as $tag) {
|
||||
$entry->addTag($tag);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$errors = $validator->validate($entry);
|
||||
|
||||
if (count($errors) > 0) {
|
||||
throw new BadRequestHttpException($errors[0]->getPropertyPath() . ' = ' . $errors[0]->getMessage());
|
||||
}
|
||||
|
||||
$this->repository->stopActiveEntries(
|
||||
$user,
|
||||
$this->configuration->getActiveEntriesHardLimit()
|
||||
);
|
||||
|
||||
$entityManager = $this->getDoctrine()->getManager();
|
||||
$entityManager->persist($entry);
|
||||
$entityManager->flush();
|
||||
|
||||
$view = new View($entry, 200);
|
||||
$view->getContext()->setGroups(['Default', 'Entity', 'Timesheet']);
|
||||
|
||||
return $this->viewHandler->handle($view);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,6 @@ use Pagerfanta\Pagerfanta;
|
||||
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
use Symfony\Component\Validator\Validator\ValidatorInterface;
|
||||
|
||||
/**
|
||||
* Controller used to manage timesheets.
|
||||
@@ -126,6 +125,8 @@ class TimesheetController extends AbstractController
|
||||
}
|
||||
|
||||
/**
|
||||
* Used for the initial page rendering.
|
||||
*
|
||||
* @return \Symfony\Component\HttpFoundation\Response
|
||||
*/
|
||||
public function activeEntriesAction()
|
||||
@@ -142,44 +143,6 @@ class TimesheetController extends AbstractController
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route(path="/start/{id}", name="timesheet_start", requirements={"id" = "\d+"}, methods={"GET", "POST"})
|
||||
* @Security("is_granted('start', timesheet)")
|
||||
*
|
||||
* @return \Symfony\Component\HttpFoundation\RedirectResponse|\Symfony\Component\HttpFoundation\Response
|
||||
*/
|
||||
public function startAction(ValidatorInterface $validator, Timesheet $timesheet)
|
||||
{
|
||||
$user = $this->getUser();
|
||||
|
||||
try {
|
||||
$entry = new Timesheet();
|
||||
$entry
|
||||
->setBegin($this->dateTime->createDateTime())
|
||||
->setUser($user)
|
||||
->setActivity($timesheet->getActivity())
|
||||
->setProject($timesheet->getProject());
|
||||
|
||||
$errors = $validator->validate($entry);
|
||||
|
||||
if (count($errors) > 0) {
|
||||
$this->flashError('timesheet.start.error', ['%reason%' => $errors[0]->getPropertyPath() . ' = ' . $errors[0]->getMessage()]);
|
||||
} else {
|
||||
$this->stopActiveEntries($user);
|
||||
|
||||
$entityManager = $this->getDoctrine()->getManager();
|
||||
$entityManager->persist($entry);
|
||||
$entityManager->flush();
|
||||
|
||||
$this->flashSuccess('timesheet.start.success');
|
||||
}
|
||||
} catch (\Exception $ex) {
|
||||
$this->flashError('timesheet.start.error', ['%reason%' => $ex->getMessage()]);
|
||||
}
|
||||
|
||||
return $this->redirectToRoute('timesheet');
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route(path="/{id}/edit", name="timesheet_edit", methods={"GET", "POST"})
|
||||
* @Security("is_granted('edit', entry)")
|
||||
|
||||
@@ -45,14 +45,6 @@ trait TimesheetControllerTrait
|
||||
$this->configuration = $configuration;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
protected function getHardLimit()
|
||||
{
|
||||
return $this->configuration->getActiveEntriesHardLimit();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
@@ -163,14 +155,17 @@ trait TimesheetControllerTrait
|
||||
|
||||
try {
|
||||
if (null === $entry->getEnd()) {
|
||||
$this->stopActiveEntries($entry->getUser());
|
||||
$this->getRepository()->stopActiveEntries(
|
||||
$entry->getUser(),
|
||||
$this->configuration->getActiveEntriesHardLimit()
|
||||
);
|
||||
}
|
||||
$entityManager->persist($entry);
|
||||
$entityManager->flush();
|
||||
|
||||
$this->flashSuccess('action.update.success');
|
||||
} catch (\Exception $ex) {
|
||||
$this->flashError('timesheet.start.error', ['%reason%' => $ex->getMessage()]);
|
||||
$this->flashError('action.update.error', ['%reason%' => $ex->getMessage()]);
|
||||
}
|
||||
|
||||
return $this->redirectToRoute($redirectRoute);
|
||||
@@ -182,17 +177,6 @@ trait TimesheetControllerTrait
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param User $user
|
||||
* @throws \App\Repository\RepositoryException
|
||||
* @throws \Doctrine\ORM\ORMException
|
||||
* @throws \Doctrine\ORM\OptimisticLockException
|
||||
*/
|
||||
protected function stopActiveEntries(User $user)
|
||||
{
|
||||
$this->getRepository()->stopActiveEntries($user, $this->getHardLimit());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Timesheet $entry
|
||||
* @param string $redirectRoute
|
||||
|
||||
@@ -257,8 +257,8 @@ class TimesheetEditForm extends AbstractType
|
||||
->add('tags', TagsInputType::class, [
|
||||
// documentation is for NelmioApiDocBundle
|
||||
'documentation' => [
|
||||
'type' => 'text',
|
||||
'description' => 'Tags for timesheet entry',
|
||||
'type' => 'string',
|
||||
'description' => 'Comma separated list of tags for this timesheet record',
|
||||
],
|
||||
'required' => false,
|
||||
]);
|
||||
|
||||
@@ -193,6 +193,9 @@
|
||||
'lastYear': '{{ 'daterangepicker.lastYear'|trans({}, 'daterangepicker') }}',
|
||||
'thisYear': '{{ 'daterangepicker.thisYear'|trans({}, 'daterangepicker') }}',
|
||||
'customRange': '{{ 'daterangepicker.customRange'|trans({}, 'daterangepicker') }}',
|
||||
'timesheet.start.success': '{{ 'timesheet.start.success'|trans({}, 'flashmessages') }}',
|
||||
'timesheet.start.error': '{{ 'timesheet.start.error'|trans({}, 'flashmessages') }}',
|
||||
'timesheet.start.exceeded_limit': '{{ 'timesheet.start.exceeded_limit'|trans({}, 'flashmessages') }}',
|
||||
'timesheet.stop.success': '{{ 'timesheet.stop.success'|trans({}, 'flashmessages') }}',
|
||||
'timesheet.stop.error': '{{ 'timesheet.stop.error'|trans({}, 'flashmessages') }}',
|
||||
'action.update.success': '{{ 'action.update.success'|trans({}, 'flashmessages') }}',
|
||||
|
||||
@@ -219,7 +219,7 @@
|
||||
{% endif %}
|
||||
|
||||
{% if timesheet.end and is_granted('start', timesheet) %}
|
||||
{% set actions = actions|merge({'repeat': path('timesheet_start', {'id' : timesheet.id})}) %}
|
||||
{% set actions = actions|merge({'repeat': {'url': path('restart_timesheet', {'id' : timesheet.id}), 'class': 'api-link', 'attr': {'data-payload': '{"copy": "all"}', 'data-event': 'kimai.timesheetStart kimai.timesheetUpdate', 'data-method': 'PATCH', 'data-msg-error': 'timesheet.start.error', 'data-msg-success': 'timesheet.start.success'}}}) %}
|
||||
{% endif %}
|
||||
|
||||
{% if is_granted('edit', timesheet) %}
|
||||
@@ -274,7 +274,7 @@
|
||||
{% endif %}
|
||||
|
||||
{% if timesheet.end and is_granted('start', timesheet) %}
|
||||
{% set actions = actions|merge({'repeat': path('timesheet_start', {'id' : timesheet.id})}) %}
|
||||
{% set actions = actions|merge({'repeat': {'url': path('restart_timesheet', {'id' : timesheet.id}), 'class': 'api-link', 'attr': {'data-payload': '{"copy": "all"}', 'data-event': 'kimai.timesheetStart kimai.timesheetUpdate', 'data-method': 'PATCH', 'data-msg-error': 'timesheet.start.error', 'data-msg-success': 'timesheet.start.success'}}}) %}
|
||||
{% endif %}
|
||||
|
||||
{% if is_granted('edit', timesheet) %}
|
||||
|
||||
@@ -253,8 +253,8 @@
|
||||
target="{{ target }}"
|
||||
{%- endif -%}
|
||||
{%- if attr is not empty -%}
|
||||
{%- for name, value in attr -%}
|
||||
{{ name }}="{{ value }}"
|
||||
{%- for name, value in attr %}
|
||||
{{- ' ' ~ name }}={% if '"' in value %}'{{ value|raw }}'{% else %}"{{ value|raw }}"{% endif %}
|
||||
{%- endfor -%}
|
||||
{%- endif -%}
|
||||
>{% if title is not null %}{{ title }}{% else %}{{ macro.icon(icon) }}{% endif %}</a>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
</a>
|
||||
<ul class="dropdown-menu"
|
||||
data-api="{{ path('recent_timesheet', {'size' : 10}) }}"
|
||||
data-href="{{ path('timesheet_start', {'id' : '000'}) }}"
|
||||
data-href="{{ path('restart_timesheet', {'id' : '000'}) }}"
|
||||
data-icon="{{ 'start-small'|icon }}"
|
||||
data-template="{{ 'recent.activities.format'|trans }}">
|
||||
<li class="header">{{ 'recent.activities'|trans }}</li>
|
||||
|
||||
@@ -198,6 +198,32 @@ abstract class APIControllerBaseTest extends ControllerBaseTest
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $role
|
||||
* @param string $url
|
||||
* @param array $data
|
||||
*/
|
||||
protected function assertEntityNotFoundForDelete(string $role, string $url, array $data)
|
||||
{
|
||||
$client = $this->getClientForAuthenticatedUser($role);
|
||||
|
||||
$this->request($client, $url, 'DELETE', [], json_encode($data));
|
||||
$response = $client->getResponse();
|
||||
$this->assertFalse($response->isSuccessful());
|
||||
|
||||
$expected = [
|
||||
'code' => 404,
|
||||
'message' => 'Not found'
|
||||
];
|
||||
|
||||
$this->assertEquals(404, $client->getResponse()->getStatusCode());
|
||||
|
||||
$this->assertEquals(
|
||||
$expected,
|
||||
json_decode($client->getResponse()->getContent(), true)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Response $response
|
||||
* @param string $message
|
||||
|
||||
@@ -86,4 +86,9 @@ class TagControllerTest extends APIControllerBaseTest
|
||||
|
||||
$this->assertEquals(9, count($result));
|
||||
}
|
||||
|
||||
public function testDeleteActionWithUnknownTimesheet()
|
||||
{
|
||||
$this->assertEntityNotFoundForDelete(User::ROLE_ADMIN, '/api/tags/255', []);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -430,16 +430,15 @@ class TimesheetControllerTest extends APIControllerBaseTest
|
||||
$this->assertEntityNotFound(User::ROLE_USER, '/api/timesheets/' . $id);
|
||||
}
|
||||
|
||||
public function testDeleteActionWithUnknownTimesheet()
|
||||
{
|
||||
$this->assertEntityNotFoundForDelete(User::ROLE_ADMIN, '/api/timesheets/255', []);
|
||||
}
|
||||
|
||||
public function testDeleteActionForDifferentUser()
|
||||
{
|
||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
|
||||
$this->assertAccessIsGranted($client, '/api/timesheets/1');
|
||||
$result = json_decode($client->getResponse()->getContent(), true);
|
||||
|
||||
$this->assertIsArray($result);
|
||||
$this->assertDefaultStructure($result);
|
||||
$this->assertNotEmpty($result['id']);
|
||||
$id = $result['id'];
|
||||
$id = 1;
|
||||
|
||||
$this->request($client, '/api/timesheets/' . $id, 'DELETE');
|
||||
$this->assertTrue($client->getResponse()->isSuccessful());
|
||||
@@ -631,6 +630,91 @@ class TimesheetControllerTest extends APIControllerBaseTest
|
||||
$this->assertDefaultStructure($result[0], false);
|
||||
}
|
||||
|
||||
public function testRestartAction()
|
||||
{
|
||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_USER);
|
||||
|
||||
$data = [
|
||||
'description' => 'foo',
|
||||
'tags' => 'another,testing,bar'
|
||||
];
|
||||
$this->request($client, '/api/timesheets/1', 'PATCH', [], json_encode($data));
|
||||
|
||||
$this->request($client, '/api/timesheets/1/restart', 'PATCH');
|
||||
$this->assertTrue($client->getResponse()->isSuccessful());
|
||||
|
||||
$result = json_decode($client->getResponse()->getContent(), true);
|
||||
$this->assertDefaultStructure($result, true);
|
||||
$this->assertEmpty($result['description']);
|
||||
$this->assertEmpty($result['tags']);
|
||||
|
||||
$em = $client->getContainer()->get('doctrine.orm.entity_manager');
|
||||
/** @var Timesheet $timesheet */
|
||||
$timesheet = $em->getRepository(Timesheet::class)->find($result['id']);
|
||||
$this->assertInstanceOf(\DateTime::class, $timesheet->getBegin());
|
||||
$this->assertNull($timesheet->getEnd());
|
||||
$this->assertEquals(1, $timesheet->getActivity()->getId());
|
||||
$this->assertEquals(1, $timesheet->getProject()->getId());
|
||||
$this->assertEmpty($timesheet->getDescription());
|
||||
$this->assertEmpty($timesheet->getTags());
|
||||
}
|
||||
|
||||
public function testRestartActionWithCopyData()
|
||||
{
|
||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_USER);
|
||||
|
||||
$data = [
|
||||
'description' => 'foo',
|
||||
'tags' => 'another,testing,bar'
|
||||
];
|
||||
$this->request($client, '/api/timesheets/1', 'PATCH', [], json_encode($data));
|
||||
$this->assertTrue($client->getResponse()->isSuccessful());
|
||||
|
||||
$em = $client->getContainer()->get('doctrine.orm.entity_manager');
|
||||
$timesheet = $em->getRepository(Timesheet::class)->find(1);
|
||||
$this->assertEquals('foo', $timesheet->getDescription());
|
||||
|
||||
$this->request($client, '/api/timesheets/1/restart', 'PATCH', ['copy' => 'all']);
|
||||
$this->assertTrue($client->getResponse()->isSuccessful());
|
||||
|
||||
$result = json_decode($client->getResponse()->getContent(), true);
|
||||
$this->assertDefaultStructure($result, true);
|
||||
$this->assertEquals('foo', $result['description']);
|
||||
$this->assertEquals(['another', 'testing', 'bar'], $result['tags']);
|
||||
|
||||
$em = $client->getContainer()->get('doctrine.orm.entity_manager');
|
||||
/** @var Timesheet $timesheet */
|
||||
$timesheet = $em->getRepository(Timesheet::class)->find($result['id']);
|
||||
$this->assertInstanceOf(\DateTime::class, $timesheet->getBegin());
|
||||
$this->assertNull($timesheet->getEnd());
|
||||
$this->assertEquals(1, $timesheet->getActivity()->getId());
|
||||
$this->assertEquals(1, $timesheet->getProject()->getId());
|
||||
$this->assertEquals('foo', $timesheet->getDescription());
|
||||
$this->assertEquals(['another', 'testing', 'bar'], $timesheet->getTagsAsArray());
|
||||
}
|
||||
|
||||
public function testRestartNotAllowedForUser()
|
||||
{
|
||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_USER);
|
||||
$em = $client->getContainer()->get('doctrine.orm.entity_manager');
|
||||
|
||||
$start = new \DateTime('-10 days');
|
||||
|
||||
$fixture = new TimesheetFixtures();
|
||||
$fixture
|
||||
->setFixedRate(true)
|
||||
->setHourlyRate(true)
|
||||
->setAmount(2)
|
||||
->setUser($this->getUserByRole($em, User::ROLE_ADMIN))
|
||||
->setStartDate($start)
|
||||
->setAmountRunning(3)
|
||||
;
|
||||
$this->importFixture($em, $fixture);
|
||||
|
||||
$this->request($client, '/api/timesheets/12/restart', 'PATCH');
|
||||
$this->assertApiResponseAccessDenied($client->getResponse(), 'You are not allowed to re-start this timesheet');
|
||||
}
|
||||
|
||||
protected function assertDefaultStructure(array $result, $full = true)
|
||||
{
|
||||
$expectedKeys = [
|
||||
|
||||
@@ -143,32 +143,6 @@ class TimesheetControllerTest extends ControllerBaseTest
|
||||
$this->assertNull($timesheet->getFixedRate());
|
||||
}
|
||||
|
||||
public function testStartAction()
|
||||
{
|
||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_USER);
|
||||
|
||||
$em = $client->getContainer()->get('doctrine.orm.entity_manager');
|
||||
$fixture = new TimesheetFixtures();
|
||||
$fixture->setUser($this->getUserByRole($em, User::ROLE_USER));
|
||||
$fixture->setAmount(1);
|
||||
$this->importFixture($em, $fixture);
|
||||
|
||||
$this->request($client, '/timesheet/start/1');
|
||||
|
||||
$this->assertIsRedirect($client, $this->createUrl('/timesheet/'));
|
||||
$client->followRedirect();
|
||||
$this->assertTrue($client->getResponse()->isSuccessful());
|
||||
$this->assertHasFlashSuccess($client, 'Time recording was started');
|
||||
|
||||
$em = $client->getContainer()->get('doctrine.orm.entity_manager');
|
||||
/** @var Timesheet $timesheet */
|
||||
$timesheet = $em->getRepository(Timesheet::class)->find(2);
|
||||
$this->assertInstanceOf(\DateTime::class, $timesheet->getBegin());
|
||||
$this->assertNull($timesheet->getEnd());
|
||||
$this->assertEquals(1, $timesheet->getActivity()->getId());
|
||||
$this->assertEquals(1, $timesheet->getProject()->getId());
|
||||
}
|
||||
|
||||
public function testCreateActionDoesNotShowRateFieldsForUser()
|
||||
{
|
||||
$client = $this->getClientForAuthenticatedUser();
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="timesheet.start.error">
|
||||
<source>timesheet.start.error</source>
|
||||
<target>تعذر بدء تسجيل الوقت:٪ reason٪</target>
|
||||
<target>تعذر بدء تسجيل الوقت</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="action.update.success">
|
||||
<source>action.update.success</source>
|
||||
|
||||
@@ -16,11 +16,11 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="timesheet.start.error">
|
||||
<source>timesheet.start.error</source>
|
||||
<target>Zeitmessung konnte nicht gestartet werden: %reason%</target>
|
||||
<target>Zeitmessung konnte nicht gestartet werden</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="timesheet.start.exceeded_limit">
|
||||
<source>timesheet.start.exceeded_limit</source>
|
||||
<target>das Limit aktiver Zeitmessungen wurde erreicht, bitte stoppen Sie mindestens eine laufende Zeitmessung.</target>
|
||||
<target>Das Limit aktiver Zeitmessungen wurde erreicht, bitte stoppen Sie mindestens eine laufende Zeitmessung.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="action.update.success">
|
||||
<source>action.update.success</source>
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="timesheet.start.error">
|
||||
<source>timesheet.start.error</source>
|
||||
<target>Time recording could not be started: %reason%</target>
|
||||
<target>Time recording could not be started</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="timesheet.start.exceeded_limit">
|
||||
<source>timesheet.start.exceeded_limit</source>
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="timesheet.start.error">
|
||||
<source>timesheet.start.error</source>
|
||||
<target>No se pudo iniciar el registro de tiempo: %reason%</target>
|
||||
<target>No se pudo iniciar el registro de tiempo</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="action.update.success">
|
||||
<source>action.update.success</source>
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="timesheet.start.error">
|
||||
<source>timesheet.start.error</source>
|
||||
<target>Le chronométrage ne peut pas être lancé : %reason%</target>
|
||||
<target>Le chronométrage ne peut pas être lancé</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="action.update.success">
|
||||
<source>action.update.success</source>
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="timesheet.start.error">
|
||||
<source>timesheet.start.error</source>
|
||||
<target>Az idő rögzítését nem lehet elindítani a következő okból: %reason%</target>
|
||||
<target>Az idő rögzítését nem lehet elindítani a következő okból</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="timesheet.start.exceeded_limit">
|
||||
<source>timesheet.start.exceeded_limit</source>
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="timesheet.start.error">
|
||||
<source>timesheet.start.error</source>
|
||||
<target>Time-recording non può essere avviata: %reason%</target>
|
||||
<target>Time-recording non può essere avviata</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="action.update.success">
|
||||
<source>action.update.success</source>
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="timesheet.start.error">
|
||||
<source>timesheet.start.error</source>
|
||||
<target>時間の記録を開始できませんでした: %reason%</target>
|
||||
<target>時間の記録を開始できませんでした</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="timesheet.start.exceeded_limit">
|
||||
<source>timesheet.start.exceeded_limit</source>
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="timesheet.start.error">
|
||||
<source>timesheet.start.error</source>
|
||||
<target>A gravação de tempo não pôde ser iniciada: %reason%</target>
|
||||
<target>A gravação de tempo não pôde ser iniciada</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="action.update.success">
|
||||
<source>action.update.success</source>
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="timesheet.start.error">
|
||||
<source>timesheet.start.error</source>
|
||||
<target>Ошибка - Хронометраж не был начат: %reason%</target>
|
||||
<target>Ошибка - Хронометраж не был начат</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="action.update.success">
|
||||
<source>action.update.success</source>
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="timesheet.start.error">
|
||||
<source>timesheet.start.error</source>
|
||||
<target state="translated">Tidinspelning kunde inte startas: %reason%</target>
|
||||
<target state="translated">Tidinspelning kunde inte startas</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="timesheet.start.exceeded_limit">
|
||||
<source>timesheet.start.exceeded_limit</source>
|
||||
|
||||
Reference in New Issue
Block a user