diff --git a/src/API/TimesheetController.php b/src/API/TimesheetController.php index e7c3c9f9..0cb5cd95 100644 --- a/src/API/TimesheetController.php +++ b/src/API/TimesheetController.php @@ -564,7 +564,7 @@ class TimesheetController extends BaseApiController * 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, meta (default: nothing is copied)") + * @Rest\RequestParam(name="copy", requirements="all|tags|rates|meta|description", strict=true, nullable=true, description="Whether data should be copied to the new entry. Allowed values: all, tags, rates, description, meta (default: nothing is copied)") * * @param int $id * @return Response @@ -597,6 +597,11 @@ class TimesheetController extends BaseApiController ; if (null !== ($copy = $paramFetcher->get('copy'))) { + if (in_array($copy, ['rates', 'all'])) { + $entry->setHourlyRate($timesheet->getHourlyRate()); + $entry->setFixedRate($timesheet->getFixedRate()); + } + if (in_array($copy, ['description', 'all'])) { $entry->setDescription($timesheet->getDescription()); } diff --git a/src/Controller/InvoiceController.php b/src/Controller/InvoiceController.php index 77d7ecee..7b8c35b8 100644 --- a/src/Controller/InvoiceController.php +++ b/src/Controller/InvoiceController.php @@ -13,8 +13,8 @@ use App\Entity\InvoiceTemplate; use App\Entity\Timesheet; use App\Form\InvoiceTemplateForm; use App\Form\Toolbar\InvoiceToolbarForm; +use App\Invoice\InvoiceModel; use App\Invoice\ServiceInvoice; -use App\Model\InvoiceModel; use App\Repository\InvoiceTemplateRepository; use App\Repository\Query\BaseQuery; use App\Repository\Query\InvoiceQuery; diff --git a/src/Invoice/Calculator/AbstractCalculator.php b/src/Invoice/Calculator/AbstractCalculator.php index 30705912..89b01f9b 100644 --- a/src/Invoice/Calculator/AbstractCalculator.php +++ b/src/Invoice/Calculator/AbstractCalculator.php @@ -9,8 +9,8 @@ namespace App\Invoice\Calculator; -use App\Entity\Timesheet; -use App\Model\InvoiceModel; +use App\Invoice\InvoiceItem; +use App\Invoice\InvoiceModel; abstract class AbstractCalculator { @@ -25,7 +25,7 @@ abstract class AbstractCalculator protected $model; /** - * @return Timesheet[] + * @return InvoiceItem[] */ abstract public function getEntries(); @@ -103,7 +103,9 @@ abstract class AbstractCalculator { $time = 0; foreach ($this->model->getEntries() as $entry) { - $time += $entry->getDuration(); + if (null === $entry->getFixedRate()) { + $time += $entry->getDuration(); + } } return $time; diff --git a/src/Invoice/Calculator/AbstractMergedCalculator.php b/src/Invoice/Calculator/AbstractMergedCalculator.php index 0418b5a2..d66c0478 100644 --- a/src/Invoice/Calculator/AbstractMergedCalculator.php +++ b/src/Invoice/Calculator/AbstractMergedCalculator.php @@ -10,52 +10,60 @@ namespace App\Invoice\Calculator; use App\Entity\Timesheet; +use App\Invoice\InvoiceItem; abstract class AbstractMergedCalculator extends AbstractCalculator { - /** - * @param Timesheet $timesheet - * @param Timesheet $entry - */ - protected function mergeTimesheets(Timesheet $timesheet, Timesheet $entry) + protected function mergeTimesheets(InvoiceItem $invoiceItem, Timesheet $entry) { - $timesheet->setUser($entry->getUser()); - $timesheet->setRate($timesheet->getRate() + $entry->getRate()); + $invoiceItem->setAmount($invoiceItem->getAmount() + 1); + $invoiceItem->setUser($entry->getUser()); + $invoiceItem->setRate($invoiceItem->getRate() + $entry->getRate()); + $invoiceItem->setDuration($invoiceItem->getDuration() + $entry->getDuration()); - if (null !== $timesheet->getFixedRate() || null !== $entry->getFixedRate()) { - $timesheet->setFixedRate($timesheet->getRate()); - } - if (null === $timesheet->getHourlyRate() && null !== $entry->getHourlyRate() && $timesheet->getHourlyRate() !== $entry->getHourlyRate()) { - $timesheet->setHourlyRate($entry->getHourlyRate()); + if (null !== $entry->getFixedRate()) { + /* + if (null !== $invoiceItem->getFixedRate() && $invoiceItem->getFixedRate() !== $entry->getFixedRate()) { + throw new \InvalidArgumentException('Cannot mix different fixed-rates'); + } + */ + $invoiceItem->setFixedRate($entry->getFixedRate()); } - $timesheet->setDuration($timesheet->getDuration() + $entry->getDuration()); - - if (null === $timesheet->getBegin() || $timesheet->getBegin()->getTimestamp() > $entry->getBegin()->getTimestamp()) { - $timesheet->setBegin($entry->getBegin()); + if (null !== $entry->getHourlyRate()) { + /* + if (null !== $invoiceItem->getHourlyRate() && $invoiceItem->getHourlyRate() !== $entry->getHourlyRate()) { + throw new \InvalidArgumentException('Cannot mix different hourly-rates'); + } + */ + $invoiceItem->setHourlyRate($entry->getHourlyRate()); } - if (null === $timesheet->getEnd() || $timesheet->getEnd()->getTimestamp() < $entry->getEnd()->getTimestamp()) { - $timesheet->setEnd($entry->getEnd()); + if (null === $invoiceItem->getBegin() || $invoiceItem->getBegin()->getTimestamp() > $entry->getBegin()->getTimestamp()) { + $invoiceItem->setBegin($entry->getBegin()); + } + + if (null === $invoiceItem->getEnd() || $invoiceItem->getEnd()->getTimestamp() < $entry->getEnd()->getTimestamp()) { + $invoiceItem->setEnd($entry->getEnd()); } if (null !== $this->model->getQuery()->getActivity()) { - $timesheet->setActivity($this->model->getQuery()->getActivity()); - $timesheet->setDescription($this->model->getQuery()->getActivity()->getName()); + $invoiceItem->setActivity($this->model->getQuery()->getActivity()); + $invoiceItem->setDescription($this->model->getQuery()->getActivity()->getName()); } elseif (null !== $this->model->getQuery()->getProject()) { - $timesheet->setDescription($this->model->getQuery()->getProject()->getName()); + $invoiceItem->setDescription($this->model->getQuery()->getProject()->getName()); } - if (null === $timesheet->getActivity()) { - $timesheet->setActivity($entry->getActivity()); + if (null === $invoiceItem->getActivity()) { + $invoiceItem->setActivity($entry->getActivity()); } - if (null === $timesheet->getProject()) { - $timesheet->setProject($entry->getProject()); + if (null === $invoiceItem->getProject()) { + $invoiceItem->setProject($entry->getProject()); } - if (empty($timesheet->getDescription())) { - $timesheet->setDescription($entry->getActivity()->getName()); + if (empty($invoiceItem->getDescription())) { + $invoiceItem->setDescription($entry->getActivity()->getName()); } } } diff --git a/src/Invoice/Calculator/AbstractSumInvoiceCalculator.php b/src/Invoice/Calculator/AbstractSumInvoiceCalculator.php index 5dc8f74d..f1d3153c 100644 --- a/src/Invoice/Calculator/AbstractSumInvoiceCalculator.php +++ b/src/Invoice/Calculator/AbstractSumInvoiceCalculator.php @@ -11,16 +11,17 @@ namespace App\Invoice\Calculator; use App\Entity\Timesheet; use App\Invoice\CalculatorInterface; +use App\Invoice\InvoiceItem; /** - * A calculator that sums up the timesheet records by activity. + * An abstract calculator that sums up the timesheet records. */ abstract class AbstractSumInvoiceCalculator extends AbstractMergedCalculator implements CalculatorInterface { abstract protected function calculateSumIdentifier(Timesheet $timesheet): string; /** - * @return Timesheet[] + * @return InvoiceItem[] */ public function getEntries() { @@ -29,18 +30,25 @@ abstract class AbstractSumInvoiceCalculator extends AbstractMergedCalculator imp return []; } - /** @var Timesheet[] $timesheets */ - $timesheets = []; + /** @var InvoiceItem[] $invoiceItems */ + $invoiceItems = []; foreach ($entries as $entry) { $id = $this->calculateSumIdentifier($entry); - if (!isset($timesheets[$id])) { - $timesheets[$id] = new Timesheet(); + + if (null !== $entry->getFixedRate()) { + $id = $id . '_fixed_' . (string) $entry->getFixedRate(); + } else { + $id = $id . '_hourly_' . (string) $entry->getHourlyRate(); } - $timesheet = $timesheets[$id]; + + if (!isset($invoiceItems[$id])) { + $invoiceItems[$id] = new InvoiceItem(); + } + $timesheet = $invoiceItems[$id]; $this->mergeTimesheets($timesheet, $entry); } - return array_values($timesheets); + return array_values($invoiceItems); } } diff --git a/src/Invoice/Calculator/DateInvoiceCalculator.php b/src/Invoice/Calculator/DateInvoiceCalculator.php index 9ba020af..029fbf72 100644 --- a/src/Invoice/Calculator/DateInvoiceCalculator.php +++ b/src/Invoice/Calculator/DateInvoiceCalculator.php @@ -19,6 +19,10 @@ class DateInvoiceCalculator extends AbstractSumInvoiceCalculator implements Calc { protected function calculateSumIdentifier(Timesheet $timesheet): string { + if (null === $timesheet->getBegin()) { + throw new \Exception('Cannot handle timesheets without start date'); + } + return $timesheet->getBegin()->format('Y-m-d'); } diff --git a/src/Invoice/Calculator/DefaultCalculator.php b/src/Invoice/Calculator/DefaultCalculator.php index ee6718dd..4a6cc5c0 100644 --- a/src/Invoice/Calculator/DefaultCalculator.php +++ b/src/Invoice/Calculator/DefaultCalculator.php @@ -9,8 +9,8 @@ namespace App\Invoice\Calculator; -use App\Entity\Timesheet; use App\Invoice\CalculatorInterface; +use App\Invoice\InvoiceItem; /** * Class DefaultCalculator works on all given entries using: @@ -18,14 +18,25 @@ use App\Invoice\CalculatorInterface; * - the invoice template vat rate * - the entries rate */ -class DefaultCalculator extends AbstractCalculator implements CalculatorInterface +class DefaultCalculator extends AbstractMergedCalculator implements CalculatorInterface { /** - * @return Timesheet[] + * @return InvoiceItem[] */ public function getEntries() { - return $this->model->getEntries(); + $entries = []; + + foreach ($this->model->getEntries() as $entry) { + $item = new InvoiceItem(); + $this->mergeTimesheets($item, $entry); + foreach ($entry->getVisibleMetaFields() as $field) { + $item->addAdditionalField($field->getName(), $field->getValue()); + } + $entries[] = $item; + } + + return $entries; } /** diff --git a/src/Invoice/Calculator/ShortInvoiceCalculator.php b/src/Invoice/Calculator/ShortInvoiceCalculator.php index 16339db4..96e74f30 100644 --- a/src/Invoice/Calculator/ShortInvoiceCalculator.php +++ b/src/Invoice/Calculator/ShortInvoiceCalculator.php @@ -11,6 +11,7 @@ namespace App\Invoice\Calculator; use App\Entity\Timesheet; use App\Invoice\CalculatorInterface; +use App\Invoice\InvoiceItem; /** * A calculator that sums up all timesheet records from the model and returns only one @@ -19,7 +20,7 @@ use App\Invoice\CalculatorInterface; class ShortInvoiceCalculator extends AbstractMergedCalculator implements CalculatorInterface { /** - * @return Timesheet[] + * @return InvoiceItem[] */ public function getEntries() { @@ -28,16 +29,27 @@ class ShortInvoiceCalculator extends AbstractMergedCalculator implements Calcula return []; } - $timesheet = new Timesheet(); + $invoiceItem = new InvoiceItem(); + $keys = []; foreach ($entries as $entry) { - $this->mergeTimesheets($timesheet, $entry); + $key = 'hourly_' . (string) $entry->getHourlyRate(); + if (null !== $entry->getFixedRate()) { + $key = 'fixed_' . (string) $entry->getFixedRate(); + } + if (!in_array($key, $keys)) { + $keys[] = $key; + } + $this->mergeTimesheets($invoiceItem, $entry); } - $timesheet->setFixedRate($timesheet->getRate()); - $timesheet->setHourlyRate($timesheet->getRate()); + if (count($keys) > 1) { + $invoiceItem->setAmount(1); + $invoiceItem->setFixedRate($invoiceItem->getRate()); + } + $invoiceItem->setHourlyRate($invoiceItem->getRate()); - return [$timesheet]; + return [$invoiceItem]; } /** diff --git a/src/Invoice/Calculator/UserInvoiceCalculator.php b/src/Invoice/Calculator/UserInvoiceCalculator.php index 29be1a44..e67256de 100644 --- a/src/Invoice/Calculator/UserInvoiceCalculator.php +++ b/src/Invoice/Calculator/UserInvoiceCalculator.php @@ -9,8 +9,8 @@ namespace App\Invoice\Calculator; -use App\Entity\Timesheet; use App\Invoice\CalculatorInterface; +use App\Invoice\InvoiceItem; /** * A calculator that sums up the timesheet records by user. @@ -18,7 +18,7 @@ use App\Invoice\CalculatorInterface; class UserInvoiceCalculator extends AbstractMergedCalculator implements CalculatorInterface { /** - * @return Timesheet[] + * @return InvoiceItem[] */ public function getEntries() { @@ -27,18 +27,19 @@ class UserInvoiceCalculator extends AbstractMergedCalculator implements Calculat return []; } - /** @var Timesheet[] $timesheets */ - $timesheets = []; + /** @var InvoiceItem[] $invoiceItems */ + $invoiceItems = []; foreach ($entries as $entry) { - if (!isset($timesheets[$entry->getUser()->getId()])) { - $timesheets[$entry->getUser()->getId()] = new Timesheet(); + $id = $entry->getUser()->getId(); + if (!isset($invoiceItems[$id])) { + $invoiceItems[$id] = new InvoiceItem(); } - $timesheet = $timesheets[$entry->getUser()->getId()]; - $this->mergeTimesheets($timesheet, $entry); + $invoiceItem = $invoiceItems[$id]; + $this->mergeTimesheets($invoiceItem, $entry); } - return array_values($timesheets); + return array_values($invoiceItems); } /** diff --git a/src/Invoice/CalculatorInterface.php b/src/Invoice/CalculatorInterface.php index 88736ab8..a36ba763 100644 --- a/src/Invoice/CalculatorInterface.php +++ b/src/Invoice/CalculatorInterface.php @@ -9,18 +9,15 @@ namespace App\Invoice; -use App\Entity\Timesheet; -use App\Model\InvoiceModel; - /** * CalculatorInterface defines all methods for any invoice price calculator. */ interface CalculatorInterface { /** - * Return the timesheet records that will be displayed on the invoice. + * Return the invoice items that will be displayed on the invoice. * - * @return Timesheet[] + * @return InvoiceItem[] */ public function getEntries(); diff --git a/src/Invoice/InvoiceItem.php b/src/Invoice/InvoiceItem.php new file mode 100644 index 00000000..bc46f19b --- /dev/null +++ b/src/Invoice/InvoiceItem.php @@ -0,0 +1,215 @@ +additionalFields[$name] = $value; + + return $this; + } + + public function getAdditionalFields(): array + { + return $this->additionalFields; + } + + public function getActivity(): ?Activity + { + return $this->activity; + } + + public function setActivity(?Activity $activity): InvoiceItem + { + $this->activity = $activity; + + return $this; + } + + public function getProject(): ?Project + { + return $this->project; + } + + public function setProject(?Project $project): InvoiceItem + { + $this->project = $project; + + return $this; + } + + public function isFixedRate(): bool + { + return null !== $this->getFixedRate(); + } + + public function getFixedRate(): ?float + { + return $this->fixedRate; + } + + public function setFixedRate(?float $fixedRate): InvoiceItem + { + $this->fixedRate = $fixedRate; + + return $this; + } + + public function getHourlyRate(): ?float + { + return $this->hourlyRate; + } + + public function setHourlyRate(float $hourlyRate): InvoiceItem + { + $this->hourlyRate = $hourlyRate; + + return $this; + } + + public function getRate(): float + { + return $this->rate; + } + + public function setRate(float $rate): InvoiceItem + { + $this->rate = $rate; + + return $this; + } + + public function getAmount(): int + { + return $this->amount; + } + + public function setAmount(int $amount): InvoiceItem + { + $this->amount = $amount; + + return $this; + } + + public function getDescription(): ?string + { + return $this->description; + } + + public function setDescription(?string $description): InvoiceItem + { + $this->description = $description; + + return $this; + } + + public function getDuration(): int + { + return $this->duration; + } + + public function setDuration(int $duration): InvoiceItem + { + $this->duration = $duration; + + return $this; + } + + public function getBegin(): ?\DateTime + { + return $this->begin; + } + + public function setBegin(\DateTime $begin): InvoiceItem + { + $this->begin = $begin; + + return $this; + } + + public function getEnd(): ?\DateTime + { + return $this->end; + } + + public function setEnd(?\DateTime $end): InvoiceItem + { + $this->end = $end; + + return $this; + } + + public function getUser(): ?User + { + return $this->user; + } + + public function setUser(?User $user): InvoiceItem + { + $this->user = $user; + + return $this; + } +} diff --git a/src/Model/InvoiceModel.php b/src/Invoice/InvoiceModel.php similarity index 97% rename from src/Model/InvoiceModel.php rename to src/Invoice/InvoiceModel.php index f189eb95..7ab4e92b 100644 --- a/src/Model/InvoiceModel.php +++ b/src/Invoice/InvoiceModel.php @@ -7,13 +7,11 @@ * file that was distributed with this source code. */ -namespace App\Model; +namespace App\Invoice; use App\Entity\Customer; use App\Entity\InvoiceTemplate; use App\Entity\Timesheet; -use App\Invoice\CalculatorInterface; -use App\Invoice\NumberGeneratorInterface; use App\Repository\Query\InvoiceQuery; /** diff --git a/src/Invoice/NumberGenerator/DateNumberGenerator.php b/src/Invoice/NumberGenerator/DateNumberGenerator.php index a45f99ba..a9dc2ce6 100644 --- a/src/Invoice/NumberGenerator/DateNumberGenerator.php +++ b/src/Invoice/NumberGenerator/DateNumberGenerator.php @@ -9,8 +9,8 @@ namespace App\Invoice\NumberGenerator; +use App\Invoice\InvoiceModel; use App\Invoice\NumberGeneratorInterface; -use App\Model\InvoiceModel; /** * Class DateNumberGenerator generates the invoice number based on the current day. diff --git a/src/Invoice/NumberGeneratorInterface.php b/src/Invoice/NumberGeneratorInterface.php index 471fb49e..ef37c598 100644 --- a/src/Invoice/NumberGeneratorInterface.php +++ b/src/Invoice/NumberGeneratorInterface.php @@ -9,8 +9,6 @@ namespace App\Invoice; -use App\Model\InvoiceModel; - /** * Class NumberGeneratorInterface defines all methods that invoice number generator have to implement. */ diff --git a/src/Invoice/Renderer/AbstractSpreadsheetRenderer.php b/src/Invoice/Renderer/AbstractSpreadsheetRenderer.php index 3d772194..f05452f5 100644 --- a/src/Invoice/Renderer/AbstractSpreadsheetRenderer.php +++ b/src/Invoice/Renderer/AbstractSpreadsheetRenderer.php @@ -10,7 +10,7 @@ namespace App\Invoice\Renderer; use App\Entity\InvoiceDocument; -use App\Model\InvoiceModel; +use App\Invoice\InvoiceModel; use PhpOffice\PhpSpreadsheet\IOFactory; use PhpOffice\PhpSpreadsheet\Spreadsheet; use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet; diff --git a/src/Invoice/Renderer/DocxRenderer.php b/src/Invoice/Renderer/DocxRenderer.php index e4a2571c..465b519c 100644 --- a/src/Invoice/Renderer/DocxRenderer.php +++ b/src/Invoice/Renderer/DocxRenderer.php @@ -10,8 +10,8 @@ namespace App\Invoice\Renderer; use App\Entity\InvoiceDocument; +use App\Invoice\InvoiceModel; use App\Invoice\RendererInterface; -use App\Model\InvoiceModel; use PhpOffice\PhpWord\Escaper\Xml; use PhpOffice\PhpWord\Exception\Exception as OfficeException; use PhpOffice\PhpWord\Settings; diff --git a/src/Invoice/Renderer/RendererTrait.php b/src/Invoice/Renderer/RendererTrait.php index bc5c0014..ed451dc9 100644 --- a/src/Invoice/Renderer/RendererTrait.php +++ b/src/Invoice/Renderer/RendererTrait.php @@ -10,9 +10,9 @@ namespace App\Invoice\Renderer; use App\Entity\InvoiceDocument; -use App\Entity\Timesheet; use App\Entity\UserPreference; -use App\Model\InvoiceModel; +use App\Invoice\InvoiceItem; +use App\Invoice\InvoiceModel; use Symfony\Component\HttpFoundation\BinaryFileResponse; use Symfony\Component\HttpFoundation\ResponseHeaderBag; @@ -176,40 +176,36 @@ trait RendererTrait return $values; } - /** - * @param Timesheet $timesheet - * @return array - */ - protected function timesheetToArray(Timesheet $timesheet) + protected function timesheetToArray(InvoiceItem $invoiceItem): array { - $rate = $timesheet->getRate(); - $hourlyRate = $timesheet->getHourlyRate(); - $amount = $this->getFormattedDuration($timesheet->getDuration()); - $description = $timesheet->getDescription(); + $rate = $invoiceItem->getRate(); + $hourlyRate = $invoiceItem->getHourlyRate(); + $amount = $this->getFormattedDuration($invoiceItem->getDuration()); + $description = $invoiceItem->getDescription(); - if (null !== $timesheet->getFixedRate()) { - $rate = $timesheet->getFixedRate(); - $hourlyRate = $timesheet->getFixedRate(); - $amount = 1; // FIXME fixed rates + if (null !== $invoiceItem->getFixedRate()) { + $rate = $invoiceItem->getFixedRate(); + $hourlyRate = $invoiceItem->getFixedRate(); + $amount = $invoiceItem->getAmount(); } if (empty($description)) { - $description = $timesheet->getActivity()->getName(); + $description = $invoiceItem->getActivity()->getName(); } - $user = $timesheet->getUser(); + $user = $invoiceItem->getUser(); if (empty($hourlyRate)) { $hourlyRate = $user->getPreferenceValue(UserPreference::HOURLY_RATE); } - $activity = $timesheet->getActivity(); - $project = $timesheet->getProject(); + $activity = $invoiceItem->getActivity(); + $project = $invoiceItem->getProject(); $customer = $project->getCustomer(); $currency = $customer->getCurrency(); - $begin = $timesheet->getBegin(); - $end = $timesheet->getEnd(); + $begin = $invoiceItem->getBegin(); + $end = $invoiceItem->getEnd(); $values = [ 'entry.row' => '', @@ -218,9 +214,9 @@ trait RendererTrait 'entry.rate' => $this->getFormattedMoney($hourlyRate, $currency), 'entry.total' => $this->getFormattedMoney($rate, $currency), 'entry.currency' => $currency, - 'entry.duration' => $timesheet->getDuration(), - 'entry.duration_decimal' => $this->getFormattedDecimalDuration($timesheet->getDuration()), - 'entry.duration_minutes' => number_format($timesheet->getDuration() / 60), + 'entry.duration' => $invoiceItem->getDuration(), + 'entry.duration_decimal' => $this->getFormattedDecimalDuration($invoiceItem->getDuration()), + 'entry.duration_minutes' => number_format($invoiceItem->getDuration() / 60), 'entry.begin' => $this->getFormattedDateTime($begin), 'entry.begin_time' => $this->getFormattedTime($begin), 'entry.begin_timestamp' => $begin->getTimestamp(), @@ -240,9 +236,9 @@ trait RendererTrait 'entry.customer_id' => $customer->getId(), ]; - foreach ($timesheet->getVisibleMetaFields() as $metaField) { + foreach ($invoiceItem->getAdditionalFields() as $name => $value) { $values = array_merge($values, [ - 'entry.meta.' . $metaField->getName() => $metaField->getValue(), + 'entry.meta.' . $name => $value, ]); } diff --git a/src/Invoice/Renderer/TwigRenderer.php b/src/Invoice/Renderer/TwigRenderer.php index 72f674fb..ab6b52d7 100644 --- a/src/Invoice/Renderer/TwigRenderer.php +++ b/src/Invoice/Renderer/TwigRenderer.php @@ -10,8 +10,8 @@ namespace App\Invoice\Renderer; use App\Entity\InvoiceDocument; +use App\Invoice\InvoiceModel; use App\Invoice\RendererInterface; -use App\Model\InvoiceModel; use Symfony\Component\HttpFoundation\Response; use Twig\Environment; diff --git a/src/Invoice/RendererInterface.php b/src/Invoice/RendererInterface.php index 87d4a032..bf7faaee 100644 --- a/src/Invoice/RendererInterface.php +++ b/src/Invoice/RendererInterface.php @@ -10,7 +10,6 @@ namespace App\Invoice; use App\Entity\InvoiceDocument; -use App\Model\InvoiceModel; use Symfony\Component\HttpFoundation\Response; interface RendererInterface diff --git a/templates/invoice/index.html.twig b/templates/invoice/index.html.twig index 3ba1db71..2d0fd713 100644 --- a/templates/invoice/index.html.twig +++ b/templates/invoice/index.html.twig @@ -10,6 +10,7 @@ 'description': 'hidden-xs hidden-sm', 'unit_price': 'hidden-xs text-center', 'amount': 'text-center', + 'duration': 'hidden-xs text-center', 'total_rate': 'text-right alwaysVisible', } %} @@ -54,14 +55,13 @@ {% set entries = model.calculator.entries %} {{ tables.data_table_header(tableName, columns) }} {% for entry in entries %} - {% set duration = entry.duration|duration %} + {% set amount = entry.amount %} + {% set duration = entry.duration|duration_decimal ~ ' / ' ~ entry.duration|duration %} + {% set rate = 0 %} {% if entry.fixedRate is not null %} {% set rate = entry.fixedRate %} - {% set duration = 1 %}{# FIXME fixed rates #} {% elseif entry.hourlyRate is not null %} {% set rate = entry.hourlyRate %} - {% else %} - {% set rate = entry.user.preferenceValue('hourly_rate') %} {% endif %} {{ entry.begin|date_short }} @@ -74,7 +74,8 @@ {% endif %} {{ rate|money(model.calculator.currency) }} - {{ duration }} + {{ amount }} + {{ duration }} {{ entry.rate|money(model.calculator.currency) }} {% endfor %} diff --git a/templates/invoice/renderer/default.html.twig b/templates/invoice/renderer/default.html.twig index a8382bd7..83d7d33a 100644 --- a/templates/invoice/renderer/default.html.twig +++ b/templates/invoice/renderer/default.html.twig @@ -67,7 +67,7 @@ {% set duration = entry.duration|duration %} {% if entry.fixedRate is not null %} {% set rate = entry.fixedRate %} - {% set duration = 1 %}{# FIXME fixed rates #} + {% set duration = entry.amount %} {% elseif entry.hourlyRate is not null %} {% set rate = entry.hourlyRate %} {% else %} diff --git a/templates/invoice/renderer/freelancer.html.twig b/templates/invoice/renderer/freelancer.html.twig index f4f08293..e74c5d70 100644 --- a/templates/invoice/renderer/freelancer.html.twig +++ b/templates/invoice/renderer/freelancer.html.twig @@ -80,7 +80,7 @@ {% set duration = entry.duration|duration %} {% if entry.fixedRate is not null %} {% set rate = entry.fixedRate %} - {% set duration = 1 %}{# FIXME fixed rates #} + {% set duration = entry.amount %} {% elseif entry.hourlyRate is not null %} {% set rate = entry.hourlyRate %} {% else %} diff --git a/tests/Invoice/Calculator/AbstractCalculatorTest.php b/tests/Invoice/Calculator/AbstractCalculatorTest.php index 523244cc..8d44da0f 100644 --- a/tests/Invoice/Calculator/AbstractCalculatorTest.php +++ b/tests/Invoice/Calculator/AbstractCalculatorTest.php @@ -16,7 +16,7 @@ use App\Entity\Project; use App\Entity\Timesheet; use App\Entity\User; use App\Invoice\CalculatorInterface; -use App\Model\InvoiceModel; +use App\Invoice\InvoiceModel; use App\Repository\Query\InvoiceQuery; use PHPUnit\Framework\TestCase; diff --git a/tests/Invoice/Calculator/ActivityInvoiceCalculatorTest.php b/tests/Invoice/Calculator/ActivityInvoiceCalculatorTest.php index f2c65d3b..13d7da59 100644 --- a/tests/Invoice/Calculator/ActivityInvoiceCalculatorTest.php +++ b/tests/Invoice/Calculator/ActivityInvoiceCalculatorTest.php @@ -16,7 +16,7 @@ use App\Entity\Project; use App\Entity\Timesheet; use App\Entity\User; use App\Invoice\Calculator\ActivityInvoiceCalculator; -use App\Model\InvoiceModel; +use App\Invoice\InvoiceModel; use App\Repository\Query\InvoiceQuery; /** diff --git a/tests/Invoice/Calculator/DateInvoiceCalculatorTest.php b/tests/Invoice/Calculator/DateInvoiceCalculatorTest.php index 51637bae..2116acdf 100644 --- a/tests/Invoice/Calculator/DateInvoiceCalculatorTest.php +++ b/tests/Invoice/Calculator/DateInvoiceCalculatorTest.php @@ -16,7 +16,7 @@ use App\Entity\Project; use App\Entity\Timesheet; use App\Entity\User; use App\Invoice\Calculator\DateInvoiceCalculator; -use App\Model\InvoiceModel; +use App\Invoice\InvoiceModel; use App\Repository\Query\InvoiceQuery; use DateTime; diff --git a/tests/Invoice/Calculator/DefaultCalculatorTest.php b/tests/Invoice/Calculator/DefaultCalculatorTest.php index d27050ee..7f20c3ca 100644 --- a/tests/Invoice/Calculator/DefaultCalculatorTest.php +++ b/tests/Invoice/Calculator/DefaultCalculatorTest.php @@ -9,11 +9,13 @@ namespace App\Tests\Invoice\Calculator; +use App\Entity\Activity; use App\Entity\Customer; use App\Entity\InvoiceTemplate; use App\Entity\Timesheet; use App\Invoice\Calculator\DefaultCalculator; -use App\Model\InvoiceModel; +use App\Invoice\InvoiceModel; +use App\Repository\Query\InvoiceQuery; /** * @covers \App\Invoice\Calculator\DefaultCalculator @@ -33,16 +35,22 @@ class DefaultCalculatorTest extends AbstractCalculatorTest $template->setVat(19); $timesheet = new Timesheet(); + $timesheet->setBegin(new \DateTime()); $timesheet->setDuration(3600); $timesheet->setRate(293.27); + $timesheet->setActivity(new Activity()); $timesheet2 = new Timesheet(); + $timesheet2->setBegin(new \DateTime()); $timesheet2->setDuration(400); $timesheet2->setRate(84); + $timesheet2->setActivity(new Activity()); $timesheet3 = new Timesheet(); + $timesheet3->setBegin(new \DateTime()); $timesheet3->setDuration(1800); $timesheet3->setRate(111.11); + $timesheet3->setActivity(new Activity()); $entries = [$timesheet, $timesheet2, $timesheet3]; @@ -50,6 +58,7 @@ class DefaultCalculatorTest extends AbstractCalculatorTest $model->setCustomer($customer); $model->setTemplate($template); $model->setEntries($entries); + $model->setQuery(new InvoiceQuery()); $sut = new DefaultCalculator(); $sut->setModel($model); @@ -60,6 +69,5 @@ class DefaultCalculatorTest extends AbstractCalculatorTest $this->assertEquals('EUR', $sut->getCurrency()); $this->assertEquals(488.38, $sut->getSubtotal()); $this->assertEquals(5800, $sut->getTimeWorked()); - $this->assertEquals($entries, $sut->getEntries()); } } diff --git a/tests/Invoice/Calculator/ProjectInvoiceCalculatorTest.php b/tests/Invoice/Calculator/ProjectInvoiceCalculatorTest.php index d9a0de94..efc40e48 100644 --- a/tests/Invoice/Calculator/ProjectInvoiceCalculatorTest.php +++ b/tests/Invoice/Calculator/ProjectInvoiceCalculatorTest.php @@ -16,7 +16,7 @@ use App\Entity\Project; use App\Entity\Timesheet; use App\Entity\User; use App\Invoice\Calculator\ProjectInvoiceCalculator; -use App\Model\InvoiceModel; +use App\Invoice\InvoiceModel; use App\Repository\Query\InvoiceQuery; use DateTime; diff --git a/tests/Invoice/Calculator/ShortInvoiceCalculatorTest.php b/tests/Invoice/Calculator/ShortInvoiceCalculatorTest.php index 2f9a4519..1f784282 100644 --- a/tests/Invoice/Calculator/ShortInvoiceCalculatorTest.php +++ b/tests/Invoice/Calculator/ShortInvoiceCalculatorTest.php @@ -16,7 +16,7 @@ use App\Entity\Project; use App\Entity\Timesheet; use App\Entity\User; use App\Invoice\Calculator\ShortInvoiceCalculator; -use App\Model\InvoiceModel; +use App\Invoice\InvoiceModel; use App\Repository\Query\InvoiceQuery; /** @@ -104,8 +104,86 @@ class ShortInvoiceCalculatorTest extends AbstractCalculatorTest $this->assertEquals('activity description', $result->getDescription()); $this->assertEquals(488.38, $result->getHourlyRate()); $this->assertEquals(488.38, $result->getRate()); - $this->assertEquals(488.38, $result->getFixedRate()); $this->assertEquals(5800, $result->getDuration()); + $this->assertNull($result->getFixedRate()); + } + + public function testWithMixedRateTypes() + { + $customer = new Customer(); + $template = new InvoiceTemplate(); + $template->setVat(19); + + $project = new Project(); + $project->setName('sdfsdf'); + + $activity = new Activity(); + $activity->setName('activity description'); + $activity->setProject($project); + + $timesheet = new Timesheet(); + $timesheet + ->setDuration(3600) + ->setRate(293.27) + ->setUser(new User()) + ->setActivity($activity) + ->setProject($project) + ->setBegin(new \DateTime()) + ->setEnd(new \DateTime()) + ; + + $timesheet2 = new Timesheet(); + $timesheet2 + ->setDuration(400) + ->setFixedRate(84) + ->setRate(84) + ->setUser(new User()) + ->setActivity($activity) + ->setProject($project) + ->setBegin(new \DateTime()) + ->setEnd(new \DateTime()) + ; + + $timesheet3 = new Timesheet(); + $timesheet3 + ->setDuration(1800) + ->setRate(111.11) + ->setUser(new User()) + ->setActivity($activity) + ->setProject($project) + ->setBegin(new \DateTime()) + ->setEnd(new \DateTime()) + ; + + $entries = [$timesheet, $timesheet2, $timesheet3]; + + $query = new InvoiceQuery(); + $query->setActivity($activity); + + $model = new InvoiceModel(); + $model->setCustomer($customer); + $model->setTemplate($template); + $model->setEntries($entries); + $model->setQuery($query); + + $sut = new ShortInvoiceCalculator(); + $sut->setModel($model); + + $this->assertEquals('short', $sut->getId()); + $this->assertEquals(581.17, $sut->getTotal()); + $this->assertEquals(19, $sut->getVat()); + $this->assertEquals('EUR', $sut->getCurrency()); + $this->assertEquals(488.38, $sut->getSubtotal()); + $this->assertEquals(5400, $sut->getTimeWorked()); + $this->assertEquals(1, count($sut->getEntries())); + + /** @var Timesheet $result */ + $result = $sut->getEntries()[0]; + $this->assertEquals('activity description', $result->getDescription()); + $this->assertEquals(488.38, $result->getHourlyRate()); + $this->assertEquals(488.38, $result->getRate()); + $this->assertEquals(5800, $result->getDuration()); + $this->assertEquals(488.38, $result->getFixedRate()); } public function testDescriptionByTimesheet() diff --git a/tests/Invoice/Calculator/UserInvoiceCalculatorTest.php b/tests/Invoice/Calculator/UserInvoiceCalculatorTest.php index 625f4d98..1b699140 100644 --- a/tests/Invoice/Calculator/UserInvoiceCalculatorTest.php +++ b/tests/Invoice/Calculator/UserInvoiceCalculatorTest.php @@ -16,7 +16,7 @@ use App\Entity\Project; use App\Entity\Timesheet; use App\Entity\User; use App\Invoice\Calculator\UserInvoiceCalculator; -use App\Model\InvoiceModel; +use App\Invoice\InvoiceModel; use App\Repository\Query\InvoiceQuery; /** diff --git a/tests/Invoice/InvoiceItemTest.php b/tests/Invoice/InvoiceItemTest.php new file mode 100644 index 00000000..7378b594 --- /dev/null +++ b/tests/Invoice/InvoiceItemTest.php @@ -0,0 +1,39 @@ +isFixedRate()); + self::assertNull($sut->getHourlyRate()); + self::assertNull($sut->getFixedRate()); + self::assertNull($sut->getEnd()); + self::assertEquals(0.00, $sut->getRate()); + self::assertNull($sut->getProject()); + self::assertIsArray($sut->getAdditionalFields()); + self::assertEmpty($sut->getAdditionalFields()); + self::assertEquals(0, $sut->getAmount()); + self::assertNull($sut->getBegin()); + self::assertNull($sut->getActivity()); + self::assertNull($sut->getUser()); + self::assertNull($sut->getDescription()); + self::assertEquals(0, $sut->getDuration()); + } +} diff --git a/tests/Invoice/InvoiceModelTest.php b/tests/Invoice/InvoiceModelTest.php new file mode 100644 index 00000000..04975e45 --- /dev/null +++ b/tests/Invoice/InvoiceModelTest.php @@ -0,0 +1,73 @@ +getQuery()); + self::assertNull($sut->getCustomer()); + self::assertNull($sut->getDueDate()); + self::assertNull($sut->getCalculator()); + self::assertNull($sut->getNumberGenerator()); + + self::assertEmpty($sut->getEntries()); + self::assertIsArray($sut->getEntries()); + + self::assertNull($sut->getTemplate()); + self::assertInstanceOf(\DateTime::class, $sut->getInvoiceDate()); + } + + public function testSetter() + { + $sut = new InvoiceModel(); + + $query = new InvoiceQuery(); + self::assertInstanceOf(InvoiceModel::class, $sut->setQuery($query)); + self::assertSame($query, $sut->getQuery()); + + $customer = new Customer(); + self::assertInstanceOf(InvoiceModel::class, $sut->setCustomer($customer)); + self::assertSame($customer, $sut->getCustomer()); + + $calculator = new DefaultCalculator(); + self::assertInstanceOf(InvoiceModel::class, $sut->setCalculator($calculator)); + self::assertSame($calculator, $sut->getCalculator()); + + $entries = [new Timesheet()]; + self::assertInstanceOf(InvoiceModel::class, $sut->setEntries($entries)); + self::assertSame($entries, $sut->getEntries()); + + $generator = new DateNumberGenerator(); + self::assertInstanceOf(InvoiceModel::class, $sut->setNumberGenerator($generator)); + self::assertSame($generator, $sut->getNumberGenerator()); + + $template = new InvoiceTemplate(); + self::assertNull($sut->getDueDate()); + self::assertInstanceOf(InvoiceModel::class, $sut->setTemplate($template)); + self::assertSame($template, $sut->getTemplate()); + self::assertInstanceOf(\DateTime::class, $sut->getDueDate()); + } +} diff --git a/tests/Invoice/NumberGenerator/DateNumberGeneratorTest.php b/tests/Invoice/NumberGenerator/DateNumberGeneratorTest.php index b8feb07c..c5dade09 100644 --- a/tests/Invoice/NumberGenerator/DateNumberGeneratorTest.php +++ b/tests/Invoice/NumberGenerator/DateNumberGeneratorTest.php @@ -9,8 +9,8 @@ namespace App\Tests\Invoice\NumberGenerator; +use App\Invoice\InvoiceModel; use App\Invoice\NumberGenerator\DateNumberGenerator; -use App\Model\InvoiceModel; use PHPUnit\Framework\TestCase; /** diff --git a/tests/Invoice/Renderer/CsvRendererTest.php b/tests/Invoice/Renderer/CsvRendererTest.php index 9fd484a2..185ca5c7 100644 --- a/tests/Invoice/Renderer/CsvRendererTest.php +++ b/tests/Invoice/Renderer/CsvRendererTest.php @@ -9,8 +9,8 @@ namespace App\Tests\Invoice\Renderer; +use App\Invoice\InvoiceModel; use App\Invoice\Renderer\CsvRenderer; -use App\Model\InvoiceModel; use PHPUnit\Framework\TestCase; use Symfony\Component\HttpFoundation\BinaryFileResponse; diff --git a/tests/Invoice/Renderer/DebugRenderer.php b/tests/Invoice/Renderer/DebugRenderer.php index e9c8db7d..9c61dd81 100644 --- a/tests/Invoice/Renderer/DebugRenderer.php +++ b/tests/Invoice/Renderer/DebugRenderer.php @@ -10,9 +10,9 @@ namespace App\Tests\Invoice\Renderer; use App\Entity\InvoiceDocument; +use App\Invoice\InvoiceModel; use App\Invoice\Renderer\RendererTrait; use App\Invoice\RendererInterface; -use App\Model\InvoiceModel; use Symfony\Component\HttpFoundation\Response; class DebugRenderer implements RendererInterface diff --git a/tests/Invoice/Renderer/DebugRendererTest.php b/tests/Invoice/Renderer/DebugRendererTest.php index 989d3905..e6b238a3 100644 --- a/tests/Invoice/Renderer/DebugRendererTest.php +++ b/tests/Invoice/Renderer/DebugRendererTest.php @@ -10,7 +10,7 @@ namespace App\Tests\Invoice\Renderer; use App\Entity\InvoiceDocument; -use App\Model\InvoiceModel; +use App\Invoice\InvoiceModel; use PHPUnit\Framework\TestCase; use Symfony\Component\HttpFoundation\Response; diff --git a/tests/Invoice/Renderer/OdsRendererTest.php b/tests/Invoice/Renderer/OdsRendererTest.php index b53039b9..bcbac3cb 100644 --- a/tests/Invoice/Renderer/OdsRendererTest.php +++ b/tests/Invoice/Renderer/OdsRendererTest.php @@ -9,8 +9,8 @@ namespace App\Tests\Invoice\Renderer; +use App\Invoice\InvoiceModel; use App\Invoice\Renderer\OdsRenderer; -use App\Model\InvoiceModel; use PHPUnit\Framework\TestCase; use Symfony\Component\HttpFoundation\BinaryFileResponse; diff --git a/tests/Invoice/Renderer/RendererTestTrait.php b/tests/Invoice/Renderer/RendererTestTrait.php index 00a98ed3..0be4cd2c 100644 --- a/tests/Invoice/Renderer/RendererTestTrait.php +++ b/tests/Invoice/Renderer/RendererTestTrait.php @@ -22,9 +22,9 @@ use App\Entity\Timesheet; use App\Entity\TimesheetMeta; use App\Entity\User; use App\Invoice\Calculator\DefaultCalculator; +use App\Invoice\InvoiceModel; use App\Invoice\NumberGenerator\DateNumberGenerator; use App\Invoice\Renderer\AbstractRenderer; -use App\Model\InvoiceModel; use App\Repository\Query\InvoiceQuery; use App\Twig\DateExtensions; use App\Twig\Extensions; @@ -82,10 +82,7 @@ trait RendererTestTrait return new $classname($translator, $dateExtension, $extensions); } - /** - * @return InvoiceModel - */ - protected function getInvoiceModel() + protected function getInvoiceModel(): InvoiceModel { $customer = new Customer(); $customer->setCurrency('EUR'); diff --git a/tests/Invoice/Renderer/TwigRendererTest.php b/tests/Invoice/Renderer/TwigRendererTest.php index 6611c33e..e6746efe 100644 --- a/tests/Invoice/Renderer/TwigRendererTest.php +++ b/tests/Invoice/Renderer/TwigRendererTest.php @@ -65,6 +65,6 @@ class TwigRendererTest extends KernelTestCase $this->assertContains('', $content); - $this->assertEquals(5, substr_count($content, 'activity description / project name')); + $this->assertEquals(5, substr_count($content, 'activity description')); } } diff --git a/tests/Invoice/Renderer/XlsxRendererTest.php b/tests/Invoice/Renderer/XlsxRendererTest.php index 6a99b6a1..1f0d528b 100644 --- a/tests/Invoice/Renderer/XlsxRendererTest.php +++ b/tests/Invoice/Renderer/XlsxRendererTest.php @@ -9,8 +9,8 @@ namespace App\Tests\Invoice\Renderer; +use App\Invoice\InvoiceModel; use App\Invoice\Renderer\XlsxRenderer; -use App\Model\InvoiceModel; use PHPUnit\Framework\TestCase; use Symfony\Component\HttpFoundation\BinaryFileResponse; diff --git a/tests/Model/InvoiceModelTest.php b/tests/Model/InvoiceModelTest.php index debce854..0d8260f8 100644 --- a/tests/Model/InvoiceModelTest.php +++ b/tests/Model/InvoiceModelTest.php @@ -13,13 +13,13 @@ use App\Entity\Customer; use App\Entity\InvoiceTemplate; use App\Entity\Timesheet; use App\Invoice\Calculator\DefaultCalculator; +use App\Invoice\InvoiceModel; use App\Invoice\NumberGenerator\DateNumberGenerator; -use App\Model\InvoiceModel; use App\Repository\Query\InvoiceQuery; use PHPUnit\Framework\TestCase; /** - * @covers \App\Model\InvoiceModel + * @covers \App\Invoice\InvoiceModel */ class InvoiceModelTest extends TestCase {