Improve UX for invoice template management (#4121)

change translations and to explain that calculator actually group items by fields
This commit is contained in:
Kevin Papst
2023-06-21 08:17:54 +02:00
committed by GitHub
parent 983bf2c88b
commit dd89363c72
33 changed files with 889 additions and 324 deletions

View File

@@ -4091,31 +4091,6 @@ parameters:
count: 1
path: src/Invoice/Calculator/AbstractMergedCalculator.php
-
message: "#^Cannot call method getId\\(\\) on App\\\\Entity\\\\Project\\|null\\.$#"
count: 2
path: src/Invoice/Calculator/ProjectInvoiceCalculator.php
-
message: "#^Cannot call method getInvoiceText\\(\\) on App\\\\Entity\\\\Project\\|null\\.$#"
count: 2
path: src/Invoice/Calculator/ProjectInvoiceCalculator.php
-
message: "#^Cannot call method getName\\(\\) on App\\\\Entity\\\\Project\\|null\\.$#"
count: 1
path: src/Invoice/Calculator/ProjectInvoiceCalculator.php
-
message: "#^Cannot call method getId\\(\\) on App\\\\Entity\\\\User\\|null\\.$#"
count: 2
path: src/Invoice/Calculator/UserInvoiceCalculator.php
-
message: "#^Cannot call method format\\(\\) on DateTime\\|null\\.$#"
count: 1
path: src/Invoice/Calculator/WeeklyInvoiceCalculator.php
-
message: "#^Cannot call method format\\(\\) on DateTime\\|null\\.$#"
count: 2

View File

@@ -36,6 +36,7 @@ final class InvoiceCalculatorType extends AbstractType
'choice_label' => function ($renderer) {
return $renderer;
},
'help' => 'invoice_calculator.help',
'translation_domain' => 'invoice-calculator',
]);
}

View File

@@ -18,7 +18,29 @@ use App\Invoice\InvoiceItem;
*/
abstract class AbstractSumInvoiceCalculator extends AbstractMergedCalculator implements CalculatorInterface
{
abstract protected function calculateSumIdentifier(ExportableItem $invoiceItem): string;
protected function calculateSumIdentifier(ExportableItem $invoiceItem): string
{
$ids = $this->getIdentifiers($invoiceItem);
$identifier = '';
foreach ($ids as $id) {
if ($id === null) {
$id = '__NULL__';
}
$identifier .= $id;
}
return $identifier;
}
/**
* @param ExportableItem $invoiceItem
* @return array<int|string|null>
*/
public function getIdentifiers(ExportableItem $invoiceItem): array
{
return [];
}
protected function calculateIdentifier(ExportableItem $entry): string
{

View File

@@ -18,13 +18,11 @@ use App\Invoice\InvoiceItem;
*/
final class ActivityInvoiceCalculator extends AbstractSumInvoiceCalculator implements CalculatorInterface
{
protected function calculateSumIdentifier(ExportableItem $invoiceItem): string
public function getIdentifiers(ExportableItem $invoiceItem): array
{
if (null === $invoiceItem->getActivity()) {
return '__NULL__';
}
return (string) $invoiceItem->getActivity()->getId();
return [
$invoiceItem->getActivity()?->getId()
];
}
protected function mergeSumInvoiceItem(InvoiceItem $invoiceItem, ExportableItem $entry): void

View File

@@ -0,0 +1,50 @@
<?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\Invoice\Calculator;
use App\Entity\ExportableItem;
use App\Invoice\CalculatorInterface;
use App\Invoice\InvoiceItem;
/**
* A calculator that sums up the invoice item records by activity and user.
*/
final class ActivityUserInvoiceCalculator extends AbstractSumInvoiceCalculator implements CalculatorInterface
{
public function getIdentifiers(ExportableItem $invoiceItem): array
{
if ($invoiceItem->getUser()?->getId() === null) {
throw new \Exception('Cannot handle un-persisted users');
}
return [
$invoiceItem->getActivity()?->getId(),
$invoiceItem->getUser()->getId()
];
}
protected function mergeSumInvoiceItem(InvoiceItem $invoiceItem, ExportableItem $entry): void
{
if (null === $entry->getActivity()) {
return;
}
if ($entry->getActivity()->getInvoiceText() !== null) {
$invoiceItem->setDescription($entry->getActivity()->getInvoiceText());
} else {
$invoiceItem->setDescription($entry->getActivity()->getName());
}
}
public function getId(): string
{
return 'activity_user';
}
}

View File

@@ -17,13 +17,15 @@ use App\Invoice\CalculatorInterface;
*/
final class DateInvoiceCalculator extends AbstractSumInvoiceCalculator implements CalculatorInterface
{
protected function calculateSumIdentifier(ExportableItem $invoiceItem): string
public function getIdentifiers(ExportableItem $invoiceItem): array
{
if (null === $invoiceItem->getBegin()) {
throw new \Exception('Cannot handle invoice items without start date');
}
return $invoiceItem->getBegin()->format('Y-m-d');
return [
$invoiceItem->getBegin()->format('Y-m-d')
];
}
public function getId(): string

View File

@@ -0,0 +1,40 @@
<?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\Invoice\Calculator;
use App\Entity\ExportableItem;
use App\Invoice\CalculatorInterface;
/**
* A calculator that sums up the invoice item records for each day and user.
*/
final class DateUserInvoiceCalculator extends AbstractSumInvoiceCalculator implements CalculatorInterface
{
public function getIdentifiers(ExportableItem $invoiceItem): array
{
if (null === $invoiceItem->getBegin()) {
throw new \Exception('Cannot handle invoice items without start date');
}
if ($invoiceItem->getUser()?->getId() === null) {
throw new \Exception('Cannot handle un-persisted users');
}
return [
$invoiceItem->getBegin()->format('Y-m-d'),
$invoiceItem->getUser()->getId()
];
}
public function getId(): string
{
return 'date_user';
}
}

View File

@@ -17,13 +17,13 @@ use App\Invoice\CalculatorInterface;
*/
final class PriceInvoiceCalculator extends AbstractSumInvoiceCalculator implements CalculatorInterface
{
protected function calculateSumIdentifier(ExportableItem $invoiceItem): string
public function getIdentifiers(ExportableItem $invoiceItem): array
{
if (null !== $invoiceItem->getFixedRate()) {
return 'fixed_' . $invoiceItem->getFixedRate();
return ['fixed_' . $invoiceItem->getFixedRate()];
}
return 'hourly_' . $invoiceItem->getHourlyRate();
return ['hourly_' . $invoiceItem->getHourlyRate()];
}
public function getId(): string

View File

@@ -18,17 +18,27 @@ use App\Invoice\InvoiceItem;
*/
final class ProjectInvoiceCalculator extends AbstractSumInvoiceCalculator implements CalculatorInterface
{
protected function calculateSumIdentifier(ExportableItem $invoiceItem): string
public function getIdentifiers(ExportableItem $invoiceItem): array
{
if (null === $invoiceItem->getProject()->getId()) {
if ($invoiceItem->getProject() === null) {
throw new \Exception('Cannot handle invoice items without project');
}
if ($invoiceItem->getProject()->getId() === null) {
throw new \Exception('Cannot handle un-persisted projects');
}
return (string) $invoiceItem->getProject()->getId();
return [
$invoiceItem->getProject()->getId()
];
}
protected function mergeSumInvoiceItem(InvoiceItem $invoiceItem, ExportableItem $entry): void
{
if ($entry->getProject() === null) {
return;
}
if ($entry->getProject()->getInvoiceText() !== null) {
$invoiceItem->setDescription($entry->getProject()->getInvoiceText());
} else {

View File

@@ -0,0 +1,62 @@
<?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\Invoice\Calculator;
use App\Entity\ExportableItem;
use App\Invoice\CalculatorInterface;
use App\Invoice\InvoiceItem;
/**
* A calculator that sums up the invoice item records by project and user.
*/
final class ProjectUserInvoiceCalculator extends AbstractSumInvoiceCalculator implements CalculatorInterface
{
public function getIdentifiers(ExportableItem $invoiceItem): array
{
if ($invoiceItem->getProject() === null) {
throw new \Exception('Cannot handle invoice items without project');
}
if ($invoiceItem->getProject()->getId() === null) {
throw new \Exception('Cannot handle un-persisted projects');
}
if ($invoiceItem->getUser() === null) {
throw new \Exception('Cannot handle invoice items without user');
}
if ($invoiceItem->getUser()->getId() === null) {
throw new \Exception('Cannot handle un-persisted users');
}
return [
$invoiceItem->getProject()->getId(),
$invoiceItem->getUser()->getId()
];
}
protected function mergeSumInvoiceItem(InvoiceItem $invoiceItem, ExportableItem $entry): void
{
if ($entry->getProject() === null) {
return;
}
if ($entry->getProject()->getInvoiceText() !== null) {
$invoiceItem->setDescription($entry->getProject()->getInvoiceText());
} else {
$invoiceItem->setDescription($entry->getProject()->getName());
}
}
public function getId(): string
{
return 'project_user';
}
}

View File

@@ -32,9 +32,9 @@ final class ShortInvoiceCalculator extends AbstractMergedCalculator implements C
$keys = [];
foreach ($entries as $entry) {
$key = 'hourly_' . (string) $entry->getHourlyRate();
$key = 'hourly_' . $entry->getHourlyRate();
if (null !== $entry->getFixedRate()) {
$key = 'fixed_' . (string) $entry->getFixedRate();
$key = 'fixed_' . $entry->getFixedRate();
}
if (!\in_array($key, $keys)) {
$keys[] = $key;

View File

@@ -17,13 +17,15 @@ use App\Invoice\CalculatorInterface;
*/
final class UserInvoiceCalculator extends AbstractSumInvoiceCalculator implements CalculatorInterface
{
protected function calculateSumIdentifier(ExportableItem $invoiceItem): string
public function getIdentifiers(ExportableItem $invoiceItem): array
{
if (null === $invoiceItem->getUser()->getId()) {
if (null === $invoiceItem->getUser()?->getId()) {
throw new \Exception('Cannot handle un-persisted user');
}
return (string) $invoiceItem->getUser()->getId();
return [
$invoiceItem->getUser()->getId()
];
}
public function getId(): string

View File

@@ -17,9 +17,15 @@ use App\Invoice\CalculatorInterface;
*/
final class WeeklyInvoiceCalculator extends AbstractSumInvoiceCalculator implements CalculatorInterface
{
protected function calculateSumIdentifier(ExportableItem $invoiceItem): string
public function getIdentifiers(ExportableItem $invoiceItem): array
{
return $invoiceItem->getBegin()->format('W');
if (null === $invoiceItem->getBegin()) {
throw new \Exception('Cannot handle invoice items without start date');
}
return [
$invoiceItem->getBegin()->format('W')
];
}
public function getId(): string

View File

@@ -11,55 +11,65 @@
{% embed formEditTemplate with formOptions %}
{% block form_body %}
{{ form_row(form.name) }}
{{ form_row(form.title) }}
<div class="row">
<div class="col-md-6">
{{ form_row(form.company) }}
<fieldset class="form-fieldset pb-0">
<div class="row">
<div class="col-12">
{{ form_row(form.title) }}
</div>
</div>
<div class="col-md-6">
{{ form_row(form.vatId) }}
<div class="row">
<div class="col-md-6">
{{ form_row(form.company) }}
</div>
<div class="col-md-6">
{{ form_row(form.vatId) }}
</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
{{ form_row(form.address) }}
<div class="row">
<div class="col-md-6">
{{ form_row(form.address) }}
</div>
<div class="col-md-6">
{{ form_row(form.contact) }}
</div>
</div>
<div class="col-md-6">
{{ form_row(form.contact) }}
<div class="row">
<div class="col-md-6">
{{ form_row(form.paymentTerms) }}
</div>
<div class="col-md-6">
{{ form_row(form.paymentDetails) }}
</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
{{ form_row(form.paymentTerms) }}
</fieldset>
<fieldset class="form-fieldset pb-0">
<div class="row">
<div class="col-md-6">
{{ form_row(form.dueDays) }}
</div>
<div class="col-md-6">
{{ form_row(form.vat) }}
</div>
</div>
<div class="col-md-6">
{{ form_row(form.paymentDetails) }}
<div class="row">
<div class="col-md-6">
{{ form_row(form.language) }}
</div>
<div class="col-md-6">
{{ form_row(form.numberGenerator) }}
</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
{{ form_row(form.dueDays) }}
</fieldset>
<fieldset class="form-fieldset pb-0">
<div class="row">
<div class="col-md-6">
{{ form_row(form.renderer, {'help_html': true, 'help': '<a href="https://github.com/kimai/invoice-templates" target="_blank">' ~ ('download_invoice_renderer'|trans({}, 'invoice-renderer')) ~ '</a>'}) }}
</div>
<div class="col-md-6">
{{ form_row(form.calculator) }}
</div>
</div>
<div class="col-md-6">
{{ form_row(form.vat) }}
</div>
</div>
<div class="row">
<div class="col-md-6">
{{ form_row(form.renderer, {'help_html': true, 'help': '<a href="https://github.com/kimai/invoice-templates" target="_blank">' ~ ('download_invoice_renderer'|trans({}, 'invoice-renderer')) ~ '</a>'}) }}
</div>
<div class="col-md-6">
{{ form_row(form.language) }}
</div>
</div>
<div class="row">
<div class="col-md-6">
{{ form_row(form.calculator) }}
</div>
<div class="col-md-6">
{{ form_row(form.numberGenerator) }}
</div>
</div>
</fieldset>
{{ form_rest(form) }}
{% endblock %}
{% endembed %}

View File

@@ -16,6 +16,7 @@ use App\Entity\Project;
use App\Entity\Timesheet;
use App\Entity\User;
use App\Invoice\CalculatorInterface;
use App\Invoice\InvoiceModel;
use App\Repository\Query\InvoiceQuery;
use App\Tests\Invoice\DebugFormatter;
use App\Tests\Mocks\InvoiceModelFactoryFactory;
@@ -23,7 +24,18 @@ use PHPUnit\Framework\TestCase;
abstract class AbstractCalculatorTest extends TestCase
{
protected function assertEmptyModel(CalculatorInterface $sut)
abstract protected function getCalculator(): CalculatorInterface;
public function testCalculatorInterface(): void
{
$sut = $this->getCalculator();
self::assertLessThanOrEqual(20, \strlen($sut->getId()));
$this->assertEmptyModel($sut);
}
private function assertEmptyModel(CalculatorInterface $sut): void
{
$model = $this->getEmptyModel();
$this->assertEquals('EUR', $model->getCurrency());
@@ -38,7 +50,7 @@ abstract class AbstractCalculatorTest extends TestCase
$this->assertEquals(0, $sut->getTax());
}
protected function getEmptyModel()
private function getEmptyModel(): InvoiceModel
{
$customer = new Customer('foo');
$template = new InvoiceTemplate();
@@ -52,7 +64,7 @@ abstract class AbstractCalculatorTest extends TestCase
return $model;
}
protected function assertDescription(CalculatorInterface $sut, $addProject = false, $addActivity = false)
protected function assertDescription(CalculatorInterface $sut, $addProject = false, $addActivity = false): void
{
$customer = new Customer('foo');
$template = new InvoiceTemplate();

View File

@@ -16,6 +16,7 @@ use App\Entity\Project;
use App\Entity\Timesheet;
use App\Entity\User;
use App\Invoice\Calculator\ActivityInvoiceCalculator;
use App\Invoice\CalculatorInterface;
use App\Repository\Query\InvoiceQuery;
use App\Tests\Invoice\DebugFormatter;
use App\Tests\Mocks\InvoiceModelFactoryFactory;
@@ -28,12 +29,12 @@ use App\Tests\Mocks\InvoiceModelFactoryFactory;
*/
class ActivityInvoiceCalculatorTest extends AbstractCalculatorTest
{
public function testEmptyModel()
protected function getCalculator(): CalculatorInterface
{
$this->assertEmptyModel(new ActivityInvoiceCalculator());
return new ActivityInvoiceCalculator();
}
public function testWithMultipleEntries()
public function testWithMultipleEntries(): void
{
$customer = new Customer('foo');
$template = new InvoiceTemplate();
@@ -140,7 +141,7 @@ class ActivityInvoiceCalculatorTest extends AbstractCalculatorTest
$model->addEntries($entries);
$model->setQuery($query);
$sut = new ActivityInvoiceCalculator();
$sut = $this->getCalculator();
$sut->setModel($model);
$this->assertEquals('activity', $sut->getId());
@@ -149,16 +150,16 @@ class ActivityInvoiceCalculatorTest extends AbstractCalculatorTest
$this->assertEquals('EUR', $model->getCurrency());
$this->assertEquals(2521.12, $sut->getSubtotal());
$this->assertEquals(6600, $sut->getTimeWorked());
$this->assertEquals(5, \count($sut->getEntries()));
$entries = $sut->getEntries();
self::assertCount(4, $entries);
$this->assertEquals(404.38, $entries[0]->getRate());
$this->assertEquals(2032.74, $entries[1]->getRate());
$this->assertEquals(84, $entries[2]->getRate());
}
public function testDescriptionByActivity()
public function testDescriptionByActivity(): void
{
$this->assertDescription(new ActivityInvoiceCalculator(), false, true);
$this->assertDescription($this->getCalculator(), false, true);
}
}

View File

@@ -0,0 +1,181 @@
<?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\Invoice\Calculator;
use App\Entity\Activity;
use App\Entity\Customer;
use App\Entity\InvoiceTemplate;
use App\Entity\Project;
use App\Entity\Timesheet;
use App\Entity\User;
use App\Invoice\Calculator\ActivityUserInvoiceCalculator;
use App\Invoice\CalculatorInterface;
use App\Repository\Query\InvoiceQuery;
use App\Tests\Invoice\DebugFormatter;
use App\Tests\Mocks\InvoiceModelFactoryFactory;
/**
* @covers \App\Invoice\Calculator\ActivityUserInvoiceCalculator
* @covers \App\Invoice\Calculator\AbstractSumInvoiceCalculator
* @covers \App\Invoice\Calculator\AbstractMergedCalculator
* @covers \App\Invoice\Calculator\AbstractCalculator
*/
class ActivityUserInvoiceCalculatorTest extends AbstractCalculatorTest
{
protected function getCalculator(): CalculatorInterface
{
return new ActivityUserInvoiceCalculator();
}
public function testWithMultipleEntries(): void
{
$customer = new Customer('foo');
$template = new InvoiceTemplate();
$template->setVat(19);
$user1 = $this->getMockBuilder(User::class)->onlyMethods(['getId'])->disableOriginalConstructor()->getMock();
$user1->method('getId')->willReturn(1);
$user2 = $this->getMockBuilder(User::class)->onlyMethods(['getId'])->disableOriginalConstructor()->getMock();
$user2->method('getId')->willReturn(2);
$activity1 = $this->getMockBuilder(Activity::class)->onlyMethods(['getId'])->disableOriginalConstructor()->getMock();
$activity1->method('getId')->willReturn(1);
$activity2 = $this->getMockBuilder(Activity::class)->onlyMethods(['getId'])->disableOriginalConstructor()->getMock();
$activity2->method('getId')->willReturn(2);
$activity3 = $this->getMockBuilder(Activity::class)->onlyMethods(['getId'])->disableOriginalConstructor()->getMock();
$activity3->method('getId')->willReturn(3);
$timesheet = new Timesheet();
$timesheet
->setBegin(new \DateTime())
->setEnd(new \DateTime())
->setDuration(3600)
->setRate(293.27)
->setUser($user1)
->setActivity($activity1)
->setProject((new Project())->setName('bar'));
$timesheet2 = new Timesheet();
$timesheet2
->setBegin(new \DateTime())
->setEnd(new \DateTime())
->setDuration(400)
->setRate(84.75)
->setUser($user1)
->setActivity($activity2)
->setProject((new Project())->setName('bar'));
$timesheet3 = new Timesheet();
$timesheet3
->setBegin(new \DateTime())
->setEnd(new \DateTime())
->setDuration(1800)
->setRate(111.11)
->setUser($user1)
->setActivity($activity1)
->setProject((new Project())->setName('bar'));
$timesheet4 = new Timesheet();
$timesheet4
->setBegin(new \DateTime())
->setEnd(new \DateTime())
->setDuration(400)
->setRate(1947.99)
->setUser($user1)
->setActivity($activity2)
->setProject((new Project())->setName('bar'));
$timesheet5 = new Timesheet();
$timesheet5
->setBegin(new \DateTime())
->setEnd(new \DateTime())
->setDuration(400)
->setRate(84)
->setUser($user2)
->setActivity($activity3)
->setProject((new Project())->setName('bar'));
$timesheet5a = new Timesheet();
$timesheet5a
->setBegin(new \DateTime())
->setEnd(new \DateTime())
->setDuration(400)
->setRate(84)
->setUser($user1)
->setActivity($activity3)
->setProject((new Project())->setName('bar'));
$timesheet6 = new Timesheet();
$timesheet6
->setBegin(new \DateTime())
->setEnd(new \DateTime())
->setDuration(0)
->setRate(0)
->setUser($user1)
->setProject((new Project())->setName('bar'));
$timesheet7 = new Timesheet();
$timesheet7
->setBegin(new \DateTime())
->setEnd(new \DateTime())
->setDuration(0)
->setRate(0)
->setUser($user2)
->setActivity(new Activity())
->setProject((new Project())->setName('bar'));
$timesheet8 = new Timesheet();
$timesheet8
->setBegin(new \DateTime())
->setEnd(new \DateTime())
->setDuration(0)
->setRate(0)
->setUser($user2)
->setProject((new Project())->setName('bar'));
$entries = [$timesheet, $timesheet2, $timesheet3, $timesheet4, $timesheet5, $timesheet5a, $timesheet6, $timesheet7, $timesheet8];
$query = new InvoiceQuery();
$query->addActivity($activity1);
$model = (new InvoiceModelFactoryFactory($this))->create()->createModel(new DebugFormatter());
$model->setCustomer($customer);
$model->setTemplate($template);
$model->addEntries($entries);
$model->setQuery($query);
$sut = $this->getCalculator();
$sut->setModel($model);
$this->assertEquals('activity_user', $sut->getId());
$this->assertEquals(3100.09, $sut->getTotal());
$this->assertEquals(19, $sut->getVat());
$this->assertEquals('EUR', $model->getCurrency());
$this->assertEquals(2605.12, $sut->getSubtotal());
$this->assertEquals(7000, $sut->getTimeWorked());
$entries = $sut->getEntries();
self::assertCount(6, $entries);
$this->assertEquals(404.38, $entries[0]->getRate());
$this->assertEquals(2032.74, $entries[1]->getRate());
$this->assertEquals(84.0, $entries[2]->getRate());
$this->assertEquals(84.0, $entries[3]->getRate());
$this->assertEquals(0, $entries[4]->getRate());
$this->assertEquals(0, $entries[5]->getRate());
}
public function testDescriptionByActivity(): void
{
$this->assertDescription($this->getCalculator(), false, true);
}
}

View File

@@ -16,6 +16,7 @@ use App\Entity\Project;
use App\Entity\Timesheet;
use App\Entity\User;
use App\Invoice\Calculator\DateInvoiceCalculator;
use App\Invoice\CalculatorInterface;
use App\Repository\Query\InvoiceQuery;
use App\Tests\Invoice\DebugFormatter;
use App\Tests\Mocks\InvoiceModelFactoryFactory;
@@ -29,12 +30,12 @@ use DateTime;
*/
class DateInvoiceCalculatorTest extends AbstractCalculatorTest
{
public function testEmptyModel()
protected function getCalculator(): CalculatorInterface
{
$this->assertEmptyModel(new DateInvoiceCalculator());
return new DateInvoiceCalculator();
}
public function testWithMultipleEntries()
public function testWithMultipleEntries(): void
{
$customer = new Customer('foo');
$template = new InvoiceTemplate();
@@ -113,7 +114,7 @@ class DateInvoiceCalculatorTest extends AbstractCalculatorTest
$model->addEntries($entries);
$model->setQuery($query);
$sut = new DateInvoiceCalculator();
$sut = $this->getCalculator();
$sut->setModel($model);
$this->assertEquals('date', $sut->getId());
@@ -122,7 +123,6 @@ class DateInvoiceCalculatorTest extends AbstractCalculatorTest
$this->assertEquals('EUR', $model->getCurrency());
$this->assertEquals(2521.12, $sut->getSubtotal());
$this->assertEquals(6600, $sut->getTimeWorked());
$this->assertEquals(3, \count($sut->getEntries()));
$entries = $sut->getEntries();
self::assertCount(3, $entries);
@@ -132,8 +132,8 @@ class DateInvoiceCalculatorTest extends AbstractCalculatorTest
self::assertEquals(2521.12, $entries[0]->getRate() + $entries[1]->getRate() + $entries[2]->getRate());
}
public function testDescriptionByTimesheet()
public function testDescriptionByTimesheet(): void
{
$this->assertDescription(new DateInvoiceCalculator(), false, false);
$this->assertDescription($this->getCalculator(), false, false);
}
}

View File

@@ -0,0 +1,143 @@
<?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\Invoice\Calculator;
use App\Entity\Activity;
use App\Entity\Customer;
use App\Entity\InvoiceTemplate;
use App\Entity\Project;
use App\Entity\Timesheet;
use App\Entity\User;
use App\Invoice\Calculator\DateUserInvoiceCalculator;
use App\Invoice\CalculatorInterface;
use App\Repository\Query\InvoiceQuery;
use App\Tests\Invoice\DebugFormatter;
use App\Tests\Mocks\InvoiceModelFactoryFactory;
use DateTime;
/**
* @covers \App\Invoice\Calculator\DateUserInvoiceCalculator
* @covers \App\Invoice\Calculator\AbstractSumInvoiceCalculator
* @covers \App\Invoice\Calculator\AbstractMergedCalculator
* @covers \App\Invoice\Calculator\AbstractCalculator
*/
class DateUserInvoiceCalculatorTest extends AbstractCalculatorTest
{
protected function getCalculator(): CalculatorInterface
{
return new DateUserInvoiceCalculator();
}
public function testWithMultipleEntries(): void
{
$customer = new Customer('foo');
$template = new InvoiceTemplate();
$template->setVat(19);
$user1 = $this->getMockBuilder(User::class)->onlyMethods(['getId'])->disableOriginalConstructor()->getMock();
$user1->method('getId')->willReturn(1);
$user2 = $this->getMockBuilder(User::class)->onlyMethods(['getId'])->disableOriginalConstructor()->getMock();
$user2->method('getId')->willReturn(2);
$project1 = $this->getMockBuilder(Project::class)->onlyMethods(['getId'])->disableOriginalConstructor()->getMock();
$project1->method('getId')->willReturn(1);
$project2 = $this->getMockBuilder(Project::class)->onlyMethods(['getId'])->disableOriginalConstructor()->getMock();
$project2->method('getId')->willReturn(2);
$project3 = $this->getMockBuilder(Project::class)->onlyMethods(['getId'])->disableOriginalConstructor()->getMock();
$project3->method('getId')->willReturn(3);
$timesheet = new Timesheet();
$timesheet
->setBegin(new DateTime('2018-11-29'))
->setEnd(new DateTime())
->setDuration(3600)
->setRate(293.27)
->setUser($user1)
->setActivity((new Activity())->setName('sdsd'))
->setProject($project1);
$timesheet2 = new Timesheet();
$timesheet2
->setBegin(new DateTime('2018-11-29'))
->setEnd(new DateTime())
->setDuration(400)
->setRate(84.75)
->setUser($user1)
->setActivity((new Activity())->setName('bar'))
->setProject($project2);
$timesheet3 = new Timesheet();
$timesheet3
->setBegin(new DateTime('2018-11-28'))
->setEnd(new DateTime())
->setDuration(1800)
->setRate(111.11)
->setUser($user1)
->setActivity((new Activity())->setName('foo'))
->setProject($project1);
$timesheet4 = new Timesheet();
$timesheet4
->setBegin(new DateTime())
->setEnd(new DateTime())
->setDuration(400)
->setRate(1947.99)
->setUser($user1)
->setActivity((new Activity())->setName('blub'))
->setProject($project2);
$timesheet5 = new Timesheet();
$timesheet5
->setBegin(new DateTime('2018-11-28'))
->setEnd(new DateTime())
->setDuration(400)
->setRate(84)
->setUser($user2)
->setActivity(new Activity())
->setProject($project3);
$entries = [$timesheet, $timesheet2, $timesheet3, $timesheet4, $timesheet5];
$query = new InvoiceQuery();
$query->setProjects([$project1]);
$model = (new InvoiceModelFactoryFactory($this))->create()->createModel(new DebugFormatter());
$model->setCustomer($customer);
$model->setTemplate($template);
$model->addEntries($entries);
$model->setQuery($query);
$sut = $this->getCalculator();
$sut->setModel($model);
$this->assertEquals('date_user', $sut->getId());
$this->assertEquals(3000.13, $sut->getTotal());
$this->assertEquals(19, $sut->getVat());
$this->assertEquals('EUR', $model->getCurrency());
$this->assertEquals(2521.12, $sut->getSubtotal());
$this->assertEquals(6600, $sut->getTimeWorked());
$entries = $sut->getEntries();
self::assertCount(4, $entries);
$this->assertEquals(378.02, $entries[0]->getRate());
$this->assertEquals(111.11, $entries[1]->getRate());
$this->assertEquals(1947.99, $entries[2]->getRate());
$this->assertEquals(84, $entries[3]->getRate());
self::assertEquals(2521.12, $entries[0]->getRate() + $entries[1]->getRate() + $entries[2]->getRate() + $entries[3]->getRate());
}
public function testDescriptionByTimesheet(): void
{
$this->assertDescription($this->getCalculator(), false, false);
}
}

View File

@@ -15,6 +15,7 @@ use App\Entity\InvoiceTemplate;
use App\Entity\Tag;
use App\Entity\Timesheet;
use App\Invoice\Calculator\DefaultCalculator;
use App\Invoice\CalculatorInterface;
use App\Repository\Query\InvoiceQuery;
use App\Tests\Invoice\DebugFormatter;
use App\Tests\Mocks\InvoiceModelFactoryFactory;
@@ -25,12 +26,12 @@ use App\Tests\Mocks\InvoiceModelFactoryFactory;
*/
class DefaultCalculatorTest extends AbstractCalculatorTest
{
public function testEmptyModel()
protected function getCalculator(): CalculatorInterface
{
$this->assertEmptyModel(new DefaultCalculator());
return new DefaultCalculator();
}
public function testWithMultipleEntries()
public function testWithMultipleEntries(): void
{
$date = new \DateTime();
$customer = new Customer('foo');
@@ -69,7 +70,7 @@ class DefaultCalculatorTest extends AbstractCalculatorTest
$model->addEntries($entries);
$model->setQuery(new InvoiceQuery());
$sut = new DefaultCalculator();
$sut = $this->getCalculator();
$sut->setModel($model);
$this->assertEquals('default', $sut->getId());

View File

@@ -15,8 +15,8 @@ use App\Entity\InvoiceTemplate;
use App\Entity\Project;
use App\Entity\Timesheet;
use App\Entity\User;
use App\Invoice\Calculator\DateInvoiceCalculator;
use App\Invoice\Calculator\PriceInvoiceCalculator;
use App\Invoice\CalculatorInterface;
use App\Repository\Query\InvoiceQuery;
use App\Tests\Invoice\DebugFormatter;
use App\Tests\Mocks\InvoiceModelFactoryFactory;
@@ -30,12 +30,12 @@ use DateTime;
*/
class PriceInvoiceCalculatorTest extends AbstractCalculatorTest
{
public function testEmptyModel()
protected function getCalculator(): CalculatorInterface
{
$this->assertEmptyModel(new DateInvoiceCalculator());
return new PriceInvoiceCalculator();
}
public function testWithMultipleEntries()
public function testWithMultipleEntries(): void
{
$customer = new Customer('foo');
$template = new InvoiceTemplate();
@@ -118,7 +118,7 @@ class PriceInvoiceCalculatorTest extends AbstractCalculatorTest
$model->addEntries($entries);
$model->setQuery($query);
$sut = new PriceInvoiceCalculator();
$sut = $this->getCalculator();
$sut->setModel($model);
$this->assertEquals('price', $sut->getId());
@@ -136,8 +136,8 @@ class PriceInvoiceCalculatorTest extends AbstractCalculatorTest
$this->assertEquals(84, $entries[3]->getRate());
}
public function testDescriptionByTimesheet()
public function testDescriptionByTimesheet(): void
{
$this->assertDescription(new PriceInvoiceCalculator(), false, false);
$this->assertDescription($this->getCalculator(), false, false);
}
}

View File

@@ -16,6 +16,7 @@ use App\Entity\Project;
use App\Entity\Timesheet;
use App\Entity\User;
use App\Invoice\Calculator\ProjectInvoiceCalculator;
use App\Invoice\CalculatorInterface;
use App\Repository\Query\InvoiceQuery;
use App\Tests\Invoice\DebugFormatter;
use App\Tests\Mocks\InvoiceModelFactoryFactory;
@@ -29,12 +30,12 @@ use DateTime;
*/
class ProjectInvoiceCalculatorTest extends AbstractCalculatorTest
{
public function testEmptyModel()
protected function getCalculator(): CalculatorInterface
{
$this->assertEmptyModel(new ProjectInvoiceCalculator());
return new ProjectInvoiceCalculator();
}
public function testWithMultipleEntries()
public function testWithMultipleEntries(): void
{
$customer = new Customer('foo');
$template = new InvoiceTemplate();
@@ -113,7 +114,7 @@ class ProjectInvoiceCalculatorTest extends AbstractCalculatorTest
$model->addEntries($entries);
$model->setQuery($query);
$sut = new ProjectInvoiceCalculator();
$sut = $this->getCalculator();
$sut->setModel($model);
$this->assertEquals('project', $sut->getId());
@@ -122,7 +123,6 @@ class ProjectInvoiceCalculatorTest extends AbstractCalculatorTest
$this->assertEquals('EUR', $model->getCurrency());
$this->assertEquals(2521.12, $sut->getSubtotal());
$this->assertEquals(6600, $sut->getTimeWorked());
$this->assertEquals(3, \count($sut->getEntries()));
$entries = $sut->getEntries();
self::assertCount(3, $entries);
@@ -132,8 +132,8 @@ class ProjectInvoiceCalculatorTest extends AbstractCalculatorTest
self::assertEquals(2521.12, $entries[0]->getRate() + $entries[1]->getRate() + $entries[2]->getRate());
}
public function testDescriptionByProject()
public function testDescriptionByProject(): void
{
$this->assertDescription(new ProjectInvoiceCalculator(), true, false);
$this->assertDescription($this->getCalculator(), true, false);
}
}

View File

@@ -0,0 +1,142 @@
<?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\Invoice\Calculator;
use App\Entity\Activity;
use App\Entity\Customer;
use App\Entity\InvoiceTemplate;
use App\Entity\Project;
use App\Entity\Timesheet;
use App\Entity\User;
use App\Invoice\Calculator\ProjectUserInvoiceCalculator;
use App\Invoice\CalculatorInterface;
use App\Repository\Query\InvoiceQuery;
use App\Tests\Invoice\DebugFormatter;
use App\Tests\Mocks\InvoiceModelFactoryFactory;
use DateTime;
/**
* @covers \App\Invoice\Calculator\ProjectUserInvoiceCalculator
* @covers \App\Invoice\Calculator\AbstractSumInvoiceCalculator
* @covers \App\Invoice\Calculator\AbstractMergedCalculator
* @covers \App\Invoice\Calculator\AbstractCalculator
*/
class ProjectUserInvoiceCalculatorTest extends AbstractCalculatorTest
{
protected function getCalculator(): CalculatorInterface
{
return new ProjectUserInvoiceCalculator();
}
public function testWithMultipleEntries(): void
{
$customer = new Customer('foo');
$template = new InvoiceTemplate();
$template->setVat(19);
$user1 = $this->getMockBuilder(User::class)->onlyMethods(['getId'])->disableOriginalConstructor()->getMock();
$user1->method('getId')->willReturn(1);
$user2 = $this->getMockBuilder(User::class)->onlyMethods(['getId'])->disableOriginalConstructor()->getMock();
$user2->method('getId')->willReturn(2);
$project1 = $this->getMockBuilder(Project::class)->onlyMethods(['getId'])->disableOriginalConstructor()->getMock();
$project1->method('getId')->willReturn(1);
$project2 = $this->getMockBuilder(Project::class)->onlyMethods(['getId'])->disableOriginalConstructor()->getMock();
$project2->method('getId')->willReturn(2);
$project3 = $this->getMockBuilder(Project::class)->onlyMethods(['getId'])->disableOriginalConstructor()->getMock();
$project3->method('getId')->willReturn(3);
$timesheet = new Timesheet();
$timesheet
->setBegin(new DateTime())
->setEnd(new DateTime())
->setDuration(3600)
->setRate(293.27)
->setUser($user1)
->setActivity((new Activity())->setName('sdsd'))
->setProject($project1);
$timesheet2 = new Timesheet();
$timesheet2
->setBegin(new DateTime())
->setEnd(new DateTime())
->setDuration(400)
->setRate(84.75)
->setUser($user1)
->setActivity((new Activity())->setName('bar'))
->setProject($project2);
$timesheet3 = new Timesheet();
$timesheet3
->setBegin(new DateTime())
->setEnd(new DateTime())
->setDuration(1800)
->setRate(111.11)
->setUser($user1)
->setActivity((new Activity())->setName('foo'))
->setProject($project1);
$timesheet4 = new Timesheet();
$timesheet4
->setBegin(new DateTime())
->setEnd(new DateTime())
->setDuration(400)
->setRate(1947.99)
->setUser($user1)
->setActivity((new Activity())->setName('blub'))
->setProject($project2);
$timesheet5 = new Timesheet();
$timesheet5
->setBegin(new DateTime())
->setEnd(new DateTime())
->setDuration(400)
->setRate(84)
->setUser($user2)
->setActivity(new Activity())
->setProject($project3);
$entries = [$timesheet, $timesheet2, $timesheet3, $timesheet4, $timesheet5];
$query = new InvoiceQuery();
$query->setProjects([$project1]);
$model = (new InvoiceModelFactoryFactory($this))->create()->createModel(new DebugFormatter());
$model->setCustomer($customer);
$model->setTemplate($template);
$model->addEntries($entries);
$model->setQuery($query);
$sut = $this->getCalculator();
$sut->setModel($model);
$this->assertEquals('project_user', $sut->getId());
$this->assertEquals(3000.13, $sut->getTotal());
$this->assertEquals(19, $sut->getVat());
$this->assertEquals('EUR', $model->getCurrency());
$this->assertEquals(2521.12, $sut->getSubtotal());
$this->assertEquals(6600, $sut->getTimeWorked());
$entries = $sut->getEntries();
self::assertCount(3, $entries);
$this->assertEquals(404.38, $entries[0]->getRate());
$this->assertEquals(2032.74, $entries[1]->getRate());
$this->assertEquals(84, $entries[2]->getRate());
self::assertEquals(2521.12, $entries[0]->getRate() + $entries[1]->getRate() + $entries[2]->getRate());
}
public function testDescriptionByProject(): void
{
$this->assertDescription($this->getCalculator(), true, false);
}
}

View File

@@ -17,6 +17,7 @@ use App\Entity\Tag;
use App\Entity\Timesheet;
use App\Entity\User;
use App\Invoice\Calculator\ShortInvoiceCalculator;
use App\Invoice\CalculatorInterface;
use App\Invoice\InvoiceItem;
use App\Repository\Query\InvoiceQuery;
use App\Tests\Invoice\DebugFormatter;
@@ -29,12 +30,12 @@ use App\Tests\Mocks\InvoiceModelFactoryFactory;
*/
class ShortInvoiceCalculatorTest extends AbstractCalculatorTest
{
public function testEmptyModel()
protected function getCalculator(): CalculatorInterface
{
$this->assertEmptyModel(new ShortInvoiceCalculator());
return new ShortInvoiceCalculator();
}
public function testWithMultipleEntries()
public function testWithMultipleEntries(): void
{
$customer = new Customer('foo');
$template = new InvoiceTemplate();
@@ -97,7 +98,7 @@ class ShortInvoiceCalculatorTest extends AbstractCalculatorTest
$model->addEntries($entries);
$model->setQuery($query);
$sut = new ShortInvoiceCalculator();
$sut = $this->getCalculator();
$sut->setModel($model);
$this->assertEquals('short', $sut->getId());
@@ -119,7 +120,7 @@ class ShortInvoiceCalculatorTest extends AbstractCalculatorTest
$this->assertEquals(['foo', 'bar', 'bar1'], $result->getTags());
}
public function testWithMultipleEntriesDifferentRates()
public function testWithMultipleEntriesDifferentRates(): void
{
$customer = new Customer('foo');
$template = new InvoiceTemplate();
@@ -179,7 +180,7 @@ class ShortInvoiceCalculatorTest extends AbstractCalculatorTest
$model->addEntries($entries);
$model->setQuery($query);
$sut = new ShortInvoiceCalculator();
$sut = $this->getCalculator();
$sut->setModel($model);
$this->assertEquals('short', $sut->getId());
@@ -200,7 +201,7 @@ class ShortInvoiceCalculatorTest extends AbstractCalculatorTest
$this->assertEquals(1, $result->getAmount());
}
public function testWithMixedRateTypes()
public function testWithMixedRateTypes(): void
{
$customer = new Customer('foo');
$template = new InvoiceTemplate();
@@ -258,7 +259,7 @@ class ShortInvoiceCalculatorTest extends AbstractCalculatorTest
$model->addEntries($entries);
$model->setQuery($query);
$sut = new ShortInvoiceCalculator();
$sut = $this->getCalculator();
$sut->setModel($model);
$this->assertEquals('short', $sut->getId());
@@ -279,8 +280,8 @@ class ShortInvoiceCalculatorTest extends AbstractCalculatorTest
$this->assertEquals(1, $result->getAmount());
}
public function testDescriptionByTimesheet()
public function testDescriptionByTimesheet(): void
{
$this->assertDescription(new ShortInvoiceCalculator(), false, false);
$this->assertDescription($this->getCalculator(), false, false);
}
}

View File

@@ -16,6 +16,7 @@ use App\Entity\Project;
use App\Entity\Timesheet;
use App\Entity\User;
use App\Invoice\Calculator\UserInvoiceCalculator;
use App\Invoice\CalculatorInterface;
use App\Repository\Query\InvoiceQuery;
use App\Tests\Invoice\DebugFormatter;
use App\Tests\Mocks\InvoiceModelFactoryFactory;
@@ -27,12 +28,12 @@ use App\Tests\Mocks\InvoiceModelFactoryFactory;
*/
class UserInvoiceCalculatorTest extends AbstractCalculatorTest
{
public function testEmptyModel()
protected function getCalculator(): CalculatorInterface
{
$this->assertEmptyModel(new UserInvoiceCalculator());
return new UserInvoiceCalculator();
}
public function testWithMultipleEntries()
public function testWithMultipleEntries(): void
{
$customer = new Customer('foo');
$template = new InvoiceTemplate();
@@ -111,7 +112,7 @@ class UserInvoiceCalculatorTest extends AbstractCalculatorTest
$model->addEntries($entries);
$model->setQuery($query);
$sut = new UserInvoiceCalculator();
$sut = $this->getCalculator();
$sut->setModel($model);
$this->assertEquals('user', $sut->getId());
@@ -120,16 +121,16 @@ class UserInvoiceCalculatorTest extends AbstractCalculatorTest
$this->assertEquals('EUR', $model->getCurrency());
$this->assertEquals(2521.12, $sut->getSubtotal());
$this->assertEquals(6600, $sut->getTimeWorked());
$this->assertEquals(3, \count($sut->getEntries()));
$entries = $sut->getEntries();
self::assertCount(3, $entries);
$this->assertEquals(404.38, $entries[0]->getRate());
$this->assertEquals(2032.74, $entries[1]->getRate());
$this->assertEquals(84, $entries[2]->getRate());
}
public function testDescriptionByTimesheet()
public function testDescriptionByTimesheet(): void
{
$this->assertDescription(new UserInvoiceCalculator(), false, false);
$this->assertDescription($this->getCalculator(), false, false);
}
}

View File

@@ -16,6 +16,7 @@ use App\Entity\Project;
use App\Entity\Timesheet;
use App\Entity\User;
use App\Invoice\Calculator\WeeklyInvoiceCalculator;
use App\Invoice\CalculatorInterface;
use App\Repository\Query\InvoiceQuery;
use App\Tests\Invoice\DebugFormatter;
use App\Tests\Mocks\InvoiceModelFactoryFactory;
@@ -29,12 +30,12 @@ use DateTime;
*/
class WeeklyInvoiceCalculatorTest extends AbstractCalculatorTest
{
public function testEmptyModel()
protected function getCalculator(): CalculatorInterface
{
$this->assertEmptyModel(new WeeklyInvoiceCalculator());
return new WeeklyInvoiceCalculator();
}
public function testWithMultipleEntries()
public function testWithMultipleEntries(): void
{
$customer = new Customer('foo');
$template = new InvoiceTemplate();
@@ -116,7 +117,7 @@ class WeeklyInvoiceCalculatorTest extends AbstractCalculatorTest
$model->addEntries($entries);
$model->setQuery($query);
$sut = new WeeklyInvoiceCalculator();
$sut = $this->getCalculator();
$sut->setModel($model);
$this->assertEquals('weekly', $sut->getId());
@@ -133,8 +134,8 @@ class WeeklyInvoiceCalculatorTest extends AbstractCalculatorTest
self::assertEquals(2521.12, $entries[0]->getRate() + $entries[1]->getRate());
}
public function testDescriptionByTimesheet()
public function testDescriptionByTimesheet(): void
{
$this->assertDescription(new WeeklyInvoiceCalculator(), false, false);
$this->assertDescription($this->getCalculator(), false, false);
}
}

View File

@@ -6547,11 +6547,6 @@ parameters:
count: 1
path: Form/Type/TypeTestModel.php
-
message: "#^Method App\\\\Tests\\\\Invoice\\\\Calculator\\\\AbstractCalculatorTest\\:\\:assertDescription\\(\\) has no return type specified\\.$#"
count: 1
path: Invoice/Calculator/AbstractCalculatorTest.php
-
message: "#^Method App\\\\Tests\\\\Invoice\\\\Calculator\\\\AbstractCalculatorTest\\:\\:assertDescription\\(\\) has parameter \\$addActivity with no type specified\\.$#"
count: 1
@@ -6562,141 +6557,6 @@ parameters:
count: 1
path: Invoice/Calculator/AbstractCalculatorTest.php
-
message: "#^Method App\\\\Tests\\\\Invoice\\\\Calculator\\\\AbstractCalculatorTest\\:\\:assertEmptyModel\\(\\) has no return type specified\\.$#"
count: 1
path: Invoice/Calculator/AbstractCalculatorTest.php
-
message: "#^Method App\\\\Tests\\\\Invoice\\\\Calculator\\\\AbstractCalculatorTest\\:\\:getEmptyModel\\(\\) has no return type specified\\.$#"
count: 1
path: Invoice/Calculator/AbstractCalculatorTest.php
-
message: "#^Method App\\\\Tests\\\\Invoice\\\\Calculator\\\\ActivityInvoiceCalculatorTest\\:\\:testDescriptionByActivity\\(\\) has no return type specified\\.$#"
count: 1
path: Invoice/Calculator/ActivityInvoiceCalculatorTest.php
-
message: "#^Method App\\\\Tests\\\\Invoice\\\\Calculator\\\\ActivityInvoiceCalculatorTest\\:\\:testEmptyModel\\(\\) has no return type specified\\.$#"
count: 1
path: Invoice/Calculator/ActivityInvoiceCalculatorTest.php
-
message: "#^Method App\\\\Tests\\\\Invoice\\\\Calculator\\\\ActivityInvoiceCalculatorTest\\:\\:testWithMultipleEntries\\(\\) has no return type specified\\.$#"
count: 1
path: Invoice/Calculator/ActivityInvoiceCalculatorTest.php
-
message: "#^Method App\\\\Tests\\\\Invoice\\\\Calculator\\\\DateInvoiceCalculatorTest\\:\\:testDescriptionByTimesheet\\(\\) has no return type specified\\.$#"
count: 1
path: Invoice/Calculator/DateInvoiceCalculatorTest.php
-
message: "#^Method App\\\\Tests\\\\Invoice\\\\Calculator\\\\DateInvoiceCalculatorTest\\:\\:testEmptyModel\\(\\) has no return type specified\\.$#"
count: 1
path: Invoice/Calculator/DateInvoiceCalculatorTest.php
-
message: "#^Method App\\\\Tests\\\\Invoice\\\\Calculator\\\\DateInvoiceCalculatorTest\\:\\:testWithMultipleEntries\\(\\) has no return type specified\\.$#"
count: 1
path: Invoice/Calculator/DateInvoiceCalculatorTest.php
-
message: "#^Method App\\\\Tests\\\\Invoice\\\\Calculator\\\\DefaultCalculatorTest\\:\\:testEmptyModel\\(\\) has no return type specified\\.$#"
count: 1
path: Invoice/Calculator/DefaultCalculatorTest.php
-
message: "#^Method App\\\\Tests\\\\Invoice\\\\Calculator\\\\DefaultCalculatorTest\\:\\:testWithMultipleEntries\\(\\) has no return type specified\\.$#"
count: 1
path: Invoice/Calculator/DefaultCalculatorTest.php
-
message: "#^Method App\\\\Tests\\\\Invoice\\\\Calculator\\\\PriceInvoiceCalculatorTest\\:\\:testDescriptionByTimesheet\\(\\) has no return type specified\\.$#"
count: 1
path: Invoice/Calculator/PriceInvoiceCalculatorTest.php
-
message: "#^Method App\\\\Tests\\\\Invoice\\\\Calculator\\\\PriceInvoiceCalculatorTest\\:\\:testEmptyModel\\(\\) has no return type specified\\.$#"
count: 1
path: Invoice/Calculator/PriceInvoiceCalculatorTest.php
-
message: "#^Method App\\\\Tests\\\\Invoice\\\\Calculator\\\\PriceInvoiceCalculatorTest\\:\\:testWithMultipleEntries\\(\\) has no return type specified\\.$#"
count: 1
path: Invoice/Calculator/PriceInvoiceCalculatorTest.php
-
message: "#^Method App\\\\Tests\\\\Invoice\\\\Calculator\\\\ProjectInvoiceCalculatorTest\\:\\:testDescriptionByProject\\(\\) has no return type specified\\.$#"
count: 1
path: Invoice/Calculator/ProjectInvoiceCalculatorTest.php
-
message: "#^Method App\\\\Tests\\\\Invoice\\\\Calculator\\\\ProjectInvoiceCalculatorTest\\:\\:testEmptyModel\\(\\) has no return type specified\\.$#"
count: 1
path: Invoice/Calculator/ProjectInvoiceCalculatorTest.php
-
message: "#^Method App\\\\Tests\\\\Invoice\\\\Calculator\\\\ProjectInvoiceCalculatorTest\\:\\:testWithMultipleEntries\\(\\) has no return type specified\\.$#"
count: 1
path: Invoice/Calculator/ProjectInvoiceCalculatorTest.php
-
message: "#^Method App\\\\Tests\\\\Invoice\\\\Calculator\\\\ShortInvoiceCalculatorTest\\:\\:testDescriptionByTimesheet\\(\\) has no return type specified\\.$#"
count: 1
path: Invoice/Calculator/ShortInvoiceCalculatorTest.php
-
message: "#^Method App\\\\Tests\\\\Invoice\\\\Calculator\\\\ShortInvoiceCalculatorTest\\:\\:testEmptyModel\\(\\) has no return type specified\\.$#"
count: 1
path: Invoice/Calculator/ShortInvoiceCalculatorTest.php
-
message: "#^Method App\\\\Tests\\\\Invoice\\\\Calculator\\\\ShortInvoiceCalculatorTest\\:\\:testWithMixedRateTypes\\(\\) has no return type specified\\.$#"
count: 1
path: Invoice/Calculator/ShortInvoiceCalculatorTest.php
-
message: "#^Method App\\\\Tests\\\\Invoice\\\\Calculator\\\\ShortInvoiceCalculatorTest\\:\\:testWithMultipleEntries\\(\\) has no return type specified\\.$#"
count: 1
path: Invoice/Calculator/ShortInvoiceCalculatorTest.php
-
message: "#^Method App\\\\Tests\\\\Invoice\\\\Calculator\\\\ShortInvoiceCalculatorTest\\:\\:testWithMultipleEntriesDifferentRates\\(\\) has no return type specified\\.$#"
count: 1
path: Invoice/Calculator/ShortInvoiceCalculatorTest.php
-
message: "#^Method App\\\\Tests\\\\Invoice\\\\Calculator\\\\UserInvoiceCalculatorTest\\:\\:testDescriptionByTimesheet\\(\\) has no return type specified\\.$#"
count: 1
path: Invoice/Calculator/UserInvoiceCalculatorTest.php
-
message: "#^Method App\\\\Tests\\\\Invoice\\\\Calculator\\\\UserInvoiceCalculatorTest\\:\\:testEmptyModel\\(\\) has no return type specified\\.$#"
count: 1
path: Invoice/Calculator/UserInvoiceCalculatorTest.php
-
message: "#^Method App\\\\Tests\\\\Invoice\\\\Calculator\\\\UserInvoiceCalculatorTest\\:\\:testWithMultipleEntries\\(\\) has no return type specified\\.$#"
count: 1
path: Invoice/Calculator/UserInvoiceCalculatorTest.php
-
message: "#^Method App\\\\Tests\\\\Invoice\\\\Calculator\\\\WeeklyInvoiceCalculatorTest\\:\\:testDescriptionByTimesheet\\(\\) has no return type specified\\.$#"
count: 1
path: Invoice/Calculator/WeeklyInvoiceCalculatorTest.php
-
message: "#^Method App\\\\Tests\\\\Invoice\\\\Calculator\\\\WeeklyInvoiceCalculatorTest\\:\\:testEmptyModel\\(\\) has no return type specified\\.$#"
count: 1
path: Invoice/Calculator/WeeklyInvoiceCalculatorTest.php
-
message: "#^Method App\\\\Tests\\\\Invoice\\\\Calculator\\\\WeeklyInvoiceCalculatorTest\\:\\:testWithMultipleEntries\\(\\) has no return type specified\\.$#"
count: 1
path: Invoice/Calculator/WeeklyInvoiceCalculatorTest.php
-
message: "#^Cannot call method getEntries\\(\\) on App\\\\Invoice\\\\CalculatorInterface\\|null\\.$#"
count: 2

View File

@@ -4,39 +4,55 @@
<body>
<trans-unit id="MviRJeB" resname="invoice_calculator">
<source>invoice_calculator</source>
<target>Summen Berechnung</target>
<target>Gruppierung der Rechnungszeilen</target>
</trans-unit>
<trans-unit id="KWX.j1b" resname="invoice_calculator.help">
<source>invoice_calculator.help</source>
<target>Anhand welcher Felder sollen Rechnungsposten gruppiert werden?</target>
</trans-unit>
<trans-unit id="N6juwc4" resname="default">
<source>default</source>
<target>Standard: ein Eintrag pro Zeitmessung</target>
<target>Standard (eine Zeile pro Eintrag)</target>
</trans-unit>
<trans-unit id="_bAHi13" resname="short">
<source>short</source>
<target>Stunden: Einträge aufsummiert, nur ein Eintrag</target>
<target>Alle (ein Eintrag insgesamt)</target>
</trans-unit>
<trans-unit id="BPiZbad" resname="user">
<source>user</source>
<target>Benutzer: ein Eintrag pro Benutzer</target>
<target>Benutzer</target>
</trans-unit>
<trans-unit id="BlGGO.X" resname="activity">
<source>activity</source>
<target>Aktivität: ein Eintrag pro Aktivität</target>
<target>Aktivität</target>
</trans-unit>
<trans-unit id="oRa0mkz" resname="activity_user">
<source>activity_user</source>
<target>Aktivität und Benutzer</target>
</trans-unit>
<trans-unit id="JEIQ5IQ" resname="project">
<source>project</source>
<target>Projekt: ein Eintrag pro Projekt</target>
<target>Projekt</target>
</trans-unit>
<trans-unit id="AJm1952" resname="project_user">
<source>project_user</source>
<target>Projekt und Benutzer</target>
</trans-unit>
<trans-unit id="DodjLNR" resname="date">
<source>date</source>
<target>Datum: ein Eintrag pro Tag (verwendet Startdatum)</target>
<target>Datum</target>
</trans-unit>
<trans-unit id="0l1P9cz" resname="date_user">
<source>date_user</source>
<target>Datum und Benutzer</target>
</trans-unit>
<trans-unit id="iFDs7Zh" resname="weekly">
<source>weekly</source>
<target>Wöchentlich: ein Eintrag pro Woche (verwendet Startdatum)</target>
<target>Kalenderwoche</target>
</trans-unit>
<trans-unit id="aDtTH0H" resname="price">
<source>price</source>
<target>Preis: ein Eintrag je Preis</target>
<target>Preis</target>
</trans-unit>
</body>
</file>

View File

@@ -1,42 +1,58 @@
<?xml version="1.0" encoding="UTF-8"?>
<?xml version="1.0" encoding="utf-8"?>
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2">
<file source-language="en" target-language="de-CH" datatype="plaintext" original="invoice-calculator.en.xlf">
<body>
<trans-unit id="MviRJeB" resname="invoice_calculator">
<source>invoice_calculator</source>
<target state="translated">Summenberechnung</target>
<target>Gruppierung der Rechnungszeilen</target>
</trans-unit>
<trans-unit id="aDtTH0H" resname="price">
<source>price</source>
<target state="translated">Preis: ein Eintrag pro Preis</target>
<trans-unit id="KWX.j1b" resname="invoice_calculator.help">
<source>invoice_calculator.help</source>
<target>Anhand welcher Felder sollen Rechnungsposten gruppiert werden?</target>
</trans-unit>
<trans-unit id="N6juwc4" resname="default">
<source>default</source>
<target state="translated">Standard: ein Eintrag pro Zeiterfassung</target>
<target>Standard (eine Zeile pro Eintrag)</target>
</trans-unit>
<trans-unit id="_bAHi13" resname="short">
<source>short</source>
<target state="translated">Stunden: Aufzeichnungen summiert, ein Eintrag insgesamt</target>
<target>Alle (ein Eintrag insgesamt)</target>
</trans-unit>
<trans-unit id="BPiZbad" resname="user">
<source>user</source>
<target state="translated">Benutzer: ein Eintrag pro Benutzer</target>
<target>Benutzer</target>
</trans-unit>
<trans-unit id="BlGGO.X" resname="activity">
<source>activity</source>
<target state="translated">Aktivität: ein Eintrag pro Aktivität</target>
<target>Aktivität</target>
</trans-unit>
<trans-unit id="oRa0mkz" resname="activity_user">
<source>activity_user</source>
<target>Aktivität und Benutzer</target>
</trans-unit>
<trans-unit id="JEIQ5IQ" resname="project">
<source>project</source>
<target state="translated">Projekt: ein Eintrag pro Projekt</target>
<target>Projekt</target>
</trans-unit>
<trans-unit id="AJm1952" resname="project_user">
<source>project_user</source>
<target>Projekt und Benutzer</target>
</trans-unit>
<trans-unit id="DodjLNR" resname="date">
<source>date</source>
<target state="translated">Datum: ein Eintrag pro Tag (verwendet Startdatum)</target>
<target>Datum</target>
</trans-unit>
<trans-unit id="0l1P9cz" resname="date_user">
<source>date_user</source>
<target>Datum und Benutzer</target>
</trans-unit>
<trans-unit id="iFDs7Zh" resname="weekly">
<source>weekly</source>
<target state="translated">Wöchentlich: ein Eintrag pro Woche (verwendet Startdatum)</target>
<target>Kalenderwoche</target>
</trans-unit>
<trans-unit id="aDtTH0H" resname="price">
<source>price</source>
<target>Preis</target>
</trans-unit>
</body>
</file>

View File

@@ -4,39 +4,55 @@
<body>
<trans-unit id="MviRJeB" resname="invoice_calculator">
<source>invoice_calculator</source>
<target>Sum calculation</target>
<target>Grouping of invoice lines</target>
</trans-unit>
<trans-unit id="KWX.j1b" resname="invoice_calculator.help">
<source>invoice_calculator.help</source>
<target>Select by which fields the invoice items should be grouped</target>
</trans-unit>
<trans-unit id="N6juwc4" resname="default">
<source>default</source>
<target>Default: one entry per time record</target>
<target>Default (one row per entry)</target>
</trans-unit>
<trans-unit id="_bAHi13" resname="short">
<source>short</source>
<target>Hours: records summed up, one entry total</target>
<target>All (one entry total)</target>
</trans-unit>
<trans-unit id="BPiZbad" resname="user">
<source>user</source>
<target>User: one entry per user</target>
<target>User</target>
</trans-unit>
<trans-unit id="BlGGO.X" resname="activity">
<source>activity</source>
<target>Activity: one entry per activity</target>
<target>Activity</target>
</trans-unit>
<trans-unit id="oRa0mkz" resname="activity_user">
<source>activity_user</source>
<target>Activity and user</target>
</trans-unit>
<trans-unit id="JEIQ5IQ" resname="project">
<source>project</source>
<target>Project: one entry per project</target>
<target>Project</target>
</trans-unit>
<trans-unit id="AJm1952" resname="project_user">
<source>project_user</source>
<target>Project and user</target>
</trans-unit>
<trans-unit id="DodjLNR" resname="date">
<source>date</source>
<target>Date: one entry per day (uses start date)</target>
<target>Date</target>
</trans-unit>
<trans-unit id="0l1P9cz" resname="date_user">
<source>date_user</source>
<target>Date and user</target>
</trans-unit>
<trans-unit id="iFDs7Zh" resname="weekly">
<source>weekly</source>
<target>Weekly: one entry per week (uses start date)</target>
<target>Calendar week</target>
</trans-unit>
<trans-unit id="aDtTH0H" resname="price">
<source>price</source>
<target>Price: one entry per price</target>
<target>Price</target>
</trans-unit>
</body>
</file>

View File

@@ -4,7 +4,7 @@
<body>
<trans-unit id="hOcTt2G" resname="invoice_renderer">
<source>invoice_renderer</source>
<target>Rechnungsdokument</target>
<target>Rechnungsvorlage</target>
</trans-unit>
<trans-unit id="7mEUv6C" resname="help.upload">
<source>help.upload</source>
@@ -14,12 +14,10 @@
<source>download_invoice_renderer</source>
<target state="translated">Sie können weitere Vorlagen hier herunterladen</target>
</trans-unit>
<!-- Optgroups -->
<trans-unit id="E5BwZHT" resname="programmatic">
<source>programmatic</source>
<target>Zur Weiterverarbeitung</target>
</trans-unit>
<!-- Renderer -->
<trans-unit id="Utbj3k_" resname="invoice">
<source>invoice</source>
<target>Rechnung</target>

View File

@@ -4,7 +4,7 @@
<body>
<trans-unit id="hOcTt2G" resname="invoice_renderer">
<source>invoice_renderer</source>
<target state="translated">Rechnungsdokument</target>
<target state="translated">Rechnungsvorlage</target>
</trans-unit>
<trans-unit id="Utbj3k_" resname="invoice">
<source>invoice</source>

View File

@@ -4,7 +4,7 @@
<body>
<trans-unit id="hOcTt2G" resname="invoice_renderer">
<source>invoice_renderer</source>
<target>Invoice document</target>
<target>Invoice template</target>
</trans-unit>
<trans-unit id="7mEUv6C" resname="help.upload">
<source>help.upload</source>
@@ -14,12 +14,10 @@
<source>download_invoice_renderer</source>
<target state="translated">You can download further templates here</target>
</trans-unit>
<!-- Optgroups -->
<trans-unit id="E5BwZHT" resname="programmatic">
<source>programmatic</source>
<target>For further processing</target>
</trans-unit>
<!-- Renderer -->
<trans-unit id="Utbj3k_" resname="invoice">
<source>invoice</source>
<target>Invoice</target>