diff --git a/src/Invoice/Calculator/WeeklyInvoiceCalculator.php b/src/Invoice/Calculator/WeeklyInvoiceCalculator.php
new file mode 100644
index 00000000..5f50303b
--- /dev/null
+++ b/src/Invoice/Calculator/WeeklyInvoiceCalculator.php
@@ -0,0 +1,32 @@
+getBegin()->format('W');
+ }
+
+ /**
+ * @return string
+ */
+ public function getId(): string
+ {
+ return 'weekly';
+ }
+}
diff --git a/src/Invoice/Hydrator/InvoiceItemDefaultHydrator.php b/src/Invoice/Hydrator/InvoiceItemDefaultHydrator.php
index 438ed95c..19c95d06 100644
--- a/src/Invoice/Hydrator/InvoiceItemDefaultHydrator.php
+++ b/src/Invoice/Hydrator/InvoiceItemDefaultHydrator.php
@@ -83,6 +83,8 @@ class InvoiceItemDefaultHydrator implements InvoiceItemHydrator
'entry.end_time' => $formatter->getFormattedTime($end),
'entry.end_timestamp' => $end->getTimestamp(),
'entry.date' => $formatter->getFormattedDateTime($begin),
+ 'entry.week' => \intval($begin->format('W')),
+ 'entry.weekyear' => $begin->format('o'),
'entry.user_id' => $user->getId(),
'entry.user_name' => $user->getUsername(),
'entry.user_title' => $user->getTitle(),
diff --git a/tests/Invoice/Calculator/WeeklyInvoiceCalculatorTest.php b/tests/Invoice/Calculator/WeeklyInvoiceCalculatorTest.php
new file mode 100644
index 00000000..bc5ddd66
--- /dev/null
+++ b/tests/Invoice/Calculator/WeeklyInvoiceCalculatorTest.php
@@ -0,0 +1,137 @@
+assertEmptyModel(new WeeklyInvoiceCalculator());
+ }
+
+ public function testWithMultipleEntries()
+ {
+ $customer = new Customer();
+ $template = new InvoiceTemplate();
+ $template->setVat(19);
+
+ $user = $this->getMockBuilder(User::class)->onlyMethods(['getId'])->disableOriginalConstructor()->getMock();
+ $user->method('getId')->willReturn(1);
+
+ $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($user)
+ ->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($user)
+ ->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($user)
+ ->setActivity((new Activity())->setName('foo'))
+ ->setProject($project1);
+
+ $timesheet4 = new Timesheet();
+ $timesheet4
+ ->setBegin(new DateTime())
+ ->setEnd(new DateTime('2018-11-28'))
+ ->setDuration(400)
+ ->setRate(1947.99)
+ ->setUser($user)
+ ->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(new User())
+ ->setActivity(new Activity())
+ ->setProject($project3);
+
+ $entries = [$timesheet, $timesheet2, $timesheet3, $timesheet4, $timesheet5];
+
+ $query = new InvoiceQuery();
+ $query->setProjects([$project1]);
+
+ $model = new InvoiceModel(new DebugFormatter());
+ $model->setCustomer($customer);
+ $model->setTemplate($template);
+ $model->addEntries($entries);
+ $model->setQuery($query);
+
+ $sut = new WeeklyInvoiceCalculator();
+ $sut->setModel($model);
+
+ $this->assertEquals('weekly', $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(2, $entries);
+ $this->assertEquals(573.13, $entries[0]->getRate());
+ $this->assertEquals(1947.99, $entries[1]->getRate());
+ self::assertEquals(2521.12, $entries[0]->getRate() + $entries[1]->getRate());
+ }
+
+ public function testDescriptionByTimesheet()
+ {
+ $this->assertDescription(new WeeklyInvoiceCalculator(), false, false);
+ }
+}
diff --git a/tests/Invoice/Hydrator/InvoiceItemDefaultHydratorTest.php b/tests/Invoice/Hydrator/InvoiceItemDefaultHydratorTest.php
index 92254b94..396a51e6 100644
--- a/tests/Invoice/Hydrator/InvoiceItemDefaultHydratorTest.php
+++ b/tests/Invoice/Hydrator/InvoiceItemDefaultHydratorTest.php
@@ -62,6 +62,8 @@ class InvoiceItemDefaultHydratorTest extends TestCase
'entry.end_time',
'entry.end_timestamp',
'entry.date',
+ 'entry.week',
+ 'entry.weekyear',
'entry.user_id',
'entry.user_name',
'entry.user_alias',
diff --git a/tests/Invoice/Renderer/DebugRendererTest.php b/tests/Invoice/Renderer/DebugRendererTest.php
index d14a5cb9..f2934532 100644
--- a/tests/Invoice/Renderer/DebugRendererTest.php
+++ b/tests/Invoice/Renderer/DebugRendererTest.php
@@ -225,6 +225,8 @@ class DebugRendererTest extends TestCase
'entry.end_time',
'entry.end_timestamp',
'entry.date',
+ 'entry.week',
+ 'entry.weekyear',
'entry.user_id',
'entry.user_name',
'entry.user_alias',
diff --git a/translations/invoice-calculator.de.xlf b/translations/invoice-calculator.de.xlf
index 96feaaac..74755b2b 100644
--- a/translations/invoice-calculator.de.xlf
+++ b/translations/invoice-calculator.de.xlf
@@ -30,6 +30,10 @@
date
Datum: ein Eintrag pro Tag (verwendet Startdatum)
+
+ weekly
+ Wöchentlich: ein Eintrag pro Woche (verwendet Startdatum)
+