Release 1.6.2 (#1289)
* include user teams in user entity * prevent unauthorized access via API * improve teamlead permission handling in team timesheets * add team data to user entity * add security tests * highlight menu for invoice template copy * unified handling of invoice data across all templates * access to the current users data in invoice templates * permission improvement in invoice form * allow to skip record rows * allow to add new invoice locations without overwriting the global ones * allow to order user preferences * change permission for normal users with access to view_other_timesheets * properly validate invoice template field length * allow to replace multiple variables in cell values text * upgraded office invoice template * doctrine deprecation fix * upgrade phpoffice/phpword * fix future begin check for default rounding rules * dashboard widget counter: respect visibility and teams - fixes #1161 * fix future begin check for default rounding rules * added new events for pre and post invoice rendering * fix permission issue for users without team seeing all records * prevent error in spreadsheet renderer for empty invoices
This commit is contained in:
@@ -13,6 +13,7 @@ use App\Entity\Timesheet;
|
||||
use App\Invoice\InvoiceItem;
|
||||
use App\Invoice\InvoiceItemInterface;
|
||||
use App\Invoice\InvoiceItemWithAmountInterface;
|
||||
use App\Invoice\InvoiceItemWithTypeInterface;
|
||||
|
||||
abstract class AbstractMergedCalculator extends AbstractCalculator
|
||||
{
|
||||
@@ -37,6 +38,25 @@ abstract class AbstractMergedCalculator extends AbstractCalculator
|
||||
if ($entry instanceof InvoiceItemWithAmountInterface) {
|
||||
$amount = $entry->getAmount();
|
||||
}
|
||||
|
||||
if ($entry instanceof InvoiceItemWithTypeInterface) {
|
||||
$type = $entry->getInvoiceType();
|
||||
$category = $entry->getInvoiceCategory();
|
||||
} else {
|
||||
$type = InvoiceItem::TYPE_TIMESHEET;
|
||||
$category = InvoiceItem::CATEGORY_TIMESHEET_WORK;
|
||||
}
|
||||
|
||||
if (null !== $invoiceItem->getType() && $type !== $invoiceItem->getType()) {
|
||||
$type = InvoiceItem::TYPE_MIXED;
|
||||
}
|
||||
if (null !== $invoiceItem->getCategory() && $category !== $invoiceItem->getCategory()) {
|
||||
$category = InvoiceItem::CATEGORY_MIXED;
|
||||
}
|
||||
|
||||
$invoiceItem->setType($type);
|
||||
$invoiceItem->setCategory($category);
|
||||
|
||||
$invoiceItem->setAmount($invoiceItem->getAmount() + $amount);
|
||||
$invoiceItem->setUser($entry->getUser());
|
||||
$invoiceItem->setRate($invoiceItem->getRate() + $entry->getRate());
|
||||
|
||||
104
src/Invoice/DefaultInvoiceFormatter.php
Normal file
104
src/Invoice/DefaultInvoiceFormatter.php
Normal file
@@ -0,0 +1,104 @@
|
||||
<?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\Twig\DateExtensions;
|
||||
use App\Twig\Extensions;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
final class DefaultInvoiceFormatter implements InvoiceFormatter
|
||||
{
|
||||
/**
|
||||
* @var DateExtensions
|
||||
*/
|
||||
private $dateExtension;
|
||||
|
||||
/**
|
||||
* @var Extensions
|
||||
*/
|
||||
private $extension;
|
||||
|
||||
/**
|
||||
* @var TranslatorInterface
|
||||
*/
|
||||
private $translator;
|
||||
|
||||
/**
|
||||
* @param TranslatorInterface $translator
|
||||
* @param DateExtensions $dateExtension
|
||||
* @param Extensions $extensions
|
||||
*/
|
||||
public function __construct(TranslatorInterface $translator, DateExtensions $dateExtension, Extensions $extensions)
|
||||
{
|
||||
$this->translator = $translator;
|
||||
$this->dateExtension = $dateExtension;
|
||||
$this->extension = $extensions;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \DateTime $date
|
||||
* @return mixed
|
||||
*/
|
||||
public function getFormattedDateTime(\DateTime $date)
|
||||
{
|
||||
return $this->dateExtension->dateShort($date);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \DateTime $date
|
||||
* @return mixed
|
||||
*/
|
||||
public function getFormattedTime(\DateTime $date)
|
||||
{
|
||||
return $this->dateExtension->time($date);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $amount
|
||||
* @param string $currency
|
||||
* @return string
|
||||
*/
|
||||
public function getFormattedMoney($amount, $currency)
|
||||
{
|
||||
return $this->extension->money($amount, $currency);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \DateTime $date
|
||||
* @return mixed
|
||||
*/
|
||||
public function getFormattedMonthName(\DateTime $date)
|
||||
{
|
||||
return $this->translator->trans($this->dateExtension->monthName($date));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $seconds
|
||||
* @return mixed
|
||||
*/
|
||||
public function getFormattedDuration($seconds)
|
||||
{
|
||||
return $this->extension->duration($seconds);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $seconds
|
||||
* @return mixed
|
||||
*/
|
||||
public function getFormattedDecimalDuration($seconds)
|
||||
{
|
||||
return $this->extension->durationDecimal($seconds);
|
||||
}
|
||||
|
||||
public function getCurrencySymbol(string $currency): string
|
||||
{
|
||||
return $this->extension->currency($currency);
|
||||
}
|
||||
}
|
||||
55
src/Invoice/InvoiceFormatter.php
Normal file
55
src/Invoice/InvoiceFormatter.php
Normal file
@@ -0,0 +1,55 @@
|
||||
<?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;
|
||||
|
||||
/**
|
||||
* @internal this is subject to change
|
||||
*/
|
||||
interface InvoiceFormatter
|
||||
{
|
||||
/**
|
||||
* @param \DateTime $date
|
||||
* @return mixed
|
||||
*/
|
||||
public function getFormattedDateTime(\DateTime $date);
|
||||
|
||||
/**
|
||||
* @param \DateTime $date
|
||||
* @return mixed
|
||||
*/
|
||||
public function getFormattedTime(\DateTime $date);
|
||||
|
||||
/**
|
||||
* @param int $amount
|
||||
* @param string|null $currency
|
||||
* @return mixed
|
||||
*/
|
||||
public function getFormattedMoney($amount, $currency);
|
||||
|
||||
/**
|
||||
* @param \DateTime $date
|
||||
* @return mixed
|
||||
*/
|
||||
public function getFormattedMonthName(\DateTime $date);
|
||||
|
||||
/**
|
||||
* @param int $seconds
|
||||
* @return mixed
|
||||
*/
|
||||
public function getFormattedDuration($seconds);
|
||||
|
||||
/**
|
||||
* @param int $seconds
|
||||
* @return mixed
|
||||
*/
|
||||
public function getFormattedDecimalDuration($seconds);
|
||||
|
||||
public function getCurrencySymbol(string $currency): string;
|
||||
}
|
||||
@@ -18,6 +18,11 @@ use App\Entity\User;
|
||||
*/
|
||||
final class InvoiceItem
|
||||
{
|
||||
public const TYPE_TIMESHEET = 'timesheet';
|
||||
public const CATEGORY_TIMESHEET_WORK = 'work';
|
||||
public const TYPE_MIXED = 'mixed';
|
||||
public const CATEGORY_MIXED = 'mixed';
|
||||
|
||||
/**
|
||||
* @var float
|
||||
*/
|
||||
@@ -66,6 +71,14 @@ final class InvoiceItem
|
||||
* @var array
|
||||
*/
|
||||
private $additionalFields = [];
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $type;
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $category;
|
||||
|
||||
public function addAdditionalField(string $name, ?string $value): InvoiceItem
|
||||
{
|
||||
@@ -215,4 +228,28 @@ final class InvoiceItem
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getType(): ?string
|
||||
{
|
||||
return $this->type;
|
||||
}
|
||||
|
||||
public function setType(string $type): InvoiceItem
|
||||
{
|
||||
$this->type = $type;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getCategory(): ?string
|
||||
{
|
||||
return $this->category;
|
||||
}
|
||||
|
||||
public function setCategory(string $category): InvoiceItem
|
||||
{
|
||||
$this->category = $category;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
||||
17
src/Invoice/InvoiceItemWithTypeInterface.php
Normal file
17
src/Invoice/InvoiceItemWithTypeInterface.php
Normal file
@@ -0,0 +1,17 @@
|
||||
<?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;
|
||||
|
||||
interface InvoiceItemWithTypeInterface
|
||||
{
|
||||
public function getInvoiceType(): string;
|
||||
|
||||
public function getInvoiceCategory(): string;
|
||||
}
|
||||
@@ -11,52 +11,57 @@ namespace App\Invoice;
|
||||
|
||||
use App\Entity\Customer;
|
||||
use App\Entity\InvoiceTemplate;
|
||||
use App\Entity\User;
|
||||
use App\Entity\UserPreference;
|
||||
use App\Repository\Query\InvoiceQuery;
|
||||
|
||||
/**
|
||||
* InvoiceModel is the ONLY value that a RendererInterface receives for generating the invoice,
|
||||
* besides the InvoiceDocument which is used as a "template".
|
||||
*/
|
||||
class InvoiceModel
|
||||
final class InvoiceModel
|
||||
{
|
||||
/**
|
||||
* @var Customer|null
|
||||
*/
|
||||
protected $customer;
|
||||
|
||||
private $customer;
|
||||
/**
|
||||
* @var InvoiceQuery
|
||||
*/
|
||||
protected $query;
|
||||
|
||||
private $query;
|
||||
/**
|
||||
* @var InvoiceItemInterface[]
|
||||
*/
|
||||
protected $entries = [];
|
||||
|
||||
private $entries = [];
|
||||
/**
|
||||
* @var InvoiceTemplate
|
||||
*/
|
||||
protected $template;
|
||||
|
||||
private $template;
|
||||
/**
|
||||
* @var CalculatorInterface
|
||||
*/
|
||||
protected $calculator;
|
||||
|
||||
private $calculator;
|
||||
/**
|
||||
* @var NumberGeneratorInterface
|
||||
*/
|
||||
protected $generator;
|
||||
|
||||
private $generator;
|
||||
/**
|
||||
* @var \DateTime
|
||||
*/
|
||||
protected $invoiceDate;
|
||||
private $invoiceDate;
|
||||
/**
|
||||
* @var User
|
||||
*/
|
||||
private $user;
|
||||
/**
|
||||
* @var InvoiceFormatter
|
||||
*/
|
||||
private $formatter;
|
||||
|
||||
public function __construct()
|
||||
public function __construct(InvoiceFormatter $formatter)
|
||||
{
|
||||
$this->invoiceDate = new \DateTime();
|
||||
$this->formatter = $formatter;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -183,4 +188,242 @@ class InvoiceModel
|
||||
{
|
||||
return $this->calculator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the user who is currently creating the invoice.
|
||||
*
|
||||
* @return User|null
|
||||
*/
|
||||
public function getUser(): ?User
|
||||
{
|
||||
return $this->user;
|
||||
}
|
||||
|
||||
public function setUser(User $user): InvoiceModel
|
||||
{
|
||||
$this->user = $user;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getFormatter(): ?InvoiceFormatter
|
||||
{
|
||||
return $this->formatter;
|
||||
}
|
||||
|
||||
public function toArray(): array
|
||||
{
|
||||
$model = $this;
|
||||
$customer = $model->getCustomer();
|
||||
$project = $model->getQuery()->getProject();
|
||||
$activity = $model->getQuery()->getActivity();
|
||||
$currency = $model->getCalculator()->getCurrency();
|
||||
$tax = $model->getCalculator()->getTax();
|
||||
$total = $model->getCalculator()->getTotal();
|
||||
$subtotal = $model->getCalculator()->getSubtotal();
|
||||
$formatter = $model->getFormatter();
|
||||
|
||||
$values = [
|
||||
'invoice.due_date' => $formatter->getFormattedDateTime($model->getDueDate()),
|
||||
'invoice.date' => $formatter->getFormattedDateTime($model->getInvoiceDate()),
|
||||
'invoice.number' => $model->getNumberGenerator()->getInvoiceNumber(),
|
||||
'invoice.currency' => $currency,
|
||||
'invoice.currency_symbol' => $formatter->getCurrencySymbol($currency),
|
||||
'invoice.vat' => $model->getCalculator()->getVat(),
|
||||
'invoice.tax' => $formatter->getFormattedMoney($tax, $currency),
|
||||
'invoice.tax_nc' => $formatter->getFormattedMoney($tax, null),
|
||||
'invoice.tax_plain' => $tax,
|
||||
'invoice.total_time' => $formatter->getFormattedDuration($model->getCalculator()->getTimeWorked()),
|
||||
'invoice.duration_decimal' => $formatter->getFormattedDecimalDuration($model->getCalculator()->getTimeWorked()),
|
||||
'invoice.total' => $formatter->getFormattedMoney($total, $currency),
|
||||
'invoice.total_nc' => $formatter->getFormattedMoney($total, null),
|
||||
'invoice.total_plain' => $total,
|
||||
'invoice.subtotal' => $formatter->getFormattedMoney($subtotal, $currency),
|
||||
'invoice.subtotal_nc' => $formatter->getFormattedMoney($subtotal, null),
|
||||
'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.due_days' => $model->getTemplate()->getDueDays(),
|
||||
'template.vat_id' => $model->getTemplate()->getVatId(),
|
||||
'template.contact' => $model->getTemplate()->getContact(),
|
||||
'template.payment_details' => $model->getTemplate()->getPaymentDetails(),
|
||||
|
||||
'query.begin' => $formatter->getFormattedDateTime($model->getQuery()->getBegin()),
|
||||
'query.day' => $model->getQuery()->getBegin()->format('d'),
|
||||
'query.end' => $formatter->getFormattedDateTime($model->getQuery()->getEnd()),
|
||||
'query.month' => $formatter->getFormattedMonthName($model->getQuery()->getBegin()),
|
||||
'query.month_number' => $model->getQuery()->getBegin()->format('m'),
|
||||
'query.year' => $model->getQuery()->getBegin()->format('Y'),
|
||||
];
|
||||
|
||||
if (null !== $model->getUser()) {
|
||||
$user = $model->getUser();
|
||||
|
||||
/** @var UserPreference $metaField */
|
||||
foreach ($user->getPreferences() as $metaField) {
|
||||
$values = array_merge($values, [
|
||||
'user.meta.' . $metaField->getName() => $metaField->getValue(),
|
||||
]);
|
||||
}
|
||||
|
||||
$values = array_merge($values, [
|
||||
'user.name' => $user->getUsername(),
|
||||
'user.email' => $user->getEmail(),
|
||||
'user.title' => $user->getTitle(),
|
||||
'user.alias' => $user->getAlias(),
|
||||
]);
|
||||
}
|
||||
|
||||
if (null !== $activity) {
|
||||
$values = array_merge($values, [
|
||||
'activity.id' => $activity->getId(),
|
||||
'activity.name' => $activity->getName(),
|
||||
'activity.comment' => $activity->getComment(),
|
||||
'activity.fixed_rate' => $formatter->getFormattedMoney($activity->getFixedRate(), $currency),
|
||||
'activity.fixed_rate_nc' => $formatter->getFormattedMoney($activity->getFixedRate(), null),
|
||||
'activity.fixed_rate_plain' => $activity->getFixedRate(),
|
||||
'activity.hourly_rate' => $formatter->getFormattedMoney($activity->getHourlyRate(), $currency),
|
||||
'activity.hourly_rate_nc' => $formatter->getFormattedMoney($activity->getHourlyRate(), null),
|
||||
'activity.hourly_rate_plain' => $activity->getHourlyRate(),
|
||||
]);
|
||||
|
||||
foreach ($activity->getVisibleMetaFields() as $metaField) {
|
||||
$values = array_merge($values, [
|
||||
'activity.meta.' . $metaField->getName() => $metaField->getValue(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
if (null !== $project) {
|
||||
$values = array_merge($values, [
|
||||
'project.id' => $project->getId(),
|
||||
'project.name' => $project->getName(),
|
||||
'project.comment' => $project->getComment(),
|
||||
'project.order_number' => $project->getOrderNumber(),
|
||||
'project.order_date' => null !== $project->getOrderDate() ? $formatter->getFormattedDateTime($project->getOrderDate()) : '',
|
||||
'project.fixed_rate' => $formatter->getFormattedMoney($project->getFixedRate(), $currency),
|
||||
'project.fixed_rate_nc' => $formatter->getFormattedMoney($project->getFixedRate(), null),
|
||||
'project.fixed_rate_plain' => $project->getFixedRate(),
|
||||
'project.hourly_rate' => $formatter->getFormattedMoney($project->getHourlyRate(), $currency),
|
||||
'project.hourly_rate_nc' => $formatter->getFormattedMoney($project->getHourlyRate(), null),
|
||||
'project.hourly_rate_plain' => $project->getHourlyRate(),
|
||||
]);
|
||||
|
||||
foreach ($project->getVisibleMetaFields() as $metaField) {
|
||||
$values = array_merge($values, [
|
||||
'project.meta.' . $metaField->getName() => $metaField->getValue(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
if (null !== $customer) {
|
||||
$values = array_merge($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.country' => $customer->getCountry(),
|
||||
'customer.homepage' => $customer->getHomepage(),
|
||||
'customer.comment' => $customer->getComment(),
|
||||
'customer.fixed_rate' => $formatter->getFormattedMoney($customer->getFixedRate(), $currency),
|
||||
'customer.fixed_rate_nc' => $formatter->getFormattedMoney($customer->getFixedRate(), null),
|
||||
'customer.fixed_rate_plain' => $customer->getFixedRate(),
|
||||
'customer.hourly_rate' => $formatter->getFormattedMoney($customer->getHourlyRate(), $currency),
|
||||
'customer.hourly_rate_nc' => $formatter->getFormattedMoney($customer->getHourlyRate(), null),
|
||||
'customer.hourly_rate_plain' => $customer->getHourlyRate(),
|
||||
]);
|
||||
|
||||
foreach ($customer->getVisibleMetaFields() as $metaField) {
|
||||
$values = array_merge($values, [
|
||||
'customer.meta.' . $metaField->getName() => $metaField->getValue(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
return $values;
|
||||
}
|
||||
|
||||
public function itemToArray(InvoiceItem $invoiceItem): array
|
||||
{
|
||||
$formatter = $this->getFormatter();
|
||||
|
||||
$rate = $invoiceItem->getRate();
|
||||
$appliedRate = $invoiceItem->getHourlyRate();
|
||||
$amount = $formatter->getFormattedDuration($invoiceItem->getDuration());
|
||||
$description = $invoiceItem->getDescription();
|
||||
|
||||
if ($invoiceItem->isFixedRate()) {
|
||||
$appliedRate = $invoiceItem->getFixedRate();
|
||||
$amount = $invoiceItem->getAmount();
|
||||
}
|
||||
|
||||
if (empty($description)) {
|
||||
$description = $invoiceItem->getActivity()->getName();
|
||||
}
|
||||
|
||||
$user = $invoiceItem->getUser();
|
||||
|
||||
// this should never happen!
|
||||
if (empty($appliedRate)) {
|
||||
$appliedRate = 0;
|
||||
}
|
||||
|
||||
$activity = $invoiceItem->getActivity();
|
||||
$project = $invoiceItem->getProject();
|
||||
$customer = $project->getCustomer();
|
||||
$currency = $customer->getCurrency();
|
||||
|
||||
$begin = $invoiceItem->getBegin();
|
||||
$end = $invoiceItem->getEnd();
|
||||
|
||||
$values = [
|
||||
'entry.row' => '',
|
||||
'entry.description' => $description,
|
||||
'entry.amount' => $amount,
|
||||
'entry.type' => $invoiceItem->getType(),
|
||||
'entry.category' => $invoiceItem->getCategory(),
|
||||
'entry.rate' => $formatter->getFormattedMoney($appliedRate, $currency),
|
||||
'entry.rate_nc' => $formatter->getFormattedMoney($appliedRate, null),
|
||||
'entry.rate_plain' => $appliedRate,
|
||||
'entry.total' => $formatter->getFormattedMoney($rate, $currency),
|
||||
'entry.total_nc' => $formatter->getFormattedMoney($rate, null),
|
||||
'entry.total_plain' => $rate,
|
||||
'entry.currency' => $currency,
|
||||
'entry.duration' => $invoiceItem->getDuration(),
|
||||
'entry.duration_decimal' => $formatter->getFormattedDecimalDuration($invoiceItem->getDuration()),
|
||||
'entry.duration_minutes' => number_format($invoiceItem->getDuration() / 60),
|
||||
'entry.begin' => $formatter->getFormattedDateTime($begin),
|
||||
'entry.begin_time' => $formatter->getFormattedTime($begin),
|
||||
'entry.begin_timestamp' => $begin->getTimestamp(),
|
||||
'entry.end' => $formatter->getFormattedDateTime($end),
|
||||
'entry.end_time' => $formatter->getFormattedTime($end),
|
||||
'entry.end_timestamp' => $end->getTimestamp(),
|
||||
'entry.date' => $formatter->getFormattedDateTime($begin),
|
||||
'entry.user_id' => $user->getId(),
|
||||
'entry.user_name' => $user->getUsername(),
|
||||
'entry.user_title' => $user->getTitle(),
|
||||
'entry.user_alias' => $user->getAlias(),
|
||||
'entry.activity' => $activity->getName(),
|
||||
'entry.activity_id' => $activity->getId(),
|
||||
'entry.project' => $project->getName(),
|
||||
'entry.project_id' => $project->getId(),
|
||||
'entry.customer' => $customer->getName(),
|
||||
'entry.customer_id' => $customer->getId(),
|
||||
];
|
||||
|
||||
foreach ($invoiceItem->getAdditionalFields() as $name => $value) {
|
||||
$values = array_merge($values, [
|
||||
'entry.meta.' . $name => $value,
|
||||
]);
|
||||
}
|
||||
|
||||
return $values;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,93 +9,54 @@
|
||||
|
||||
namespace App\Invoice\Renderer;
|
||||
|
||||
use App\Twig\DateExtensions;
|
||||
use App\Twig\Extensions;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
use App\Entity\InvoiceDocument;
|
||||
use Symfony\Component\HttpFoundation\BinaryFileResponse;
|
||||
use Symfony\Component\HttpFoundation\ResponseHeaderBag;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
abstract class AbstractRenderer
|
||||
{
|
||||
use RendererTrait;
|
||||
|
||||
/**
|
||||
* @var DateExtensions
|
||||
* @return string[]
|
||||
*/
|
||||
protected $dateExtension;
|
||||
abstract protected function getFileExtensions();
|
||||
|
||||
/**
|
||||
* @var Extensions
|
||||
*/
|
||||
protected $extension;
|
||||
|
||||
/**
|
||||
* @var TranslatorInterface
|
||||
*/
|
||||
protected $translator;
|
||||
|
||||
/**
|
||||
* @param TranslatorInterface $translator
|
||||
* @param DateExtensions $dateExtension
|
||||
* @param Extensions $extensions
|
||||
*/
|
||||
public function __construct(TranslatorInterface $translator, DateExtensions $dateExtension, Extensions $extensions)
|
||||
{
|
||||
$this->translator = $translator;
|
||||
$this->dateExtension = $dateExtension;
|
||||
$this->extension = $extensions;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \DateTime $date
|
||||
* @return mixed
|
||||
*/
|
||||
protected function getFormattedDateTime(\DateTime $date)
|
||||
{
|
||||
return $this->dateExtension->dateShort($date);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \DateTime $date
|
||||
* @return mixed
|
||||
*/
|
||||
protected function getFormattedTime(\DateTime $date)
|
||||
{
|
||||
return $this->dateExtension->time($date);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $amount
|
||||
* @param string $currency
|
||||
* @return string
|
||||
*/
|
||||
protected function getFormattedMoney($amount, $currency)
|
||||
abstract protected function getContentType();
|
||||
|
||||
/**
|
||||
* @param InvoiceDocument $document
|
||||
* @return bool
|
||||
*/
|
||||
public function supports(InvoiceDocument $document): bool
|
||||
{
|
||||
return $this->extension->money($amount, $currency);
|
||||
foreach ($this->getFileExtensions() as $extension) {
|
||||
if (stripos($document->getFilename(), $extension) !== false) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \DateTime $date
|
||||
* @return mixed
|
||||
* @param mixed $file
|
||||
* @param string $filename
|
||||
* @return BinaryFileResponse
|
||||
*/
|
||||
protected function getFormattedMonthName(\DateTime $date)
|
||||
protected function getFileResponse($file, $filename)
|
||||
{
|
||||
return $this->translator->trans($this->dateExtension->monthName($date));
|
||||
}
|
||||
$response = new BinaryFileResponse($file);
|
||||
$disposition = $response->headers->makeDisposition(ResponseHeaderBag::DISPOSITION_ATTACHMENT, $filename);
|
||||
|
||||
/**
|
||||
* @param int $seconds
|
||||
* @return mixed
|
||||
*/
|
||||
protected function getFormattedDuration($seconds)
|
||||
{
|
||||
return $this->extension->duration($seconds);
|
||||
}
|
||||
$response->headers->set('Content-Type', $this->getContentType());
|
||||
$response->headers->set('Content-Disposition', $disposition);
|
||||
$response->deleteFileAfterSend(true);
|
||||
|
||||
/**
|
||||
* @param int $seconds
|
||||
* @return mixed
|
||||
*/
|
||||
protected function getFormattedDecimalDuration($seconds)
|
||||
{
|
||||
return $this->extension->durationDecimal($seconds);
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,9 @@ use PhpOffice\PhpSpreadsheet\Spreadsheet;
|
||||
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
abstract class AbstractSpreadsheetRenderer extends AbstractRenderer
|
||||
{
|
||||
/**
|
||||
@@ -40,7 +43,7 @@ abstract class AbstractSpreadsheetRenderer extends AbstractRenderer
|
||||
$spreadsheet = IOFactory::load($document->getFilename());
|
||||
$worksheet = $spreadsheet->getActiveSheet();
|
||||
$entries = $model->getCalculator()->getEntries();
|
||||
$replacer = $this->modelToReplacer($model);
|
||||
$sheetReplacer = $model->toArray();
|
||||
$invoiceItemCount = count($entries);
|
||||
if ($invoiceItemCount > 1) {
|
||||
$this->addTemplateRows($worksheet, $invoiceItemCount);
|
||||
@@ -57,26 +60,37 @@ abstract class AbstractSpreadsheetRenderer extends AbstractRenderer
|
||||
$entryRow = 0;
|
||||
|
||||
foreach ($worksheet->getRowIterator() as $row) {
|
||||
$invoiceItem = $entries[$entryRow];
|
||||
$sheetValues = false;
|
||||
foreach ($row->getCellIterator() as $cell) {
|
||||
$value = $cell->getValue();
|
||||
if (stripos($value, '${entry.') !== false) {
|
||||
if ($sheetValues === false) {
|
||||
$sheetValues = $this->invoiceItemToArray($invoiceItem);
|
||||
}
|
||||
$searcher = str_replace('${', '', $value);
|
||||
$searcher = str_replace('}', '', $searcher);
|
||||
if (isset($sheetValues[$searcher])) {
|
||||
$cell->setValue($sheetValues[$searcher]);
|
||||
}
|
||||
} elseif (stripos($value, '${') !== false) {
|
||||
$searcher = str_replace('${', '', $value);
|
||||
$searcher = str_replace('}', '', $searcher);
|
||||
if (isset($replacer[$searcher])) {
|
||||
$cell->setValue($replacer[$searcher]);
|
||||
}
|
||||
$replacer = null;
|
||||
if (stripos($value, '${') === false) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (stripos($value, '${entry.') !== false) {
|
||||
if ($sheetValues === false && isset($entries[$entryRow])) {
|
||||
$sheetValues = $model->itemToArray($entries[$entryRow]);
|
||||
}
|
||||
$replacer = $sheetValues;
|
||||
} elseif (stripos($value, '${') !== false) {
|
||||
$replacer = $sheetReplacer;
|
||||
}
|
||||
|
||||
if (empty($replacer)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// we can have mixed cell content, which makes it much more complicated
|
||||
foreach ($replacer as $key => $content) {
|
||||
$searchKey = '${' . $key . '}';
|
||||
if (stripos($value, $searchKey) === false) {
|
||||
continue;
|
||||
}
|
||||
$value = str_replace($searchKey, $content, $value);
|
||||
}
|
||||
|
||||
$cell->setValue($value);
|
||||
}
|
||||
|
||||
if ($sheetValues !== false && $entryRow < $invoiceItemCount - 1) {
|
||||
@@ -105,7 +119,7 @@ abstract class AbstractSpreadsheetRenderer extends AbstractRenderer
|
||||
$value = $cell->getValue();
|
||||
if (stripos($value, '${entry.') !== false) {
|
||||
$startRow = $row->getRowIndex();
|
||||
$worksheet->insertNewRowBefore($row->getRowIndex(), $invoiceItemCount - 1);
|
||||
$worksheet->insertNewRowBefore($startRow + 1, $invoiceItemCount - 1);
|
||||
break 2;
|
||||
}
|
||||
|
||||
@@ -123,15 +137,18 @@ abstract class AbstractSpreadsheetRenderer extends AbstractRenderer
|
||||
throw new \Exception('Invalid invoice document, no template row found.');
|
||||
}
|
||||
|
||||
// fill up all new rows with template values
|
||||
$templateRow = $invoiceItemCount + $startRow;
|
||||
$iterator = $worksheet->getRowIterator($templateRow - 1, $templateRow);
|
||||
// fill up all new rows with template replacer
|
||||
$templateRow = $startRow;
|
||||
$iterator = $worksheet->getRowIterator($templateRow, $templateRow + 1);
|
||||
|
||||
$templateColumns = [];
|
||||
foreach ($iterator->current()->getCellIterator() as $cell) {
|
||||
|
||||
$tmpRow = $iterator->current();
|
||||
foreach ($tmpRow->getCellIterator() as $cell) {
|
||||
$templateColumns[$cell->getColumn()] = $cell->getValue();
|
||||
}
|
||||
|
||||
$iterator = $worksheet->getRowIterator($startRow, $templateRow - 2);
|
||||
$iterator = $worksheet->getRowIterator($startRow, $startRow + $invoiceItemCount - 1);
|
||||
foreach ($iterator as $row) {
|
||||
foreach ($row->getCellIterator() as $cell) {
|
||||
$cell->setValue($templateColumns[$cell->getColumn()]);
|
||||
|
||||
@@ -13,7 +13,7 @@ use App\Invoice\RendererInterface;
|
||||
use PhpOffice\PhpSpreadsheet\IOFactory;
|
||||
use PhpOffice\PhpSpreadsheet\Spreadsheet;
|
||||
|
||||
class CsvRenderer extends AbstractSpreadsheetRenderer implements RendererInterface
|
||||
final class CsvRenderer extends AbstractSpreadsheetRenderer implements RendererInterface
|
||||
{
|
||||
/**
|
||||
* @return string[]
|
||||
|
||||
@@ -19,7 +19,7 @@ use PhpOffice\PhpWord\TemplateProcessor;
|
||||
use Symfony\Component\HttpFoundation\File\Stream;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class DocxRenderer extends AbstractRenderer implements RendererInterface
|
||||
final class DocxRenderer extends AbstractRenderer implements RendererInterface
|
||||
{
|
||||
/**
|
||||
* @param InvoiceDocument $document
|
||||
@@ -35,7 +35,7 @@ class DocxRenderer extends AbstractRenderer implements RendererInterface
|
||||
$xmlEscaper = new Xml();
|
||||
$template = new TemplateProcessor($document->getFilename());
|
||||
|
||||
foreach ($this->modelToReplacer($model) as $search => $replace) {
|
||||
foreach ($model->toArray() as $search => $replace) {
|
||||
$replace = $xmlEscaper->escape($replace);
|
||||
$replace = str_replace(PHP_EOL, '</w:t><w:br /><w:t xml:space="preserve">', $replace);
|
||||
|
||||
@@ -45,12 +45,18 @@ class DocxRenderer extends AbstractRenderer implements RendererInterface
|
||||
try {
|
||||
$template->cloneRow('entry.description', count($model->getCalculator()->getEntries()));
|
||||
} catch (OfficeException $ex) {
|
||||
$template->cloneRow('entry.row', count($model->getCalculator()->getEntries()));
|
||||
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())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$i = 1;
|
||||
foreach ($model->getCalculator()->getEntries() as $entry) {
|
||||
$values = $this->invoiceItemToArray($entry);
|
||||
$values = $model->itemToArray($entry);
|
||||
foreach ($values as $search => $replace) {
|
||||
$replace = $xmlEscaper->escape($replace);
|
||||
$replace = str_replace(PHP_EOL, '</w:t><w:br /><w:t xml:space="preserve">', $replace);
|
||||
|
||||
@@ -13,7 +13,7 @@ use App\Invoice\RendererInterface;
|
||||
use PhpOffice\PhpSpreadsheet\IOFactory;
|
||||
use PhpOffice\PhpSpreadsheet\Spreadsheet;
|
||||
|
||||
class OdsRenderer extends AbstractSpreadsheetRenderer implements RendererInterface
|
||||
final class OdsRenderer extends AbstractSpreadsheetRenderer implements RendererInterface
|
||||
{
|
||||
/**
|
||||
* @return string[]
|
||||
|
||||
@@ -9,181 +9,28 @@
|
||||
|
||||
namespace App\Invoice\Renderer;
|
||||
|
||||
use App\Entity\InvoiceDocument;
|
||||
use App\Invoice\InvoiceItem;
|
||||
use App\Invoice\InvoiceModel;
|
||||
use Symfony\Component\HttpFoundation\BinaryFileResponse;
|
||||
use Symfony\Component\HttpFoundation\ResponseHeaderBag;
|
||||
|
||||
trait RendererTrait
|
||||
{
|
||||
/**
|
||||
* @return string[]
|
||||
* @var InvoiceModel
|
||||
*/
|
||||
abstract protected function getFileExtensions();
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
abstract protected function getContentType();
|
||||
|
||||
/**
|
||||
* @param InvoiceDocument $document
|
||||
* @return bool
|
||||
*/
|
||||
public function supports(InvoiceDocument $document): bool
|
||||
{
|
||||
foreach ($this->getFileExtensions() as $extension) {
|
||||
if (stripos($document->getFilename(), $extension) !== false) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \DateTime $date
|
||||
* @return mixed
|
||||
*/
|
||||
abstract protected function getFormattedDateTime(\DateTime $date);
|
||||
|
||||
/**
|
||||
* @param \DateTime $date
|
||||
* @return mixed
|
||||
*/
|
||||
abstract protected function getFormattedTime(\DateTime $date);
|
||||
|
||||
/**
|
||||
* @param int $amount
|
||||
* @param string|null $currency
|
||||
* @return mixed
|
||||
*/
|
||||
abstract protected function getFormattedMoney($amount, $currency);
|
||||
|
||||
/**
|
||||
* @param \DateTime $date
|
||||
* @return mixed
|
||||
*/
|
||||
abstract protected function getFormattedMonthName(\DateTime $date);
|
||||
|
||||
/**
|
||||
* @param int $seconds
|
||||
* @return mixed
|
||||
*/
|
||||
abstract protected function getFormattedDuration($seconds);
|
||||
|
||||
/**
|
||||
* @param int $seconds
|
||||
* @return mixed
|
||||
*/
|
||||
abstract protected function getFormattedDecimalDuration($seconds);
|
||||
private $model;
|
||||
|
||||
/**
|
||||
* @deprecated since 1.6.2 - will be removed with 2.0
|
||||
* @param InvoiceModel $model
|
||||
* @return array
|
||||
*/
|
||||
protected function modelToReplacer(InvoiceModel $model)
|
||||
{
|
||||
$customer = $model->getCustomer();
|
||||
$project = $model->getQuery()->getProject();
|
||||
$activity = $model->getQuery()->getActivity();
|
||||
$currency = $model->getCalculator()->getCurrency();
|
||||
$tax = $model->getCalculator()->getTax();
|
||||
$total = $model->getCalculator()->getTotal();
|
||||
$subtotal = $model->getCalculator()->getSubtotal();
|
||||
@trigger_error('modelToReplacer() is deprecated and will be removed with 2.0', E_USER_DEPRECATED);
|
||||
|
||||
$values = [
|
||||
'invoice.due_date' => $this->getFormattedDateTime($model->getDueDate()),
|
||||
'invoice.date' => $this->getFormattedDateTime($model->getInvoiceDate()),
|
||||
'invoice.number' => $model->getNumberGenerator()->getInvoiceNumber(),
|
||||
'invoice.currency' => $currency,
|
||||
'invoice.vat' => $model->getCalculator()->getVat(),
|
||||
'invoice.tax' => $this->getFormattedMoney($tax, $currency),
|
||||
'invoice.tax_nc' => $this->getFormattedMoney($tax, null),
|
||||
'invoice.total_time' => $this->getFormattedDuration($model->getCalculator()->getTimeWorked()),
|
||||
'invoice.duration_decimal' => $this->getFormattedDecimalDuration($model->getCalculator()->getTimeWorked()),
|
||||
'invoice.total' => $this->getFormattedMoney($total, $currency),
|
||||
'invoice.total_nc' => $this->getFormattedMoney($total, null),
|
||||
'invoice.subtotal' => $this->getFormattedMoney($subtotal, $currency),
|
||||
'invoice.subtotal_nc' => $this->getFormattedMoney($subtotal, null),
|
||||
$this->model = $model;
|
||||
|
||||
'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(),
|
||||
|
||||
'query.begin' => $this->getFormattedDateTime($model->getQuery()->getBegin()),
|
||||
'query.day' => $model->getQuery()->getBegin()->format('d'),
|
||||
'query.end' => $this->getFormattedDateTime($model->getQuery()->getEnd()),
|
||||
'query.month' => $this->getFormattedMonthName($model->getQuery()->getBegin()),
|
||||
'query.month_number' => $model->getQuery()->getBegin()->format('m'),
|
||||
'query.year' => $model->getQuery()->getBegin()->format('Y'),
|
||||
];
|
||||
|
||||
if (null !== $activity) {
|
||||
$values = array_merge($values, [
|
||||
'activity.id' => $activity->getId(),
|
||||
'activity.name' => $activity->getName(),
|
||||
'activity.comment' => $activity->getComment(),
|
||||
'activity.fixed_rate' => $activity->getFixedRate(),
|
||||
'activity.hourly_rate' => $activity->getHourlyRate(),
|
||||
]);
|
||||
|
||||
foreach ($activity->getVisibleMetaFields() as $metaField) {
|
||||
$values = array_merge($values, [
|
||||
'activity.meta.' . $metaField->getName() => $metaField->getValue(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
if (null !== $project) {
|
||||
$values = array_merge($values, [
|
||||
'project.id' => $project->getId(),
|
||||
'project.name' => $project->getName(),
|
||||
'project.comment' => $project->getComment(),
|
||||
'project.order_number' => $project->getOrderNumber(),
|
||||
'project.order_date' => null !== $project->getOrderDate() ? $this->getFormattedDateTime($project->getOrderDate()) : '',
|
||||
'project.fixed_rate' => $project->getFixedRate(),
|
||||
'project.hourly_rate' => $project->getHourlyRate(),
|
||||
]);
|
||||
|
||||
foreach ($project->getVisibleMetaFields() as $metaField) {
|
||||
$values = array_merge($values, [
|
||||
'project.meta.' . $metaField->getName() => $metaField->getValue(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
if (null !== $customer) {
|
||||
$values = array_merge($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.country' => $customer->getCountry(),
|
||||
'customer.homepage' => $customer->getHomepage(),
|
||||
'customer.comment' => $customer->getComment(),
|
||||
'customer.fixed_rate' => $customer->getFixedRate(),
|
||||
'customer.hourly_rate' => $customer->getHourlyRate(),
|
||||
]);
|
||||
|
||||
foreach ($customer->getVisibleMetaFields() as $metaField) {
|
||||
$values = array_merge($values, [
|
||||
'customer.meta.' . $metaField->getName() => $metaField->getValue(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
return $values;
|
||||
return $model->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -193,94 +40,18 @@ trait RendererTrait
|
||||
{
|
||||
@trigger_error('timesheetToArray() is deprecated and will be removed with 2.0', E_USER_DEPRECATED);
|
||||
|
||||
return $this->invoiceItemToArray($invoiceItem);
|
||||
}
|
||||
|
||||
protected function invoiceItemToArray(InvoiceItem $invoiceItem): array
|
||||
{
|
||||
$rate = $invoiceItem->getRate();
|
||||
$hourlyRate = $invoiceItem->getHourlyRate();
|
||||
$amount = $this->getFormattedDuration($invoiceItem->getDuration());
|
||||
$description = $invoiceItem->getDescription();
|
||||
|
||||
if ($invoiceItem->isFixedRate()) {
|
||||
$hourlyRate = $invoiceItem->getFixedRate();
|
||||
$amount = $invoiceItem->getAmount();
|
||||
}
|
||||
|
||||
if (empty($description)) {
|
||||
$description = $invoiceItem->getActivity()->getName();
|
||||
}
|
||||
|
||||
$user = $invoiceItem->getUser();
|
||||
|
||||
// this should never happen!
|
||||
if (empty($hourlyRate)) {
|
||||
$hourlyRate = 0;
|
||||
}
|
||||
|
||||
$activity = $invoiceItem->getActivity();
|
||||
$project = $invoiceItem->getProject();
|
||||
$customer = $project->getCustomer();
|
||||
$currency = $customer->getCurrency();
|
||||
|
||||
$begin = $invoiceItem->getBegin();
|
||||
$end = $invoiceItem->getEnd();
|
||||
|
||||
$values = [
|
||||
'entry.row' => '',
|
||||
'entry.description' => $description,
|
||||
'entry.amount' => $amount,
|
||||
'entry.rate' => $this->getFormattedMoney($hourlyRate, $currency),
|
||||
'entry.rate_nc' => $this->getFormattedMoney($hourlyRate, null),
|
||||
'entry.total' => $this->getFormattedMoney($rate, $currency),
|
||||
'entry.total_nc' => $this->getFormattedMoney($rate, null),
|
||||
'entry.currency' => $currency,
|
||||
'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(),
|
||||
'entry.end' => $this->getFormattedDateTime($end),
|
||||
'entry.end_time' => $this->getFormattedTime($end),
|
||||
'entry.end_timestamp' => $end->getTimestamp(),
|
||||
'entry.date' => $this->getFormattedDateTime($begin),
|
||||
'entry.user_id' => $user->getId(),
|
||||
'entry.user_name' => $user->getUsername(),
|
||||
'entry.user_title' => $user->getTitle(),
|
||||
'entry.user_alias' => $user->getAlias(),
|
||||
'entry.activity' => $activity->getName(),
|
||||
'entry.activity_id' => $activity->getId(),
|
||||
'entry.project' => $project->getName(),
|
||||
'entry.project_id' => $project->getId(),
|
||||
'entry.customer' => $customer->getName(),
|
||||
'entry.customer_id' => $customer->getId(),
|
||||
];
|
||||
|
||||
foreach ($invoiceItem->getAdditionalFields() as $name => $value) {
|
||||
$values = array_merge($values, [
|
||||
'entry.meta.' . $name => $value,
|
||||
]);
|
||||
}
|
||||
|
||||
return $values;
|
||||
return $this->model->itemToArray($invoiceItem);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $file
|
||||
* @param string $filename
|
||||
* @return BinaryFileResponse
|
||||
* @deprecated since 1.6.2 - will be removed with 2.0
|
||||
* @param InvoiceItem $invoiceItem
|
||||
* @return array
|
||||
*/
|
||||
protected function getFileResponse($file, $filename)
|
||||
protected function invoiceItemToArray(InvoiceItem $invoiceItem): array
|
||||
{
|
||||
$response = new BinaryFileResponse($file);
|
||||
$disposition = $response->headers->makeDisposition(ResponseHeaderBag::DISPOSITION_ATTACHMENT, $filename);
|
||||
@trigger_error('invoiceItemToArray() is deprecated and will be removed with 2.0', E_USER_DEPRECATED);
|
||||
|
||||
$response->headers->set('Content-Type', $this->getContentType());
|
||||
$response->headers->set('Content-Disposition', $disposition);
|
||||
$response->deleteFileAfterSend(true);
|
||||
|
||||
return $response;
|
||||
return $this->model->itemToArray($invoiceItem);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ use App\Invoice\RendererInterface;
|
||||
use PhpOffice\PhpSpreadsheet\IOFactory;
|
||||
use PhpOffice\PhpSpreadsheet\Spreadsheet;
|
||||
|
||||
class XlsxRenderer extends AbstractSpreadsheetRenderer implements RendererInterface
|
||||
final class XlsxRenderer extends AbstractSpreadsheetRenderer implements RendererInterface
|
||||
{
|
||||
/**
|
||||
* @return string[]
|
||||
|
||||
Reference in New Issue
Block a user