Next major version 2 with PHP 8.1, Symfony 6, Tabler UI, 2FA ... (#2902)
This commit is contained in:
@@ -9,43 +9,25 @@
|
||||
|
||||
namespace App\Invoice\Calculator;
|
||||
|
||||
use App\Invoice\CalculatorInterface;
|
||||
use App\Invoice\InvoiceItem;
|
||||
use App\Invoice\InvoiceModel;
|
||||
|
||||
abstract class AbstractCalculator
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $currency;
|
||||
|
||||
/**
|
||||
* @var InvoiceModel
|
||||
*/
|
||||
protected $model;
|
||||
protected InvoiceModel $model;
|
||||
|
||||
/**
|
||||
* @return InvoiceItem[]
|
||||
*/
|
||||
abstract public function getEntries();
|
||||
abstract public function getEntries(): array;
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
abstract public function getId(): string;
|
||||
|
||||
/**
|
||||
* @param InvoiceModel $model
|
||||
*/
|
||||
public function setModel(InvoiceModel $model)
|
||||
public function setModel(InvoiceModel $model): void
|
||||
{
|
||||
$this->model = $model;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return float
|
||||
*/
|
||||
public function getSubtotal(): float
|
||||
{
|
||||
$amount = 0.00;
|
||||
@@ -56,17 +38,11 @@ abstract class AbstractCalculator
|
||||
return round($amount, 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return float
|
||||
*/
|
||||
public function getVat(): ?float
|
||||
public function getVat(): float
|
||||
{
|
||||
return $this->model->getTemplate()->getVat();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return float
|
||||
*/
|
||||
public function getTax(): float
|
||||
{
|
||||
$vat = $this->getVat();
|
||||
@@ -79,28 +55,11 @@ abstract class AbstractCalculator
|
||||
return round($this->getSubtotal() * $percent, 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return float
|
||||
*/
|
||||
public function getTotal(): float
|
||||
{
|
||||
return $this->getSubtotal() + $this->getTax();
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated since 1.8 will be removed with 2.0
|
||||
* @return string
|
||||
*/
|
||||
public function getCurrency(): string
|
||||
{
|
||||
@trigger_error(
|
||||
sprintf('%s::getCurrency() is deprecated and will be removed with 2.0', CalculatorInterface::class),
|
||||
E_USER_DEPRECATED
|
||||
);
|
||||
|
||||
return $this->model->getCurrency();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the total amount of worked time in seconds.
|
||||
*
|
||||
|
||||
@@ -9,42 +9,23 @@
|
||||
|
||||
namespace App\Invoice\Calculator;
|
||||
|
||||
use App\Entity\ExportableItem;
|
||||
use App\Entity\Timesheet;
|
||||
use App\Invoice\InvoiceItem;
|
||||
use App\Invoice\InvoiceItemInterface;
|
||||
use App\Invoice\InvoiceItemWithAmountInterface;
|
||||
|
||||
abstract class AbstractMergedCalculator extends AbstractCalculator
|
||||
{
|
||||
public const TYPE_MIXED = 'mixed';
|
||||
public const CATEGORY_MIXED = 'mixed';
|
||||
|
||||
/**
|
||||
* @deprecated since 1.3 - will be removed with 2.0
|
||||
*/
|
||||
protected function mergeTimesheets(InvoiceItem $invoiceItem, Timesheet $entry)
|
||||
{
|
||||
@trigger_error('mergeTimesheets() is deprecated and will be removed with 2.0', E_USER_DEPRECATED);
|
||||
|
||||
$this->mergeInvoiceItems($invoiceItem, $entry);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param InvoiceItem $invoiceItem
|
||||
* @param InvoiceItemInterface $entry
|
||||
* @return void
|
||||
*/
|
||||
protected function mergeInvoiceItems(InvoiceItem $invoiceItem, InvoiceItemInterface $entry) /* : void */
|
||||
protected function mergeInvoiceItems(InvoiceItem $invoiceItem, ExportableItem $entry): void
|
||||
{
|
||||
$duration = $invoiceItem->getDuration();
|
||||
if (null !== $entry->getDuration()) {
|
||||
$duration += $entry->getDuration();
|
||||
}
|
||||
|
||||
$amount = 1;
|
||||
if ($entry instanceof InvoiceItemWithAmountInterface) {
|
||||
$amount = $entry->getAmount();
|
||||
}
|
||||
$amount = $entry->getAmount();
|
||||
|
||||
$type = $entry->getType();
|
||||
$category = $entry->getCategory();
|
||||
@@ -62,11 +43,7 @@ abstract class AbstractMergedCalculator extends AbstractCalculator
|
||||
$invoiceItem->setAmount($invoiceItem->getAmount() + $amount);
|
||||
$invoiceItem->setUser($entry->getUser());
|
||||
$invoiceItem->setRate($invoiceItem->getRate() + $entry->getRate());
|
||||
if (method_exists($entry, 'getInternalRate')) {
|
||||
$invoiceItem->setInternalRate($invoiceItem->getInternalRate() + $entry->getInternalRate());
|
||||
} else {
|
||||
$invoiceItem->setInternalRate($invoiceItem->getInternalRate() + $entry->getRate());
|
||||
}
|
||||
$invoiceItem->setInternalRate($invoiceItem->getInternalRate() + ($entry->getInternalRate() ?? 0.00));
|
||||
$invoiceItem->setDuration($duration);
|
||||
|
||||
if (null !== $entry->getFixedRate()) {
|
||||
|
||||
@@ -9,18 +9,18 @@
|
||||
|
||||
namespace App\Invoice\Calculator;
|
||||
|
||||
use App\Entity\ExportableItem;
|
||||
use App\Invoice\CalculatorInterface;
|
||||
use App\Invoice\InvoiceItem;
|
||||
use App\Invoice\InvoiceItemInterface;
|
||||
|
||||
/**
|
||||
* An abstract calculator that sums up the invoice item records.
|
||||
*/
|
||||
abstract class AbstractSumInvoiceCalculator extends AbstractMergedCalculator implements CalculatorInterface
|
||||
{
|
||||
abstract protected function calculateSumIdentifier(InvoiceItemInterface $invoiceItem): string;
|
||||
abstract protected function calculateSumIdentifier(ExportableItem $invoiceItem): string;
|
||||
|
||||
protected function calculateIdentifier(InvoiceItemInterface $entry): string
|
||||
protected function calculateIdentifier(ExportableItem $entry): string
|
||||
{
|
||||
$prefix = $this->calculateSumIdentifier($entry);
|
||||
|
||||
@@ -34,7 +34,7 @@ abstract class AbstractSumInvoiceCalculator extends AbstractMergedCalculator imp
|
||||
/**
|
||||
* @return InvoiceItem[]
|
||||
*/
|
||||
public function getEntries()
|
||||
public function getEntries(): array
|
||||
{
|
||||
$entries = $this->model->getEntries();
|
||||
if (empty($entries)) {
|
||||
@@ -60,14 +60,10 @@ abstract class AbstractSumInvoiceCalculator extends AbstractMergedCalculator imp
|
||||
|
||||
/**
|
||||
* @param InvoiceItem $invoiceItem
|
||||
* @param InvoiceItemInterface $entry
|
||||
* @param ExportableItem $entry
|
||||
* @return void
|
||||
*/
|
||||
protected function mergeSumInvoiceItem(InvoiceItem $invoiceItem, InvoiceItemInterface $entry) /* : void */
|
||||
protected function mergeSumInvoiceItem(InvoiceItem $invoiceItem, ExportableItem $entry): void
|
||||
{
|
||||
if (method_exists($this, 'mergeSumTimesheet')) {
|
||||
@trigger_error('mergeSumTimesheet() is deprecated and will be removed with 2.0 - use mergeSumInvoiceItem() instead', E_USER_DEPRECATED);
|
||||
$this->mergeSumTimesheet($invoiceItem, $entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,16 +9,16 @@
|
||||
|
||||
namespace App\Invoice\Calculator;
|
||||
|
||||
use App\Entity\ExportableItem;
|
||||
use App\Invoice\CalculatorInterface;
|
||||
use App\Invoice\InvoiceItem;
|
||||
use App\Invoice\InvoiceItemInterface;
|
||||
|
||||
/**
|
||||
* A calculator that sums up the invoice item records by activity.
|
||||
*/
|
||||
class ActivityInvoiceCalculator extends AbstractSumInvoiceCalculator implements CalculatorInterface
|
||||
final class ActivityInvoiceCalculator extends AbstractSumInvoiceCalculator implements CalculatorInterface
|
||||
{
|
||||
protected function calculateSumIdentifier(InvoiceItemInterface $invoiceItem): string
|
||||
protected function calculateSumIdentifier(ExportableItem $invoiceItem): string
|
||||
{
|
||||
if (null === $invoiceItem->getActivity()) {
|
||||
return '__NULL__';
|
||||
@@ -27,7 +27,7 @@ class ActivityInvoiceCalculator extends AbstractSumInvoiceCalculator implements
|
||||
return (string) $invoiceItem->getActivity()->getId();
|
||||
}
|
||||
|
||||
protected function mergeSumInvoiceItem(InvoiceItem $invoiceItem, InvoiceItemInterface $entry)
|
||||
protected function mergeSumInvoiceItem(InvoiceItem $invoiceItem, ExportableItem $entry): void
|
||||
{
|
||||
if (null === $entry->getActivity()) {
|
||||
return;
|
||||
@@ -40,9 +40,6 @@ class ActivityInvoiceCalculator extends AbstractSumInvoiceCalculator implements
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getId(): string
|
||||
{
|
||||
return 'activity';
|
||||
|
||||
@@ -9,15 +9,15 @@
|
||||
|
||||
namespace App\Invoice\Calculator;
|
||||
|
||||
use App\Entity\ExportableItem;
|
||||
use App\Invoice\CalculatorInterface;
|
||||
use App\Invoice\InvoiceItemInterface;
|
||||
|
||||
/**
|
||||
* A calculator that sums up the invoice item records for each day.
|
||||
*/
|
||||
class DateInvoiceCalculator extends AbstractSumInvoiceCalculator implements CalculatorInterface
|
||||
final class DateInvoiceCalculator extends AbstractSumInvoiceCalculator implements CalculatorInterface
|
||||
{
|
||||
protected function calculateSumIdentifier(InvoiceItemInterface $invoiceItem): string
|
||||
protected function calculateSumIdentifier(ExportableItem $invoiceItem): string
|
||||
{
|
||||
if (null === $invoiceItem->getBegin()) {
|
||||
throw new \Exception('Cannot handle invoice items without start date');
|
||||
@@ -26,9 +26,6 @@ class DateInvoiceCalculator extends AbstractSumInvoiceCalculator implements Calc
|
||||
return $invoiceItem->getBegin()->format('Y-m-d');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getId(): string
|
||||
{
|
||||
return 'date';
|
||||
|
||||
@@ -14,23 +14,26 @@ use App\Invoice\InvoiceItem;
|
||||
|
||||
/**
|
||||
* Class DefaultCalculator works on all given entries using:
|
||||
* - the customers currency
|
||||
* - the customer currency
|
||||
* - the invoice template vat rate
|
||||
* - the entries rate
|
||||
*/
|
||||
class DefaultCalculator extends AbstractMergedCalculator implements CalculatorInterface
|
||||
final class DefaultCalculator extends AbstractMergedCalculator implements CalculatorInterface
|
||||
{
|
||||
/**
|
||||
* @return InvoiceItem[]
|
||||
*/
|
||||
public function getEntries()
|
||||
public function getEntries(): array
|
||||
{
|
||||
$entries = [];
|
||||
|
||||
foreach ($this->model->getEntries() as $entry) {
|
||||
$item = new InvoiceItem();
|
||||
$this->mergeInvoiceItems($item, $entry);
|
||||
foreach ($entry->getVisibleMetaFields() as $field) {
|
||||
foreach ($entry->getMetaFields() as $field) {
|
||||
if ($field->getName() === null) {
|
||||
continue;
|
||||
}
|
||||
$item->addAdditionalField($field->getName(), $field->getValue());
|
||||
}
|
||||
$entries[] = $item;
|
||||
@@ -39,9 +42,6 @@ class DefaultCalculator extends AbstractMergedCalculator implements CalculatorIn
|
||||
return $entries;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getId(): string
|
||||
{
|
||||
return 'default';
|
||||
|
||||
@@ -9,15 +9,15 @@
|
||||
|
||||
namespace App\Invoice\Calculator;
|
||||
|
||||
use App\Entity\ExportableItem;
|
||||
use App\Invoice\CalculatorInterface;
|
||||
use App\Invoice\InvoiceItemInterface;
|
||||
|
||||
/**
|
||||
* A calculator that sums up the invoice item records by price.
|
||||
*/
|
||||
class PriceInvoiceCalculator extends AbstractSumInvoiceCalculator implements CalculatorInterface
|
||||
final class PriceInvoiceCalculator extends AbstractSumInvoiceCalculator implements CalculatorInterface
|
||||
{
|
||||
protected function calculateSumIdentifier(InvoiceItemInterface $invoiceItem): string
|
||||
protected function calculateSumIdentifier(ExportableItem $invoiceItem): string
|
||||
{
|
||||
if (null !== $invoiceItem->getFixedRate()) {
|
||||
return 'fixed_' . $invoiceItem->getFixedRate();
|
||||
@@ -26,9 +26,6 @@ class PriceInvoiceCalculator extends AbstractSumInvoiceCalculator implements Cal
|
||||
return 'hourly_' . $invoiceItem->getHourlyRate();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getId(): string
|
||||
{
|
||||
return 'price';
|
||||
|
||||
@@ -9,16 +9,16 @@
|
||||
|
||||
namespace App\Invoice\Calculator;
|
||||
|
||||
use App\Entity\ExportableItem;
|
||||
use App\Invoice\CalculatorInterface;
|
||||
use App\Invoice\InvoiceItem;
|
||||
use App\Invoice\InvoiceItemInterface;
|
||||
|
||||
/**
|
||||
* A calculator that sums up the invoice item records by project.
|
||||
*/
|
||||
class ProjectInvoiceCalculator extends AbstractSumInvoiceCalculator implements CalculatorInterface
|
||||
final class ProjectInvoiceCalculator extends AbstractSumInvoiceCalculator implements CalculatorInterface
|
||||
{
|
||||
protected function calculateSumIdentifier(InvoiceItemInterface $invoiceItem): string
|
||||
protected function calculateSumIdentifier(ExportableItem $invoiceItem): string
|
||||
{
|
||||
if (null === $invoiceItem->getProject()->getId()) {
|
||||
throw new \Exception('Cannot handle un-persisted projects');
|
||||
@@ -27,7 +27,7 @@ class ProjectInvoiceCalculator extends AbstractSumInvoiceCalculator implements C
|
||||
return (string) $invoiceItem->getProject()->getId();
|
||||
}
|
||||
|
||||
protected function mergeSumInvoiceItem(InvoiceItem $invoiceItem, InvoiceItemInterface $entry)
|
||||
protected function mergeSumInvoiceItem(InvoiceItem $invoiceItem, ExportableItem $entry): void
|
||||
{
|
||||
if ($entry->getProject()->getInvoiceText() !== null) {
|
||||
$invoiceItem->setDescription($entry->getProject()->getInvoiceText());
|
||||
@@ -36,9 +36,6 @@ class ProjectInvoiceCalculator extends AbstractSumInvoiceCalculator implements C
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getId(): string
|
||||
{
|
||||
return 'project';
|
||||
|
||||
@@ -16,12 +16,12 @@ use App\Invoice\InvoiceItem;
|
||||
* A calculator that sums up all invoice item records from the model and returns only one
|
||||
* entry for a compact invoice version.
|
||||
*/
|
||||
class ShortInvoiceCalculator extends AbstractMergedCalculator implements CalculatorInterface
|
||||
final class ShortInvoiceCalculator extends AbstractMergedCalculator implements CalculatorInterface
|
||||
{
|
||||
/**
|
||||
* @return InvoiceItem[]
|
||||
*/
|
||||
public function getEntries()
|
||||
public function getEntries(): array
|
||||
{
|
||||
$entries = $this->model->getEntries();
|
||||
if (empty($entries)) {
|
||||
@@ -51,9 +51,6 @@ class ShortInvoiceCalculator extends AbstractMergedCalculator implements Calcula
|
||||
return [$invoiceItem];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getId(): string
|
||||
{
|
||||
return 'short';
|
||||
|
||||
@@ -9,15 +9,15 @@
|
||||
|
||||
namespace App\Invoice\Calculator;
|
||||
|
||||
use App\Entity\ExportableItem;
|
||||
use App\Invoice\CalculatorInterface;
|
||||
use App\Invoice\InvoiceItemInterface;
|
||||
|
||||
/**
|
||||
* A calculator that sums up the invoice item records by user.
|
||||
*/
|
||||
class UserInvoiceCalculator extends AbstractSumInvoiceCalculator implements CalculatorInterface
|
||||
final class UserInvoiceCalculator extends AbstractSumInvoiceCalculator implements CalculatorInterface
|
||||
{
|
||||
protected function calculateSumIdentifier(InvoiceItemInterface $invoiceItem): string
|
||||
protected function calculateSumIdentifier(ExportableItem $invoiceItem): string
|
||||
{
|
||||
if (null === $invoiceItem->getUser()->getId()) {
|
||||
throw new \Exception('Cannot handle un-persisted user');
|
||||
@@ -26,9 +26,6 @@ class UserInvoiceCalculator extends AbstractSumInvoiceCalculator implements Calc
|
||||
return (string) $invoiceItem->getUser()->getId();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getId(): string
|
||||
{
|
||||
return 'user';
|
||||
|
||||
@@ -9,22 +9,19 @@
|
||||
|
||||
namespace App\Invoice\Calculator;
|
||||
|
||||
use App\Entity\ExportableItem;
|
||||
use App\Invoice\CalculatorInterface;
|
||||
use App\Invoice\InvoiceItemInterface;
|
||||
|
||||
/**
|
||||
* A calculator that sums up the invoice item records per week.
|
||||
*/
|
||||
class WeeklyInvoiceCalculator extends AbstractSumInvoiceCalculator implements CalculatorInterface
|
||||
final class WeeklyInvoiceCalculator extends AbstractSumInvoiceCalculator implements CalculatorInterface
|
||||
{
|
||||
protected function calculateSumIdentifier(InvoiceItemInterface $invoiceItem): string
|
||||
protected function calculateSumIdentifier(ExportableItem $invoiceItem): string
|
||||
{
|
||||
return $invoiceItem->getBegin()->format('W');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getId(): string
|
||||
{
|
||||
return 'weekly';
|
||||
|
||||
@@ -19,14 +19,14 @@ interface CalculatorInterface
|
||||
*
|
||||
* @return InvoiceItem[]
|
||||
*/
|
||||
public function getEntries();
|
||||
public function getEntries(): array;
|
||||
|
||||
/**
|
||||
* Set the invoice model and can be used to fetch the customer.
|
||||
*
|
||||
* @param InvoiceModel $model
|
||||
*/
|
||||
public function setModel(InvoiceModel $model);
|
||||
public function setModel(InvoiceModel $model): void;
|
||||
|
||||
/**
|
||||
* Returns the subtotal before taxes.
|
||||
@@ -49,20 +49,12 @@ interface CalculatorInterface
|
||||
*/
|
||||
public function getTotal(): float;
|
||||
|
||||
/**
|
||||
* Returns the currency for the invoices amounts.
|
||||
*
|
||||
* @deprecated since 1.8 will be removed with 2.0
|
||||
* @return string
|
||||
*/
|
||||
public function getCurrency(): string;
|
||||
|
||||
/**
|
||||
* Returns the percentage for the value-added tax (VAT) calculation.
|
||||
*
|
||||
* @return float
|
||||
*/
|
||||
public function getVat(): ?float;
|
||||
public function getVat(): float;
|
||||
|
||||
/**
|
||||
* Returns the total amount of worked time in seconds.
|
||||
|
||||
@@ -9,28 +9,24 @@
|
||||
|
||||
namespace App\Invoice;
|
||||
|
||||
use App\Configuration\LanguageFormattings;
|
||||
use App\Configuration\LocaleService;
|
||||
use App\Utils\LocaleFormatter;
|
||||
|
||||
final class DefaultInvoiceFormatter implements InvoiceFormatter
|
||||
{
|
||||
private $locale;
|
||||
private $formats;
|
||||
/**
|
||||
* @var LocaleFormatter|null
|
||||
*/
|
||||
private $formatter;
|
||||
|
||||
public function __construct(LanguageFormattings $formats, string $locale)
|
||||
public function __construct(private LocaleService $localeService, private string $locale)
|
||||
{
|
||||
$this->formats = $formats;
|
||||
$this->locale = $locale;
|
||||
}
|
||||
|
||||
private function getFormatter(): LocaleFormatter
|
||||
{
|
||||
if ($this->formatter === null) {
|
||||
$this->formatter = new LocaleFormatter($this->formats, $this->locale);
|
||||
$this->formatter = new LocaleFormatter($this->localeService, $this->locale);
|
||||
}
|
||||
|
||||
return $this->formatter;
|
||||
@@ -38,12 +34,12 @@ final class DefaultInvoiceFormatter implements InvoiceFormatter
|
||||
|
||||
public function getFormattedDateTime(\DateTime $date): string
|
||||
{
|
||||
return $this->getFormatter()->dateShort($date);
|
||||
return (string) $this->getFormatter()->dateShort($date);
|
||||
}
|
||||
|
||||
public function getFormattedTime(\DateTime $date): string
|
||||
{
|
||||
return $this->getFormatter()->time($date);
|
||||
return (string) $this->getFormatter()->time($date);
|
||||
}
|
||||
|
||||
public function getFormattedMonthName(\DateTime $date): string
|
||||
|
||||
@@ -21,12 +21,7 @@ trait BudgetHydratorTrait
|
||||
|
||||
$budgetOpen = $statistic->getBudgetOpenRelative();
|
||||
$budgetTimeOpen = $statistic->getTimeBudgetOpenRelative();
|
||||
|
||||
if ($model->getTemplate()->isDecimalDuration()) {
|
||||
$budgetOpenDuration = $formatter->getFormattedDecimalDuration($budgetTimeOpen);
|
||||
} else {
|
||||
$budgetOpenDuration = $formatter->getFormattedDuration($budgetTimeOpen);
|
||||
}
|
||||
$budgetOpenDuration = $formatter->getFormattedDecimalDuration($budgetTimeOpen);
|
||||
|
||||
return [
|
||||
$prefix . 'budget_open' => $formatter->getFormattedMoney($budgetOpen, $currency),
|
||||
|
||||
@@ -13,12 +13,9 @@ use App\Invoice\InvoiceItem;
|
||||
use App\Invoice\InvoiceItemHydrator;
|
||||
use App\Invoice\InvoiceModel;
|
||||
|
||||
class InvoiceItemDefaultHydrator implements InvoiceItemHydrator
|
||||
final class InvoiceItemDefaultHydrator implements InvoiceItemHydrator
|
||||
{
|
||||
/**
|
||||
* @var InvoiceModel
|
||||
*/
|
||||
private $model;
|
||||
private InvoiceModel $model;
|
||||
|
||||
public function setInvoiceModel(InvoiceModel $model)
|
||||
{
|
||||
@@ -32,11 +29,7 @@ class InvoiceItemDefaultHydrator implements InvoiceItemHydrator
|
||||
$rate = $item->getRate();
|
||||
$internalRate = $item->getInternalRate();
|
||||
$appliedRate = $item->getHourlyRate();
|
||||
if ($this->model->getTemplate()->isDecimalDuration()) {
|
||||
$amount = $formatter->getFormattedDecimalDuration($item->getDuration());
|
||||
} else {
|
||||
$amount = $formatter->getFormattedDuration($item->getDuration());
|
||||
}
|
||||
$amount = $formatter->getFormattedDecimalDuration($item->getDuration());
|
||||
$description = $item->getDescription();
|
||||
|
||||
if ($item->isFixedRate()) {
|
||||
@@ -63,11 +56,11 @@ class InvoiceItemDefaultHydrator implements InvoiceItemHydrator
|
||||
|
||||
$values = [
|
||||
'entry.row' => '',
|
||||
'entry.description' => $description,
|
||||
'entry.description' => $description ?? '',
|
||||
'entry.amount' => $amount,
|
||||
'entry.type' => $item->getType(),
|
||||
'entry.tags' => implode(', ', $item->getTags()),
|
||||
'entry.category' => $item->getCategory(),
|
||||
'entry.category' => $item->getCategory() ?? '',
|
||||
'entry.rate' => $formatter->getFormattedMoney($appliedRate, $currency),
|
||||
'entry.rate_nc' => $formatter->getFormattedMoney($appliedRate, $currency, false),
|
||||
'entry.rate_plain' => $appliedRate,
|
||||
@@ -91,9 +84,9 @@ class InvoiceItemDefaultHydrator implements InvoiceItemHydrator
|
||||
'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(),
|
||||
'entry.user_alias' => $user->getAlias(),
|
||||
'entry.user_name' => $user->getUserIdentifier(),
|
||||
'entry.user_title' => $user->getTitle() ?? '',
|
||||
'entry.user_alias' => $user->getAlias() ?? '',
|
||||
];
|
||||
|
||||
if (null !== $activity) {
|
||||
|
||||
@@ -14,31 +14,39 @@ use App\Entity\Activity;
|
||||
use App\Invoice\InvoiceModel;
|
||||
use App\Invoice\InvoiceModelHydrator;
|
||||
|
||||
class InvoiceModelActivityHydrator implements InvoiceModelHydrator
|
||||
final class InvoiceModelActivityHydrator implements InvoiceModelHydrator
|
||||
{
|
||||
use BudgetHydratorTrait;
|
||||
|
||||
private $activityStatistic;
|
||||
|
||||
public function __construct(ActivityStatisticService $activityStatistic)
|
||||
public function __construct(private ActivityStatisticService $activityStatistic)
|
||||
{
|
||||
$this->activityStatistic = $activityStatistic;
|
||||
}
|
||||
|
||||
public function hydrate(InvoiceModel $model): array
|
||||
{
|
||||
if (!$model->getQuery()->hasActivities()) {
|
||||
$activities = [];
|
||||
|
||||
foreach ($model->getEntries() as $entry) {
|
||||
if ($entry->getActivity() === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$key = 'A_' . $entry->getActivity()->getId();
|
||||
if (!\array_key_exists($key, $activities)) {
|
||||
$activities[$key] = $entry->getActivity();
|
||||
}
|
||||
}
|
||||
|
||||
if (\count($activities) === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$activities = array_values($activities);
|
||||
|
||||
$values = [];
|
||||
$i = 0;
|
||||
|
||||
if (\count($model->getQuery()->getActivities()) === 1) {
|
||||
$values['activity'] = $model->getQuery()->getActivities()[0]->getName();
|
||||
}
|
||||
|
||||
foreach ($model->getQuery()->getActivities() as $activity) {
|
||||
foreach ($activities as $activity) {
|
||||
$prefix = '';
|
||||
if ($i > 0) {
|
||||
$prefix = $i . '.';
|
||||
@@ -56,15 +64,17 @@ class InvoiceModelActivityHydrator implements InvoiceModelHydrator
|
||||
|
||||
$values = [
|
||||
$prefix . 'id' => $activity->getId(),
|
||||
$prefix . 'name' => $activity->getName(),
|
||||
$prefix . 'comment' => $activity->getComment(),
|
||||
$prefix . 'name' => $activity->getName() ?? '',
|
||||
$prefix . 'comment' => $activity->getComment() ?? '',
|
||||
];
|
||||
|
||||
$statistic = $this->activityStatistic->getBudgetStatisticModel($activity, $model->getQuery()->getEnd());
|
||||
if ($model->getQuery()?->getEnd() !== null) {
|
||||
$statistic = $this->activityStatistic->getBudgetStatisticModel($activity, $model->getQuery()->getEnd());
|
||||
|
||||
$values = array_merge($values, $this->getBudgetValues($prefix, $statistic, $model));
|
||||
$values = array_merge($values, $this->getBudgetValues($prefix, $statistic, $model));
|
||||
}
|
||||
|
||||
foreach ($activity->getVisibleMetaFields() as $metaField) {
|
||||
foreach ($activity->getMetaFields() as $metaField) {
|
||||
$values = array_merge($values, [
|
||||
$prefix . 'meta.' . $metaField->getName() => $metaField->getValue(),
|
||||
]);
|
||||
|
||||
@@ -13,15 +13,12 @@ use App\Customer\CustomerStatisticService;
|
||||
use App\Invoice\InvoiceModel;
|
||||
use App\Invoice\InvoiceModelHydrator;
|
||||
|
||||
class InvoiceModelCustomerHydrator implements InvoiceModelHydrator
|
||||
final class InvoiceModelCustomerHydrator implements InvoiceModelHydrator
|
||||
{
|
||||
use BudgetHydratorTrait;
|
||||
|
||||
private $customerStatistic;
|
||||
|
||||
public function __construct(CustomerStatisticService $customerStatistic)
|
||||
public function __construct(private CustomerStatisticService $customerStatisticService)
|
||||
{
|
||||
$this->customerStatistic = $customerStatistic;
|
||||
}
|
||||
|
||||
public function hydrate(InvoiceModel $model): array
|
||||
@@ -34,22 +31,23 @@ class InvoiceModelCustomerHydrator implements InvoiceModelHydrator
|
||||
|
||||
$values = [
|
||||
'customer.id' => $customer->getId(),
|
||||
'customer.address' => $customer->getAddress(),
|
||||
'customer.name' => $customer->getName(),
|
||||
'customer.contact' => $customer->getContact(),
|
||||
'customer.company' => $customer->getCompany(),
|
||||
'customer.vat' => $customer->getVatId(),
|
||||
'customer.number' => $customer->getNumber(),
|
||||
'customer.address' => $customer->getAddress() ?? '',
|
||||
'customer.name' => $customer->getName() ?? '',
|
||||
'customer.contact' => $customer->getContact() ?? '',
|
||||
'customer.company' => $customer->getCompany() ?? '',
|
||||
'customer.vat' => $customer->getVatId() ?? '',
|
||||
'customer.number' => $customer->getNumber() ?? '',
|
||||
'customer.country' => $customer->getCountry(),
|
||||
'customer.homepage' => $customer->getHomepage(),
|
||||
'customer.comment' => $customer->getComment(),
|
||||
'customer.email' => $customer->getEmail(),
|
||||
'customer.fax' => $customer->getFax(),
|
||||
'customer.phone' => $customer->getPhone(),
|
||||
'customer.mobile' => $customer->getMobile(),
|
||||
'customer.homepage' => $customer->getHomepage() ?? '',
|
||||
'customer.comment' => $customer->getComment() ?? '',
|
||||
'customer.email' => $customer->getEmail() ?? '',
|
||||
'customer.fax' => $customer->getFax() ?? '',
|
||||
'customer.phone' => $customer->getPhone() ?? '',
|
||||
'customer.mobile' => $customer->getMobile() ?? '',
|
||||
'customer.invoice_text' => $customer->getInvoiceText() ?? '',
|
||||
];
|
||||
|
||||
$statistic = $this->customerStatistic->getBudgetStatisticModel($customer, $model->getQuery()->getEnd());
|
||||
$statistic = $this->customerStatisticService->getBudgetStatisticModel($customer, $model->getQuery()->getEnd());
|
||||
|
||||
$values = array_merge($values, $this->getBudgetValues('customer.', $statistic, $model));
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ namespace App\Invoice\Hydrator;
|
||||
use App\Invoice\InvoiceModel;
|
||||
use App\Invoice\InvoiceModelHydrator;
|
||||
|
||||
class InvoiceModelDefaultHydrator implements InvoiceModelHydrator
|
||||
final class InvoiceModelDefaultHydrator implements InvoiceModelHydrator
|
||||
{
|
||||
public function hydrate(InvoiceModel $model): array
|
||||
{
|
||||
@@ -45,21 +45,17 @@ class InvoiceModelDefaultHydrator implements InvoiceModelHydrator
|
||||
'invoice.subtotal_nc' => $formatter->getFormattedMoney($subtotal, $currency, false),
|
||||
'invoice.subtotal_plain' => $subtotal,
|
||||
|
||||
'template.name' => $model->getTemplate()->getName(),
|
||||
'template.company' => $model->getTemplate()->getCompany(),
|
||||
'template.address' => $model->getTemplate()->getAddress(),
|
||||
'template.title' => $model->getTemplate()->getTitle(),
|
||||
'template.payment_terms' => $model->getTemplate()->getPaymentTerms(),
|
||||
'template.name' => $model->getTemplate()->getName() ?? '',
|
||||
'template.company' => $model->getTemplate()->getCompany() ?? '',
|
||||
'template.address' => $model->getTemplate()->getAddress() ?? '',
|
||||
'template.title' => $model->getTemplate()->getTitle() ?? '',
|
||||
'template.payment_terms' => $model->getTemplate()->getPaymentTerms() ?? '',
|
||||
'template.due_days' => $model->getTemplate()->getDueDays(),
|
||||
'template.vat_id' => $model->getTemplate()->getVatId(),
|
||||
'template.contact' => $model->getTemplate()->getContact(),
|
||||
'template.payment_details' => $model->getTemplate()->getPaymentDetails(),
|
||||
'template.vat_id' => $model->getTemplate()->getVatId() ?? '',
|
||||
'template.contact' => $model->getTemplate()->getContact() ?? '',
|
||||
'template.payment_details' => $model->getTemplate()->getPaymentDetails() ?? '',
|
||||
|
||||
'query.begin' => '',
|
||||
'query.day' => '', // @deprecated
|
||||
'query.month' => '', // @deprecated
|
||||
'query.month_number' => '', // @deprecated
|
||||
'query.year' => '', // @deprecated
|
||||
'query.begin_day' => '',
|
||||
'query.begin_month' => '',
|
||||
'query.begin_month_number' => '',
|
||||
@@ -73,11 +69,11 @@ class InvoiceModelDefaultHydrator implements InvoiceModelHydrator
|
||||
|
||||
if ($begin !== null) {
|
||||
$values = array_merge($values, [
|
||||
'query.day' => $begin->format('d'), // @deprecated - but impossible to delete
|
||||
'query.month' => $formatter->getFormattedMonthName($begin), // @deprecated - but impossible to delete
|
||||
'query.month_number' => $begin->format('m'), // @deprecated - but impossible to delete
|
||||
'query.year' => $begin->format('Y'), // @deprecated - but impossible to delete
|
||||
'query.begin' => $formatter->getFormattedDateTime($begin),
|
||||
'query.day' => $begin->format('d'), // @deprecated
|
||||
'query.month' => $formatter->getFormattedMonthName($begin), // @deprecated
|
||||
'query.month_number' => $begin->format('m'), // @deprecated
|
||||
'query.year' => $begin->format('Y'), // @deprecated
|
||||
'query.begin_day' => $begin->format('d'),
|
||||
'query.begin_month' => $formatter->getFormattedMonthName($begin),
|
||||
'query.begin_month_number' => $begin->format('m'),
|
||||
|
||||
@@ -14,31 +14,39 @@ use App\Invoice\InvoiceModel;
|
||||
use App\Invoice\InvoiceModelHydrator;
|
||||
use App\Project\ProjectStatisticService;
|
||||
|
||||
class InvoiceModelProjectHydrator implements InvoiceModelHydrator
|
||||
final class InvoiceModelProjectHydrator implements InvoiceModelHydrator
|
||||
{
|
||||
use BudgetHydratorTrait;
|
||||
|
||||
private $projectStatistic;
|
||||
|
||||
public function __construct(ProjectStatisticService $projectStatistic)
|
||||
public function __construct(private ProjectStatisticService $projectStatistic)
|
||||
{
|
||||
$this->projectStatistic = $projectStatistic;
|
||||
}
|
||||
|
||||
public function hydrate(InvoiceModel $model): array
|
||||
{
|
||||
if (!$model->getQuery()->hasProjects()) {
|
||||
$projects = [];
|
||||
|
||||
foreach ($model->getEntries() as $entry) {
|
||||
if ($entry->getProject() === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$key = 'P_' . $entry->getProject()->getId();
|
||||
if (!\array_key_exists($key, $projects)) {
|
||||
$projects[$key] = $entry->getProject();
|
||||
}
|
||||
}
|
||||
|
||||
if (\count($projects) === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$projects = array_values($projects);
|
||||
|
||||
$values = [];
|
||||
$i = 0;
|
||||
|
||||
if (\count($model->getQuery()->getProjects()) === 1) {
|
||||
$values['project'] = $model->getQuery()->getProjects()[0]->getName();
|
||||
}
|
||||
|
||||
foreach ($model->getQuery()->getProjects() as $project) {
|
||||
foreach ($projects as $project) {
|
||||
$prefix = '';
|
||||
if ($i > 0) {
|
||||
$prefix = $i . '.';
|
||||
@@ -59,8 +67,8 @@ class InvoiceModelProjectHydrator implements InvoiceModelHydrator
|
||||
|
||||
$values = [
|
||||
$prefix . 'id' => $project->getId(),
|
||||
$prefix . 'name' => $project->getName(),
|
||||
$prefix . 'comment' => $project->getComment(),
|
||||
$prefix . 'name' => $project->getName() ?? '',
|
||||
$prefix . 'comment' => $project->getComment() ?? '',
|
||||
$prefix . 'order_number' => $project->getOrderNumber(),
|
||||
$prefix . 'start_date' => null !== $project->getStart() ? $formatter->getFormattedDateTime($project->getStart()) : '',
|
||||
$prefix . 'end_date' => null !== $project->getEnd() ? $formatter->getFormattedDateTime($project->getEnd()) : '',
|
||||
@@ -73,11 +81,13 @@ class InvoiceModelProjectHydrator implements InvoiceModelHydrator
|
||||
$prefix . 'budget_time_minutes' => (int) ($project->getTimeBudget() / 60),
|
||||
];
|
||||
|
||||
$statistic = $this->projectStatistic->getBudgetStatisticModel($project, $model->getQuery()->getEnd());
|
||||
if ($model->getQuery()?->getEnd() !== null) {
|
||||
$statistic = $this->projectStatistic->getBudgetStatisticModel($project, $model->getQuery()->getEnd());
|
||||
|
||||
$values = array_merge($values, $this->getBudgetValues($prefix, $statistic, $model));
|
||||
$values = array_merge($values, $this->getBudgetValues($prefix, $statistic, $model));
|
||||
}
|
||||
|
||||
foreach ($project->getVisibleMetaFields() as $metaField) {
|
||||
foreach ($project->getMetaFields() as $metaField) {
|
||||
$values = array_merge($values, [
|
||||
$prefix . 'meta.' . $metaField->getName() => $metaField->getValue(),
|
||||
]);
|
||||
|
||||
@@ -13,7 +13,7 @@ use App\Entity\UserPreference;
|
||||
use App\Invoice\InvoiceModel;
|
||||
use App\Invoice\InvoiceModelHydrator;
|
||||
|
||||
class InvoiceModelUserHydrator implements InvoiceModelHydrator
|
||||
final class InvoiceModelUserHydrator implements InvoiceModelHydrator
|
||||
{
|
||||
public function hydrate(InvoiceModel $model): array
|
||||
{
|
||||
@@ -24,10 +24,10 @@ class InvoiceModelUserHydrator implements InvoiceModelHydrator
|
||||
}
|
||||
|
||||
$values = [
|
||||
'user.name' => $user->getUsername(),
|
||||
'user.name' => $user->getUserIdentifier(),
|
||||
'user.email' => $user->getEmail(),
|
||||
'user.title' => $user->getTitle(),
|
||||
'user.alias' => $user->getAlias(),
|
||||
'user.title' => $user->getTitle() ?? '',
|
||||
'user.alias' => $user->getAlias() ?? '',
|
||||
];
|
||||
|
||||
/** @var UserPreference $metaField */
|
||||
|
||||
@@ -9,15 +9,11 @@
|
||||
|
||||
namespace App\Invoice;
|
||||
|
||||
use App\Entity\Project;
|
||||
use App\Utils\FileHelper;
|
||||
|
||||
final class InvoiceFilename
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $filename;
|
||||
private string $filename;
|
||||
|
||||
public function __construct(InvoiceModel $model)
|
||||
{
|
||||
@@ -37,10 +33,7 @@ final class InvoiceFilename
|
||||
if (null !== $model->getQuery()) {
|
||||
$projects = $model->getQuery()->getProjects();
|
||||
if (\count($projects) === 1) {
|
||||
$pName = $projects[0];
|
||||
if ($pName instanceof Project) {
|
||||
$filename .= '-' . $this->convert($pName->getName());
|
||||
}
|
||||
$filename .= '-' . $this->convert($projects[0]->getName());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,12 +45,12 @@ final class InvoiceFilename
|
||||
return FileHelper::convertToAsciiFilename($filename);
|
||||
}
|
||||
|
||||
public function getFilename()
|
||||
public function getFilename(): string
|
||||
{
|
||||
return $this->filename;
|
||||
}
|
||||
|
||||
public function __toString()
|
||||
public function __toString(): string
|
||||
{
|
||||
return $this->getFilename();
|
||||
}
|
||||
|
||||
@@ -12,87 +12,47 @@ namespace App\Invoice;
|
||||
use App\Entity\Activity;
|
||||
use App\Entity\Project;
|
||||
use App\Entity\User;
|
||||
use DateTime;
|
||||
|
||||
final class InvoiceItem
|
||||
{
|
||||
/**
|
||||
* @var float
|
||||
*/
|
||||
private $fixedRate;
|
||||
/**
|
||||
* @var float
|
||||
*/
|
||||
private $hourlyRate;
|
||||
/**
|
||||
* @var float
|
||||
*/
|
||||
private $rate = 0.00;
|
||||
/**
|
||||
* @var float
|
||||
*/
|
||||
private $rateInternal = 0.00;
|
||||
/**
|
||||
* @var float
|
||||
*/
|
||||
private $amount = 0;
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $description;
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $duration = 0;
|
||||
/**
|
||||
* @var \DateTime
|
||||
*/
|
||||
private $begin;
|
||||
/**
|
||||
* @var \DateTime
|
||||
*/
|
||||
private $end;
|
||||
/**
|
||||
* @var User
|
||||
*/
|
||||
private $user;
|
||||
/**
|
||||
* @var Activity
|
||||
*/
|
||||
private $activity;
|
||||
/**
|
||||
* @var Project
|
||||
*/
|
||||
private $project;
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $additionalFields = [];
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $type;
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $category;
|
||||
private ?float $fixedRate = null;
|
||||
private ?float $hourlyRate = null;
|
||||
private float $rate = 0.00;
|
||||
private float $rateInternal = 0.00;
|
||||
private float $amount = 0.00;
|
||||
private ?string $description = null;
|
||||
private int $duration = 0;
|
||||
private ?DateTime $begin = null;
|
||||
private ?DateTime $end = null;
|
||||
private ?User $user = null;
|
||||
private ?Activity $activity = null;
|
||||
private ?Project $project = null;
|
||||
/** @var array<string, mixed> */
|
||||
private array $additionalFields = [];
|
||||
private ?string $type = null;
|
||||
private ?string $category = null;
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
private $tags = [];
|
||||
private array $tags = [];
|
||||
|
||||
public function addAdditionalField(string $name, ?string $value): InvoiceItem
|
||||
public function addAdditionalField(string $name, mixed $value): InvoiceItem
|
||||
{
|
||||
$this->additionalFields[$name] = $value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function getAdditionalFields(): array
|
||||
{
|
||||
return $this->additionalFields;
|
||||
}
|
||||
|
||||
public function getAdditionalField(string $name, $default = null)
|
||||
public function getAdditionalField(string $name, mixed $default = null): mixed
|
||||
{
|
||||
if (\array_key_exists($name, $this->additionalFields)) {
|
||||
return $this->additionalFields[$name];
|
||||
@@ -101,15 +61,6 @@ final class InvoiceItem
|
||||
return $default;
|
||||
}
|
||||
|
||||
public function getMetaFieldValue(string $field)
|
||||
{
|
||||
if (\array_key_exists($field, $this->additionalFields)) {
|
||||
return $this->additionalFields[$field];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getActivity(): ?Activity
|
||||
{
|
||||
return $this->activity;
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
<?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;
|
||||
|
||||
use App\Entity\Activity;
|
||||
use App\Entity\MetaTableTypeInterface;
|
||||
use App\Entity\Project;
|
||||
use App\Entity\User;
|
||||
|
||||
/**
|
||||
* @method float|null getInternalRate()
|
||||
*/
|
||||
interface InvoiceItemInterface
|
||||
{
|
||||
public function getActivity(): ?Activity;
|
||||
|
||||
public function getProject(): ?Project;
|
||||
|
||||
public function getFixedRate(): ?float;
|
||||
|
||||
public function getHourlyRate(): ?float;
|
||||
|
||||
public function getRate(): float;
|
||||
|
||||
// will be activated with 2.0
|
||||
// public function getInternalRate(): ?float;
|
||||
|
||||
public function getUser(): ?User;
|
||||
|
||||
public function getBegin(): ?\DateTime;
|
||||
|
||||
public function getEnd(): ?\DateTime;
|
||||
|
||||
public function getDuration(): ?int;
|
||||
|
||||
public function getDescription(): ?string;
|
||||
|
||||
/**
|
||||
* @return MetaTableTypeInterface[]
|
||||
*/
|
||||
public function getVisibleMetaFields(): array;
|
||||
|
||||
/**
|
||||
* A name representation for this type of item.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getType(): string;
|
||||
|
||||
/**
|
||||
* A name representation for the category of this item.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getCategory(): string;
|
||||
}
|
||||
@@ -9,19 +9,20 @@
|
||||
|
||||
namespace App\Invoice;
|
||||
|
||||
use App\Entity\ExportableItem;
|
||||
use App\Repository\Query\InvoiceQuery;
|
||||
|
||||
interface InvoiceItemRepositoryInterface
|
||||
{
|
||||
/**
|
||||
* @param InvoiceItemInterface[] $invoiceItems
|
||||
* @param ExportableItem[] $invoiceItems
|
||||
* @return void
|
||||
*/
|
||||
public function setExported(array $invoiceItems) /* : void */;
|
||||
|
||||
/**
|
||||
* @param InvoiceQuery $query
|
||||
* @return InvoiceItemInterface[]
|
||||
* @return ExportableItem[]
|
||||
*/
|
||||
public function getInvoiceItemsForQuery(InvoiceQuery $query): iterable;
|
||||
}
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
<?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;
|
||||
|
||||
/**
|
||||
* Will be removed with 2.0, just here for BC compatibility.
|
||||
*/
|
||||
interface InvoiceItemWithAmountInterface
|
||||
{
|
||||
public function getAmount(): float;
|
||||
}
|
||||
@@ -12,6 +12,7 @@ namespace App\Invoice;
|
||||
use App\Activity\ActivityStatisticService;
|
||||
use App\Customer\CustomerStatisticService;
|
||||
use App\Entity\Customer;
|
||||
use App\Entity\ExportableItem;
|
||||
use App\Entity\InvoiceTemplate;
|
||||
use App\Entity\User;
|
||||
use App\Invoice\Hydrator\InvoiceItemDefaultHydrator;
|
||||
@@ -29,58 +30,28 @@ use App\Repository\Query\InvoiceQuery;
|
||||
*/
|
||||
final class InvoiceModel
|
||||
{
|
||||
private ?Customer $customer = null;
|
||||
private ?InvoiceQuery $query = null;
|
||||
/**
|
||||
* @var Customer|null
|
||||
* @var ExportableItem[]
|
||||
*/
|
||||
private $customer;
|
||||
/**
|
||||
* @var InvoiceQuery
|
||||
*/
|
||||
private $query;
|
||||
/**
|
||||
* @var InvoiceItemInterface[]
|
||||
*/
|
||||
private $entries = [];
|
||||
/**
|
||||
* @var InvoiceTemplate
|
||||
*/
|
||||
private $template;
|
||||
/**
|
||||
* @var CalculatorInterface
|
||||
*/
|
||||
private $calculator;
|
||||
/**
|
||||
* @var NumberGeneratorInterface
|
||||
*/
|
||||
private $generator;
|
||||
/**
|
||||
* @var \DateTime
|
||||
*/
|
||||
private $invoiceDate;
|
||||
/**
|
||||
* @var User
|
||||
*/
|
||||
private $user;
|
||||
/**
|
||||
* @var InvoiceFormatter
|
||||
*/
|
||||
private $formatter;
|
||||
private array $entries = [];
|
||||
private ?InvoiceTemplate $template = null;
|
||||
private ?CalculatorInterface $calculator = null;
|
||||
private ?NumberGeneratorInterface $generator = null;
|
||||
private \DateTime $invoiceDate;
|
||||
private ?User $user = null;
|
||||
private InvoiceFormatter $formatter;
|
||||
/**
|
||||
* @var InvoiceModelHydrator[]
|
||||
*/
|
||||
private $modelHydrator = [];
|
||||
private array $modelHydrator = [];
|
||||
/**
|
||||
* @var InvoiceItemHydrator[]
|
||||
*/
|
||||
private $itemHydrator = [];
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $invoiceNumber;
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $hideZeroTax = false;
|
||||
private array $itemHydrator = [];
|
||||
private ?string $invoiceNumber = null;
|
||||
private bool $hideZeroTax = false;
|
||||
|
||||
/**
|
||||
* @internal use InvoiceModelFactory
|
||||
@@ -97,19 +68,12 @@ final class InvoiceModel
|
||||
$this->addItemHydrator(new InvoiceItemDefaultHydrator());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return InvoiceQuery
|
||||
*/
|
||||
public function getQuery(): ?InvoiceQuery
|
||||
{
|
||||
return $this->query;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param InvoiceQuery $query
|
||||
* @return InvoiceModel
|
||||
*/
|
||||
public function setQuery(InvoiceQuery $query)
|
||||
public function setQuery(InvoiceQuery $query): InvoiceModel
|
||||
{
|
||||
$this->query = $query;
|
||||
|
||||
@@ -119,9 +83,9 @@ final class InvoiceModel
|
||||
/**
|
||||
* Returns the raw data from the model.
|
||||
*
|
||||
* Do not use this method for rendering the invoice, use getItems() instead.
|
||||
* Do not use this method for rendering the invoice, use getCalculator()->getEntries() instead.
|
||||
*
|
||||
* @return InvoiceItemInterface[]
|
||||
* @return ExportableItem[]
|
||||
*/
|
||||
public function getEntries(): array
|
||||
{
|
||||
@@ -129,21 +93,7 @@ final class InvoiceModel
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated since 1.3 - will be removed with 2.0
|
||||
* @param InvoiceItemInterface[] $entries
|
||||
* @return InvoiceModel
|
||||
*/
|
||||
public function setEntries(array $entries): InvoiceModel
|
||||
{
|
||||
@trigger_error('setEntries() is deprecated and will be removed with 2.0', E_USER_DEPRECATED);
|
||||
|
||||
$this->entries = $entries;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param InvoiceItemInterface[] $entries
|
||||
* @param ExportableItem[] $entries
|
||||
* @return InvoiceModel
|
||||
*/
|
||||
public function addEntries(array $entries): InvoiceModel
|
||||
@@ -186,11 +136,7 @@ final class InvoiceModel
|
||||
return $this->customer;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Customer|null $customer
|
||||
* @return InvoiceModel
|
||||
*/
|
||||
public function setCustomer($customer): InvoiceModel
|
||||
public function setCustomer(?Customer $customer): InvoiceModel
|
||||
{
|
||||
$this->customer = $customer;
|
||||
|
||||
@@ -239,14 +185,6 @@ final class InvoiceModel
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated since 1.9 - will be removed with 2.0 - use getInvoiceNumber() instead
|
||||
*/
|
||||
public function getNumberGenerator(): ?NumberGeneratorInterface
|
||||
{
|
||||
return $this->generator;
|
||||
}
|
||||
|
||||
public function setCalculator(CalculatorInterface $calculator): InvoiceModel
|
||||
{
|
||||
$this->calculator = $calculator;
|
||||
|
||||
@@ -15,15 +15,11 @@ use App\Project\ProjectStatisticService;
|
||||
|
||||
final class InvoiceModelFactory
|
||||
{
|
||||
private $customerStatisticService;
|
||||
private $projectStatisticService;
|
||||
private $activityStatisticService;
|
||||
|
||||
public function __construct(CustomerStatisticService $customerStatistic, ProjectStatisticService $projectStatistic, ActivityStatisticService $activityStatistic)
|
||||
{
|
||||
$this->customerStatisticService = $customerStatistic;
|
||||
$this->projectStatisticService = $projectStatistic;
|
||||
$this->activityStatisticService = $activityStatistic;
|
||||
public function __construct(
|
||||
private CustomerStatisticService $customerStatisticService,
|
||||
private ProjectStatisticService $projectStatisticService,
|
||||
private ActivityStatisticService $activityStatisticService
|
||||
) {
|
||||
}
|
||||
|
||||
public function createModel(InvoiceFormatter $formatter): InvoiceModel
|
||||
|
||||
@@ -13,40 +13,22 @@ use App\Configuration\SystemConfiguration;
|
||||
use App\Invoice\InvoiceModel;
|
||||
use App\Invoice\NumberGeneratorInterface;
|
||||
use App\Repository\InvoiceRepository;
|
||||
use App\Utils\NumberGenerator;
|
||||
|
||||
final class ConfigurableNumberGenerator implements NumberGeneratorInterface
|
||||
{
|
||||
/**
|
||||
* @var InvoiceModel
|
||||
*/
|
||||
private $model;
|
||||
/**
|
||||
* @var InvoiceRepository
|
||||
*/
|
||||
private $repository;
|
||||
/**
|
||||
* @var SystemConfiguration
|
||||
*/
|
||||
private $configuration;
|
||||
private ?InvoiceModel $model = null;
|
||||
|
||||
public function __construct(InvoiceRepository $repository, SystemConfiguration $configuration)
|
||||
public function __construct(private InvoiceRepository $repository, private SystemConfiguration $configuration)
|
||||
{
|
||||
$this->repository = $repository;
|
||||
$this->configuration = $configuration;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getId(): string
|
||||
{
|
||||
return 'default';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param InvoiceModel $model
|
||||
*/
|
||||
public function setModel(InvoiceModel $model)
|
||||
public function setModel(InvoiceModel $model): void
|
||||
{
|
||||
$this->model = $model;
|
||||
}
|
||||
@@ -57,181 +39,53 @@ final class ConfigurableNumberGenerator implements NumberGeneratorInterface
|
||||
public function getInvoiceNumber(): string
|
||||
{
|
||||
$format = $this->configuration->find('invoice.number_format');
|
||||
if (empty($format) || !\is_string($format)) {
|
||||
$format = '{Y}/{cy,3}';
|
||||
}
|
||||
|
||||
$invoiceDate = $this->model->getInvoiceDate();
|
||||
|
||||
$loops = 0;
|
||||
$increaseBy = 0;
|
||||
|
||||
do {
|
||||
$result = $format;
|
||||
|
||||
preg_match_all('/{[^}]*?}/', $format, $matches);
|
||||
|
||||
foreach ($matches[0] as $part) {
|
||||
$partialResult = $this->parseReplacer($invoiceDate, $part, $increaseBy);
|
||||
$result = str_replace($part, $partialResult, $result);
|
||||
$numberGenerator = new NumberGenerator($format, function (string $originalFormat, string $format, int $increaseBy) use ($invoiceDate): string|int {
|
||||
if ($this->model === null) {
|
||||
throw new \InvalidArgumentException('Missing invoice model, cannot calculate invoice number');
|
||||
}
|
||||
|
||||
return match ($format) {
|
||||
'Y' => $invoiceDate->format('Y'),
|
||||
'y' => $invoiceDate->format('y'),
|
||||
'M' => $invoiceDate->format('m'),
|
||||
'm' => $invoiceDate->format('n'),
|
||||
'D' => $invoiceDate->format('d'),
|
||||
'd' => $invoiceDate->format('j'),
|
||||
'date' => $invoiceDate->format('ymd'),
|
||||
'cc' => $this->repository->getCounterForCustomerAllTime($this->model->getCustomer()) + $increaseBy,
|
||||
'ccy' => $this->repository->getCounterForYear($invoiceDate, $this->model->getCustomer()) + $increaseBy,
|
||||
'ccm' => $this->repository->getCounterForMonth($invoiceDate, $this->model->getCustomer()) + $increaseBy,
|
||||
'ccd' => $this->repository->getCounterForDay($invoiceDate, $this->model->getCustomer()) + $increaseBy,
|
||||
'cu' => $this->repository->getCounterForUserAllTime($this->model->getUser()) + $increaseBy,
|
||||
'cuy' => $this->repository->getCounterForYear($invoiceDate, null, $this->model->getUser()) + $increaseBy,
|
||||
'cum' => $this->repository->getCounterForMonth($invoiceDate, null, $this->model->getUser()) + $increaseBy,
|
||||
'cud' => $this->repository->getCounterForDay($invoiceDate, null, $this->model->getUser()) + $increaseBy,
|
||||
'ustaff' => (string) $this->model->getUser()?->getAccountNumber(),
|
||||
'uid' => (string) $this->model->getUser()?->getId(),
|
||||
'c' => $this->repository->getCounterForCustomerAllTime() + $increaseBy,
|
||||
'cy' => $this->repository->getCounterForYear($invoiceDate) + $increaseBy,
|
||||
'cm' => $this->repository->getCounterForMonth($invoiceDate) + $increaseBy,
|
||||
'cd' => $this->repository->getCounterForDay($invoiceDate) + $increaseBy,
|
||||
'cname' => (string) $this->model->getCustomer()?->getName(),
|
||||
'cnumber' => (string) $this->model->getCustomer()?->getNumber(),
|
||||
default => $originalFormat,
|
||||
};
|
||||
});
|
||||
|
||||
do {
|
||||
$result = $numberGenerator->getNumber($increaseBy);
|
||||
$increaseBy++;
|
||||
} while ($this->repository->hasInvoice($result) && $loops++ < 99);
|
||||
|
||||
return (string) $result;
|
||||
}
|
||||
|
||||
private function parseReplacer(\DateTime $invoiceDate, string $originalFormat, int $increaseBy): string
|
||||
{
|
||||
$formatterLength = null;
|
||||
$formatPattern = str_replace(['{', '}'], '', $originalFormat);
|
||||
|
||||
$parts = preg_split('/([+\-,])+/', $formatPattern, -1, PREG_SPLIT_DELIM_CAPTURE);
|
||||
$format = array_shift($parts);
|
||||
|
||||
if (\count($parts) % 2 !== 0) {
|
||||
throw new \InvalidArgumentException('Invalid configuration found');
|
||||
}
|
||||
|
||||
while (null !== ($tmp = array_shift($parts))) {
|
||||
switch ($tmp) {
|
||||
case '+':
|
||||
$local = array_shift($parts);
|
||||
if (!is_numeric($local)) {
|
||||
throw new \InvalidArgumentException('Unknown increment found');
|
||||
}
|
||||
$increaseBy = $increaseBy + \intval($local);
|
||||
break;
|
||||
|
||||
case '-':
|
||||
$local = array_shift($parts);
|
||||
if (!is_numeric($local)) {
|
||||
throw new \InvalidArgumentException('Unknown decrement found');
|
||||
}
|
||||
$increaseBy = $increaseBy - \intval($local);
|
||||
break;
|
||||
|
||||
case ',':
|
||||
$local = array_shift($parts);
|
||||
if (!is_numeric($local)) {
|
||||
throw new \InvalidArgumentException('Unknown format length found');
|
||||
}
|
||||
$formatterLength = \intval($local);
|
||||
if ((string) $formatterLength !== $local) {
|
||||
throw new \InvalidArgumentException('Unknown format length found');
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new \InvalidArgumentException('Unknown pattern found');
|
||||
}
|
||||
}
|
||||
|
||||
if ($increaseBy === 0) {
|
||||
$increaseBy = 1;
|
||||
}
|
||||
|
||||
switch ($format) {
|
||||
case 'Y':
|
||||
$partialResult = $invoiceDate->format('Y');
|
||||
break;
|
||||
|
||||
case 'y':
|
||||
$partialResult = $invoiceDate->format('y');
|
||||
break;
|
||||
|
||||
case 'M':
|
||||
$partialResult = $invoiceDate->format('m');
|
||||
break;
|
||||
|
||||
case 'm':
|
||||
$partialResult = $invoiceDate->format('n');
|
||||
break;
|
||||
|
||||
case 'D':
|
||||
$partialResult = $invoiceDate->format('d');
|
||||
break;
|
||||
|
||||
case 'd':
|
||||
$partialResult = $invoiceDate->format('j');
|
||||
break;
|
||||
|
||||
case 'date':
|
||||
$partialResult = $invoiceDate->format('ymd');
|
||||
break;
|
||||
|
||||
// for customer
|
||||
case 'cc':
|
||||
$partialResult = $this->repository->getCounterForCustomerAllTime($this->model->getCustomer()) + $increaseBy;
|
||||
break;
|
||||
|
||||
case 'ccy':
|
||||
$partialResult = $this->repository->getCounterForYear($invoiceDate, $this->model->getCustomer()) + $increaseBy;
|
||||
break;
|
||||
|
||||
case 'ccm':
|
||||
$partialResult = $this->repository->getCounterForMonth($invoiceDate, $this->model->getCustomer()) + $increaseBy;
|
||||
break;
|
||||
|
||||
case 'ccd':
|
||||
$partialResult = $this->repository->getCounterForDay($invoiceDate, $this->model->getCustomer()) + $increaseBy;
|
||||
break;
|
||||
|
||||
// for user
|
||||
case 'cu':
|
||||
$partialResult = $this->repository->getCounterForUserAllTime($this->model->getUser()) + $increaseBy;
|
||||
break;
|
||||
|
||||
case 'cuy':
|
||||
$partialResult = $this->repository->getCounterForYear($invoiceDate, null, $this->model->getUser()) + $increaseBy;
|
||||
break;
|
||||
|
||||
case 'cum':
|
||||
$partialResult = $this->repository->getCounterForMonth($invoiceDate, null, $this->model->getUser()) + $increaseBy;
|
||||
break;
|
||||
|
||||
case 'cud':
|
||||
$partialResult = $this->repository->getCounterForDay($invoiceDate, null, $this->model->getUser()) + $increaseBy;
|
||||
break;
|
||||
|
||||
case 'ustaff':
|
||||
$partialResult = $this->model->getUser() !== null ? $this->model->getUser()->getAccountNumber() : '';
|
||||
break;
|
||||
|
||||
case 'uid':
|
||||
$partialResult = $this->model->getUser() !== null ? (string) $this->model->getUser()->getId() : '';
|
||||
break;
|
||||
|
||||
// across all invoices
|
||||
case 'c':
|
||||
$partialResult = $this->repository->getCounterForCustomerAllTime() + $increaseBy;
|
||||
break;
|
||||
|
||||
case 'cy':
|
||||
$partialResult = $this->repository->getCounterForYear($invoiceDate) + $increaseBy;
|
||||
break;
|
||||
|
||||
case 'cm':
|
||||
$partialResult = $this->repository->getCounterForMonth($invoiceDate) + $increaseBy;
|
||||
break;
|
||||
|
||||
case 'cd':
|
||||
$partialResult = $this->repository->getCounterForDay($invoiceDate) + $increaseBy;
|
||||
break;
|
||||
|
||||
case 'cname':
|
||||
$partialResult = $this->model->getCustomer() !== null ? $this->model->getCustomer()->getName() : '';
|
||||
break;
|
||||
|
||||
case 'cnumber':
|
||||
$partialResult = $this->model->getCustomer() !== null ? $this->model->getCustomer()->getNumber() : '';
|
||||
break;
|
||||
|
||||
default:
|
||||
$partialResult = $originalFormat;
|
||||
}
|
||||
|
||||
if (null !== $formatterLength) {
|
||||
$partialResult = str_pad($partialResult, $formatterLength, '0', STR_PAD_LEFT);
|
||||
}
|
||||
|
||||
return $partialResult;
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,32 +19,18 @@ use App\Repository\InvoiceRepository;
|
||||
*/
|
||||
final class DateNumberGenerator implements NumberGeneratorInterface
|
||||
{
|
||||
/**
|
||||
* @var InvoiceModel
|
||||
*/
|
||||
private $model;
|
||||
/**
|
||||
* @var InvoiceRepository
|
||||
*/
|
||||
private $repository;
|
||||
private ?InvoiceModel $model = null;
|
||||
|
||||
public function __construct(InvoiceRepository $repository)
|
||||
public function __construct(private InvoiceRepository $repository)
|
||||
{
|
||||
$this->repository = $repository;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getId(): string
|
||||
{
|
||||
return 'date';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param InvoiceModel $model
|
||||
*/
|
||||
public function setModel(InvoiceModel $model)
|
||||
public function setModel(InvoiceModel $model): void
|
||||
{
|
||||
$this->model = $model;
|
||||
}
|
||||
|
||||
@@ -14,14 +14,8 @@ namespace App\Invoice;
|
||||
*/
|
||||
interface NumberGeneratorInterface
|
||||
{
|
||||
/**
|
||||
* @param InvoiceModel $model
|
||||
*/
|
||||
public function setModel(InvoiceModel $model);
|
||||
public function setModel(InvoiceModel $model): void;
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getInvoiceNumber(): string;
|
||||
|
||||
/**
|
||||
|
||||
@@ -9,9 +9,9 @@
|
||||
|
||||
namespace App\Invoice\Renderer;
|
||||
|
||||
use App\Entity\InvoiceDocument;
|
||||
use App\Invoice\InvoiceFilename;
|
||||
use App\Invoice\InvoiceModel;
|
||||
use App\Model\InvoiceDocument;
|
||||
use Symfony\Component\HttpFoundation\BinaryFileResponse;
|
||||
use Symfony\Component\HttpFoundation\ResponseHeaderBag;
|
||||
|
||||
|
||||
@@ -9,8 +9,8 @@
|
||||
|
||||
namespace App\Invoice\Renderer;
|
||||
|
||||
use App\Entity\InvoiceDocument;
|
||||
use App\Invoice\InvoiceModel;
|
||||
use App\Model\InvoiceDocument;
|
||||
use PhpOffice\PhpSpreadsheet\Cell\Cell;
|
||||
use PhpOffice\PhpSpreadsheet\Cell\DataType;
|
||||
use PhpOffice\PhpSpreadsheet\IOFactory;
|
||||
@@ -67,10 +67,10 @@ abstract class AbstractSpreadsheetRenderer extends AbstractRenderer
|
||||
$sheetValues = false;
|
||||
foreach ($row->getCellIterator() as $cell) {
|
||||
$value = $cell->getValue();
|
||||
$replacer = null;
|
||||
if ($value === null) {
|
||||
continue;
|
||||
}
|
||||
$replacer = null;
|
||||
$firstReplacerPos = stripos($value, '${');
|
||||
if ($firstReplacerPos === false) {
|
||||
continue;
|
||||
@@ -98,7 +98,7 @@ abstract class AbstractSpreadsheetRenderer extends AbstractRenderer
|
||||
if (stripos($value, $searchKey) === false) {
|
||||
continue;
|
||||
}
|
||||
if (\is_string($content) && $content[0] === '=') {
|
||||
if (\is_string($content) && str_starts_with($content, '=')) {
|
||||
$contentLooksLikeFormula = true;
|
||||
}
|
||||
$value = str_replace($searchKey, $content, $value);
|
||||
|
||||
@@ -9,9 +9,9 @@
|
||||
|
||||
namespace App\Invoice\Renderer;
|
||||
|
||||
use App\Entity\InvoiceDocument;
|
||||
use App\Invoice\InvoiceModel;
|
||||
use App\Invoice\RendererInterface;
|
||||
use App\Model\InvoiceDocument;
|
||||
use App\Twig\TwigRendererTrait;
|
||||
use Twig\Environment;
|
||||
|
||||
@@ -22,14 +22,8 @@ abstract class AbstractTwigRenderer implements RendererInterface
|
||||
{
|
||||
use TwigRendererTrait;
|
||||
|
||||
/**
|
||||
* @var Environment
|
||||
*/
|
||||
private $twig;
|
||||
|
||||
public function __construct(Environment $twig)
|
||||
public function __construct(private Environment $twig)
|
||||
{
|
||||
$this->twig = $twig;
|
||||
}
|
||||
|
||||
protected function renderTwigTemplate(InvoiceDocument $document, InvoiceModel $model, array $options = []): string
|
||||
|
||||
@@ -16,7 +16,7 @@ use PhpOffice\PhpSpreadsheet\Cell\IValueBinder;
|
||||
use PhpOffice\PhpSpreadsheet\RichText\RichText;
|
||||
use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
|
||||
|
||||
class AdvancedValueBinder extends DefaultValueBinder implements IValueBinder
|
||||
final class AdvancedValueBinder extends DefaultValueBinder implements IValueBinder
|
||||
{
|
||||
/**
|
||||
* Bind value to a cell.
|
||||
@@ -38,7 +38,7 @@ class AdvancedValueBinder extends DefaultValueBinder implements IValueBinder
|
||||
|
||||
if ($dataType === DataType::TYPE_STRING && !$value instanceof RichText) {
|
||||
// Check for newline character "\n"
|
||||
if (strpos($value, "\n") !== false) {
|
||||
if (\is_string($value) && str_contains($value, "\n")) {
|
||||
$cell->setValueExplicit($value, DataType::TYPE_STRING);
|
||||
$cell->getWorksheet()->getStyle($cell->getCoordinate())->getAlignment()->setWrapText(true);
|
||||
|
||||
|
||||
@@ -9,9 +9,9 @@
|
||||
|
||||
namespace App\Invoice\Renderer;
|
||||
|
||||
use App\Entity\InvoiceDocument;
|
||||
use App\Invoice\InvoiceModel;
|
||||
use App\Invoice\RendererInterface;
|
||||
use App\Model\InvoiceDocument;
|
||||
use PhpOffice\PhpWord\Escaper\Xml;
|
||||
use PhpOffice\PhpWord\Exception\Exception as OfficeException;
|
||||
use PhpOffice\PhpWord\Settings;
|
||||
@@ -21,11 +21,6 @@ use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
final class DocxRenderer extends AbstractRenderer implements RendererInterface
|
||||
{
|
||||
/**
|
||||
* @param InvoiceDocument $document
|
||||
* @param InvoiceModel $model
|
||||
* @return Response
|
||||
*/
|
||||
public function render(InvoiceDocument $document, InvoiceModel $model): Response
|
||||
{
|
||||
Settings::setOutputEscapingEnabled(false);
|
||||
@@ -46,9 +41,7 @@ final class DocxRenderer extends AbstractRenderer implements RendererInterface
|
||||
try {
|
||||
$template->cloneRow('entry.row', \count($model->getCalculator()->getEntries()));
|
||||
} catch (OfficeException $ex) {
|
||||
@trigger_error(
|
||||
sprintf('Invoice document (%s) did not contain a clone row, was that on purpose?', $document->getFilename())
|
||||
);
|
||||
@trigger_error('Invoice document did not contain a clone row, was that on purpose?');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,18 +66,12 @@ final class DocxRenderer extends AbstractRenderer implements RendererInterface
|
||||
return $this->getFileResponse(new Stream($cacheFile), $filename);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
protected function getFileExtensions()
|
||||
protected function getFileExtensions(): array
|
||||
{
|
||||
return ['.docx'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
protected function getContentType()
|
||||
protected function getContentType(): string
|
||||
{
|
||||
return 'application/vnd.openxmlformats-officedocument.wordprocessingml.document';
|
||||
}
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
<?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\Renderer;
|
||||
|
||||
use App\Entity\InvoiceDocument;
|
||||
use App\Invoice\InvoiceFilename;
|
||||
use App\Invoice\InvoiceModel;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpFoundation\ResponseHeaderBag;
|
||||
|
||||
final class JsonRenderer extends AbstractTwigRenderer
|
||||
{
|
||||
public function supports(InvoiceDocument $document): bool
|
||||
{
|
||||
return stripos($document->getFilename(), '.json.twig') !== false;
|
||||
}
|
||||
|
||||
public function render(InvoiceDocument $document, InvoiceModel $model): Response
|
||||
{
|
||||
$content = $this->renderTwigTemplate($document, $model);
|
||||
$filename = (string) new InvoiceFilename($model);
|
||||
|
||||
$response = new Response($content);
|
||||
|
||||
$disposition = $response->headers->makeDisposition(ResponseHeaderBag::DISPOSITION_ATTACHMENT, $filename . '.json');
|
||||
|
||||
$response->headers->set('Content-Type', 'application/json');
|
||||
$response->headers->set('Content-Disposition', $disposition);
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
@@ -15,28 +15,17 @@ use PhpOffice\PhpSpreadsheet\Spreadsheet;
|
||||
|
||||
final class OdsRenderer extends AbstractSpreadsheetRenderer implements RendererInterface
|
||||
{
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
protected function getFileExtensions()
|
||||
protected function getFileExtensions(): array
|
||||
{
|
||||
return ['.ods'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
protected function getContentType()
|
||||
protected function getContentType(): string
|
||||
{
|
||||
return 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Spreadsheet $spreadsheet
|
||||
* @return string
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function saveSpreadsheet(Spreadsheet $spreadsheet)
|
||||
protected function saveSpreadsheet(Spreadsheet $spreadsheet): string
|
||||
{
|
||||
$filename = @tempnam(sys_get_temp_dir(), 'kimai-invoice-ods');
|
||||
if (false === $filename) {
|
||||
|
||||
@@ -9,29 +9,23 @@
|
||||
|
||||
namespace App\Invoice\Renderer;
|
||||
|
||||
use App\Entity\InvoiceDocument;
|
||||
use App\Export\Base\DispositionInlineInterface;
|
||||
use App\Export\Base\DispositionInlineTrait;
|
||||
use App\Export\ExportContext;
|
||||
use App\Invoice\InvoiceFilename;
|
||||
use App\Invoice\InvoiceModel;
|
||||
use App\Utils\HtmlToPdfConverter;
|
||||
use App\Model\InvoiceDocument;
|
||||
use App\Pdf\HtmlToPdfConverter;
|
||||
use App\Pdf\PdfContext;
|
||||
use App\Pdf\PdfRendererTrait;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Twig\Environment;
|
||||
|
||||
final class PdfRenderer extends AbstractTwigRenderer implements DispositionInlineInterface
|
||||
{
|
||||
use DispositionInlineTrait;
|
||||
use PDFRendererTrait;
|
||||
|
||||
/**
|
||||
* @var HtmlToPdfConverter
|
||||
*/
|
||||
private $converter;
|
||||
|
||||
public function __construct(Environment $twig, HtmlToPdfConverter $converter)
|
||||
public function __construct(Environment $twig, private HtmlToPdfConverter $converter)
|
||||
{
|
||||
parent::__construct($twig);
|
||||
$this->converter = $converter;
|
||||
}
|
||||
|
||||
public function supports(InvoiceDocument $document): bool
|
||||
@@ -43,7 +37,7 @@ final class PdfRenderer extends AbstractTwigRenderer implements DispositionInlin
|
||||
{
|
||||
$filename = new InvoiceFilename($model);
|
||||
|
||||
$context = new ExportContext();
|
||||
$context = new PdfContext();
|
||||
$context->setOption('filename', $filename->getFilename());
|
||||
$context->setOption('setAutoTopMargin', 'pad');
|
||||
$context->setOption('setAutoBottomMargin', 'pad');
|
||||
@@ -53,19 +47,6 @@ final class PdfRenderer extends AbstractTwigRenderer implements DispositionInlin
|
||||
$content = $this->renderTwigTemplate($document, $model, ['pdfContext' => $context]);
|
||||
$content = $this->converter->convertToPdf($content, $context->getOptions());
|
||||
|
||||
$filename = $context->getOption('filename');
|
||||
if (empty($filename)) {
|
||||
$filename = new InvoiceFilename($model);
|
||||
$filename = $filename->getFilename();
|
||||
}
|
||||
|
||||
$response = new Response($content);
|
||||
|
||||
$disposition = $response->headers->makeDisposition($this->getDisposition(), $filename . '.pdf');
|
||||
|
||||
$response->headers->set('Content-Type', 'application/pdf');
|
||||
$response->headers->set('Content-Disposition', $disposition);
|
||||
|
||||
return $response;
|
||||
return $this->createPdfResponse($content, $context);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
<?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\Renderer;
|
||||
|
||||
use App\Invoice\InvoiceItem;
|
||||
use App\Invoice\InvoiceModel;
|
||||
|
||||
trait RendererTrait
|
||||
{
|
||||
/**
|
||||
* @var InvoiceModel
|
||||
*/
|
||||
private $model;
|
||||
|
||||
/**
|
||||
* @deprecated since 1.6.2 - will be removed with 2.0
|
||||
* @param InvoiceModel $model
|
||||
* @return array
|
||||
*/
|
||||
protected function modelToReplacer(InvoiceModel $model)
|
||||
{
|
||||
@trigger_error('modelToReplacer() is deprecated and will be removed with 2.0', E_USER_DEPRECATED);
|
||||
|
||||
$this->model = $model;
|
||||
|
||||
return $model->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated since 1.3 - will be removed with 2.0
|
||||
*/
|
||||
protected function timesheetToArray(InvoiceItem $invoiceItem): array
|
||||
{
|
||||
@trigger_error('timesheetToArray() is deprecated and will be removed with 2.0', E_USER_DEPRECATED);
|
||||
|
||||
return $this->model->itemToArray($invoiceItem);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated since 1.6.2 - will be removed with 2.0
|
||||
* @param InvoiceItem $invoiceItem
|
||||
* @return array
|
||||
*/
|
||||
protected function invoiceItemToArray(InvoiceItem $invoiceItem): array
|
||||
{
|
||||
@trigger_error('invoiceItemToArray() is deprecated and will be removed with 2.0', E_USER_DEPRECATED);
|
||||
|
||||
return $this->model->itemToArray($invoiceItem);
|
||||
}
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
<?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\Renderer;
|
||||
|
||||
use App\Entity\InvoiceDocument;
|
||||
use App\Invoice\InvoiceFilename;
|
||||
use App\Invoice\InvoiceModel;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpFoundation\ResponseHeaderBag;
|
||||
|
||||
final class TextRenderer extends AbstractTwigRenderer
|
||||
{
|
||||
public function supports(InvoiceDocument $document): bool
|
||||
{
|
||||
return stripos($document->getFilename(), '.txt.twig') !== false;
|
||||
}
|
||||
|
||||
public function render(InvoiceDocument $document, InvoiceModel $model): Response
|
||||
{
|
||||
$content = $this->renderTwigTemplate($document, $model);
|
||||
$filename = (string) new InvoiceFilename($model);
|
||||
|
||||
$response = new Response($content);
|
||||
|
||||
$disposition = $response->headers->makeDisposition(ResponseHeaderBag::DISPOSITION_ATTACHMENT, $filename . '.txt');
|
||||
|
||||
$response->headers->set('Content-Type', 'text/plain');
|
||||
$response->headers->set('Content-Disposition', $disposition);
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
@@ -9,8 +9,8 @@
|
||||
|
||||
namespace App\Invoice\Renderer;
|
||||
|
||||
use App\Entity\InvoiceDocument;
|
||||
use App\Invoice\InvoiceModel;
|
||||
use App\Model\InvoiceDocument;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
final class TwigRenderer extends AbstractTwigRenderer
|
||||
|
||||
@@ -15,27 +15,16 @@ use PhpOffice\PhpSpreadsheet\Spreadsheet;
|
||||
|
||||
final class XlsxRenderer extends AbstractSpreadsheetRenderer implements RendererInterface
|
||||
{
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
protected function getFileExtensions()
|
||||
protected function getFileExtensions(): array
|
||||
{
|
||||
return ['.xlsx', '.xls'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
protected function getContentType()
|
||||
protected function getContentType(): string
|
||||
{
|
||||
return 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Spreadsheet $spreadsheet
|
||||
* @return string
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function saveSpreadsheet(Spreadsheet $spreadsheet)
|
||||
{
|
||||
$filename = @tempnam(sys_get_temp_dir(), 'kimai-invoice-xlsx');
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
<?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\Renderer;
|
||||
|
||||
use App\Entity\InvoiceDocument;
|
||||
use App\Invoice\InvoiceFilename;
|
||||
use App\Invoice\InvoiceModel;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpFoundation\ResponseHeaderBag;
|
||||
|
||||
final class XmlRenderer extends AbstractTwigRenderer
|
||||
{
|
||||
public function supports(InvoiceDocument $document): bool
|
||||
{
|
||||
return stripos($document->getFilename(), '.xml.twig') !== false;
|
||||
}
|
||||
|
||||
public function render(InvoiceDocument $document, InvoiceModel $model): Response
|
||||
{
|
||||
$content = $this->renderTwigTemplate($document, $model);
|
||||
$filename = (string) new InvoiceFilename($model);
|
||||
|
||||
$response = new Response($content);
|
||||
|
||||
$disposition = $response->headers->makeDisposition(ResponseHeaderBag::DISPOSITION_ATTACHMENT, $filename . '.xml');
|
||||
|
||||
$response->headers->set('Content-Type', 'application/xml');
|
||||
$response->headers->set('Content-Disposition', $disposition);
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
namespace App\Invoice;
|
||||
|
||||
use App\Entity\InvoiceDocument;
|
||||
use App\Model\InvoiceDocument;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
interface RendererInterface
|
||||
|
||||
@@ -9,20 +9,18 @@
|
||||
|
||||
namespace App\Invoice;
|
||||
|
||||
use App\Configuration\LanguageFormattings;
|
||||
use App\Constants;
|
||||
use App\Entity\Customer;
|
||||
use App\Configuration\LocaleService;
|
||||
use App\Entity\ExportableItem;
|
||||
use App\Entity\Invoice;
|
||||
use App\Entity\InvoiceDocument;
|
||||
use App\Event\InvoiceCreatedEvent;
|
||||
use App\Event\InvoiceDeleteEvent;
|
||||
use App\Event\InvoicePostRenderEvent;
|
||||
use App\Event\InvoicePreRenderEvent;
|
||||
use App\Export\Base\DispositionInlineInterface;
|
||||
use App\Model\InvoiceDocument;
|
||||
use App\Repository\InvoiceDocumentRepository;
|
||||
use App\Repository\InvoiceRepository;
|
||||
use App\Repository\Query\InvoiceQuery;
|
||||
use App\Timesheet\DateTimeFactory;
|
||||
use App\Utils\FileHelper;
|
||||
use Symfony\Component\HttpFoundation\BinaryFileResponse;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
@@ -36,32 +34,27 @@ final class ServiceInvoice
|
||||
/**
|
||||
* @var CalculatorInterface[]
|
||||
*/
|
||||
private $calculator = [];
|
||||
private array $calculator = [];
|
||||
/**
|
||||
* @var RendererInterface[]
|
||||
*/
|
||||
private $renderer = [];
|
||||
private array $renderer = [];
|
||||
/**
|
||||
* @var NumberGeneratorInterface[]
|
||||
*/
|
||||
private $numberGenerator = [];
|
||||
private array $numberGenerator = [];
|
||||
/**
|
||||
* @var array InvoiceItemRepositoryInterface[]
|
||||
*/
|
||||
private $invoiceItemRepositories = [];
|
||||
private $documents;
|
||||
private $fileHelper;
|
||||
private $formatter;
|
||||
private $invoiceRepository;
|
||||
private $invoiceModelFactory;
|
||||
private array $invoiceItemRepositories = [];
|
||||
|
||||
public function __construct(InvoiceDocumentRepository $repository, FileHelper $fileHelper, InvoiceRepository $invoiceRepository, LanguageFormattings $formatter, InvoiceModelFactory $invoiceModelFactory)
|
||||
{
|
||||
$this->documents = $repository;
|
||||
$this->fileHelper = $fileHelper;
|
||||
$this->invoiceRepository = $invoiceRepository;
|
||||
$this->formatter = $formatter;
|
||||
$this->invoiceModelFactory = $invoiceModelFactory;
|
||||
public function __construct(
|
||||
private InvoiceDocumentRepository $documents,
|
||||
private FileHelper $fileHelper,
|
||||
private InvoiceRepository $invoiceRepository,
|
||||
private LocaleService $formatter,
|
||||
private InvoiceModelFactory $invoiceModelFactory
|
||||
) {
|
||||
}
|
||||
|
||||
public function addNumberGenerator(NumberGeneratorInterface $generator): ServiceInvoice
|
||||
@@ -258,24 +251,7 @@ final class ServiceInvoice
|
||||
|
||||
/**
|
||||
* @param InvoiceQuery $query
|
||||
* @return InvoiceItemInterface[]
|
||||
* @deprecated since 1.14 and will be removed with 2.0
|
||||
*/
|
||||
public function findInvoiceItems(InvoiceQuery $query): array
|
||||
{
|
||||
@trigger_error('Using findInvoiceItems() is deprecated since 1.14 and will be removed with 2.0', E_USER_DEPRECATED);
|
||||
|
||||
// customer needs to be defined, as we need the currency for the invoice
|
||||
if (!$query->hasCustomers()) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return $this->getInvoiceItems($query);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param InvoiceQuery $query
|
||||
* @return InvoiceItemInterface[]
|
||||
* @return ExportableItem[]
|
||||
*/
|
||||
public function getInvoiceItems(InvoiceQuery $query): array
|
||||
{
|
||||
@@ -288,21 +264,8 @@ final class ServiceInvoice
|
||||
return $items;
|
||||
}
|
||||
|
||||
private function getDateTimeFactory(InvoiceQuery $query): DateTimeFactory
|
||||
{
|
||||
$timezone = date_default_timezone_get();
|
||||
$sunday = false;
|
||||
|
||||
if (null !== ($user = $query->getCurrentUser())) {
|
||||
$timezone = $user->getTimezone();
|
||||
$sunday = $user->isFirstDayOfWeekSunday();
|
||||
}
|
||||
|
||||
return new DateTimeFactory(new \DateTimeZone($timezone), $sunday);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param InvoiceItemInterface[] $entries
|
||||
* @param ExportableItem[] $entries
|
||||
*/
|
||||
private function markEntriesAsExported(array $entries)
|
||||
{
|
||||
@@ -311,7 +274,7 @@ final class ServiceInvoice
|
||||
}
|
||||
}
|
||||
|
||||
public function renderInvoiceWithModel(InvoiceModel $model, EventDispatcherInterface $dispatcher, bool $dispositionInline = false): Response
|
||||
public function renderInvoice(InvoiceModel $model, EventDispatcherInterface $dispatcher, bool $dispositionInline = false): Response
|
||||
{
|
||||
$document = $this->getDocumentByName($model->getTemplate()->getRenderer());
|
||||
if (null === $document) {
|
||||
@@ -339,20 +302,13 @@ final class ServiceInvoice
|
||||
);
|
||||
}
|
||||
|
||||
public function renderInvoice(InvoiceQuery $query, EventDispatcherInterface $dispatcher): Response
|
||||
{
|
||||
$model = $this->createModel($query);
|
||||
|
||||
return $this->renderInvoiceWithModel($model, $dispatcher);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param InvoiceModel $model
|
||||
* @param EventDispatcherInterface $dispatcher
|
||||
* @return Invoice
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function createInvoiceFromModel(InvoiceModel $model, EventDispatcherInterface $dispatcher): Invoice
|
||||
public function createInvoice(InvoiceModel $model, EventDispatcherInterface $dispatcher): Invoice
|
||||
{
|
||||
$document = $this->getDocumentByName($model->getTemplate()->getRenderer());
|
||||
if (null === $document) {
|
||||
@@ -382,12 +338,13 @@ final class ServiceInvoice
|
||||
$invoice = new Invoice();
|
||||
$invoice->setModel($model);
|
||||
$invoice->setFilename($invoiceFilename);
|
||||
|
||||
if (!$invoice->getCustomer()->hasInvoiceTemplate()) {
|
||||
$invoice->getCustomer()->setInvoiceTemplate($model->getTemplate());
|
||||
}
|
||||
$this->invoiceRepository->saveInvoice($invoice);
|
||||
|
||||
if ($model->getQuery()->isMarkAsExported()) {
|
||||
$this->markEntriesAsExported($model->getEntries());
|
||||
}
|
||||
|
||||
$this->markEntriesAsExported($model->getEntries());
|
||||
$dispatcher->dispatch(new InvoiceCreatedEvent($invoice, $model));
|
||||
|
||||
return $invoice;
|
||||
@@ -399,37 +356,6 @@ final class ServiceInvoice
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param InvoiceQuery $query
|
||||
* @param EventDispatcherInterface $dispatcher
|
||||
* @return Invoice[]
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function createInvoices(InvoiceQuery $query, EventDispatcherInterface $dispatcher): array
|
||||
{
|
||||
$invoices = [];
|
||||
|
||||
$models = $this->createModels($query);
|
||||
foreach ($models as $model) {
|
||||
$invoices[] = $this->createInvoiceFromModel($model, $dispatcher);
|
||||
}
|
||||
|
||||
return $invoices;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param InvoiceQuery $query
|
||||
* @param EventDispatcherInterface $dispatcher
|
||||
* @return Invoice
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function createInvoice(InvoiceQuery $query, EventDispatcherInterface $dispatcher): Invoice
|
||||
{
|
||||
$model = $this->createModel($query);
|
||||
|
||||
return $this->createInvoiceFromModel($model, $dispatcher);
|
||||
}
|
||||
|
||||
public function deleteInvoice(Invoice $invoice, EventDispatcherInterface $dispatcher)
|
||||
{
|
||||
$invoiceDirectory = $this->getInvoicesDirectory();
|
||||
@@ -461,47 +387,46 @@ final class ServiceInvoice
|
||||
|
||||
private function createModelWithoutEntries(InvoiceQuery $query): InvoiceModel
|
||||
{
|
||||
$customer = $query->getCustomer();
|
||||
if ($customer === null) {
|
||||
throw new \Exception('Cannot create invoice model without customer');
|
||||
}
|
||||
|
||||
$template = $query->getTemplate();
|
||||
|
||||
if (!$query->hasCustomers()) {
|
||||
throw new \Exception('Cannot create invoice model without customer');
|
||||
if ($query->isAllowTemplateOverwrite() && $customer->hasInvoiceTemplate()) {
|
||||
$template = $customer->getInvoiceTemplate();
|
||||
}
|
||||
|
||||
if (null === $template) {
|
||||
throw new \Exception('Cannot create invoice model without template');
|
||||
}
|
||||
|
||||
// prevent that changes on the template will be persisted
|
||||
$this->invoiceRepository->preventTemplateUpdate($template);
|
||||
|
||||
if (null === $template->getLanguage()) {
|
||||
$template->setLanguage(Constants::DEFAULT_LOCALE);
|
||||
@trigger_error('Using invoice templates without a language is is deprecated and trigger and will throw an exception with 2.0', E_USER_DEPRECATED);
|
||||
}
|
||||
|
||||
$formatter = new DefaultInvoiceFormatter($this->formatter, $template->getLanguage());
|
||||
|
||||
$model = $this->invoiceModelFactory->createModel($formatter);
|
||||
$model
|
||||
->setCustomer($customer)
|
||||
->setTemplate($template)
|
||||
->setInvoiceDate($this->getDateTimeFactory($query)->createDateTime())
|
||||
->setQuery($query)
|
||||
;
|
||||
|
||||
if ($query->getInvoiceDate() !== null) {
|
||||
$model->setInvoiceDate($query->getInvoiceDate());
|
||||
}
|
||||
|
||||
if (null !== $query->getCurrentUser()) {
|
||||
$model->setUser($query->getCurrentUser());
|
||||
}
|
||||
|
||||
$model->setCustomer($query->getCustomers()[0]);
|
||||
|
||||
$generator = $this->getNumberGeneratorByName($query->getTemplate()->getNumberGenerator());
|
||||
$generator = $this->getNumberGeneratorByName($template->getNumberGenerator());
|
||||
if (null === $generator) {
|
||||
throw new \Exception('Unknown number generator: ' . $query->getTemplate()->getNumberGenerator());
|
||||
throw new \Exception('Unknown number generator: ' . $template->getNumberGenerator());
|
||||
}
|
||||
|
||||
$calculator = $this->getCalculatorByName($query->getTemplate()->getCalculator());
|
||||
$calculator = $this->getCalculatorByName($template->getCalculator());
|
||||
if (null === $calculator) {
|
||||
throw new \Exception('Unknown invoice calculator: ' . $query->getTemplate()->getCalculator());
|
||||
throw new \Exception('Unknown invoice calculator: ' . $template->getCalculator());
|
||||
}
|
||||
|
||||
$model->setCalculator($calculator);
|
||||
@@ -510,10 +435,10 @@ final class ServiceInvoice
|
||||
return $model;
|
||||
}
|
||||
|
||||
private function prepareModelQueryDates(InvoiceModel $model)
|
||||
private function prepareModelQueryDates(InvoiceModel $model): void
|
||||
{
|
||||
$begin = $model->getQuery()->getBegin();
|
||||
$end = $model->getQuery()->getEnd();
|
||||
$begin = $model->getQuery()?->getBegin();
|
||||
$end = $model->getQuery()?->getEnd();
|
||||
|
||||
if ($begin !== null && $end !== null) {
|
||||
return;
|
||||
@@ -566,6 +491,9 @@ final class ServiceInvoice
|
||||
|
||||
foreach ($items as $entry) {
|
||||
$customer = $entry->getProject()->getCustomer();
|
||||
if ($customer === null || !$customer->isVisible()) { // generating invoices for hidden customers does not yet work
|
||||
continue;
|
||||
}
|
||||
$id = $customer->getId();
|
||||
if (!\array_key_exists($id, $customerEntries)) {
|
||||
$customerEntries[$id] = [
|
||||
@@ -580,11 +508,9 @@ final class ServiceInvoice
|
||||
return [];
|
||||
}
|
||||
|
||||
uasort($customerEntries, function ($a, $b) {
|
||||
$customerA = $a['customer'] ?? null;
|
||||
$customerB = $b['customer'] ?? null;
|
||||
$nameA = ($customerA instanceof Customer) ? $customerA->getName() : null;
|
||||
$nameB = ($customerB instanceof Customer) ? $customerB->getName() : null;
|
||||
uasort($customerEntries, function ($a, $b): int {
|
||||
$nameA = $a['customer']->getName();
|
||||
$nameB = $b['customer']->getName();
|
||||
|
||||
if ($nameA === null && $nameB === null) {
|
||||
$result = 0;
|
||||
@@ -599,7 +525,7 @@ final class ServiceInvoice
|
||||
return $result;
|
||||
});
|
||||
|
||||
foreach ($customerEntries as $id => $settings) {
|
||||
foreach ($customerEntries as $settings) {
|
||||
$customerQuery = clone $query;
|
||||
$customerQuery->setCustomers([$settings['customer']]);
|
||||
$model = $this->createModelWithoutEntries($customerQuery);
|
||||
|
||||
Reference in New Issue
Block a user