create multiple invoices at once (#2465)
This commit is contained in:
@@ -90,7 +90,9 @@ final class ServiceInvoice
|
||||
{
|
||||
foreach ($this->getNumberGenerator() as $generator) {
|
||||
if ($generator->getId() === $name) {
|
||||
return $generator;
|
||||
// several models can co-exist at the same time and NumberGeneratorInterface works
|
||||
// with setModel() instead of __construct() - returning the same instance would lead to bugs!
|
||||
return clone $generator;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,7 +118,9 @@ final class ServiceInvoice
|
||||
{
|
||||
foreach ($this->getCalculator() as $calculator) {
|
||||
if ($calculator->getId() === $name) {
|
||||
return $calculator;
|
||||
// several models can co-exist at the same time and CalculatorInterface works
|
||||
// with setModel() instead of __construct() - returning the same instance would lead to bugs!
|
||||
return clone $calculator;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -258,50 +262,33 @@ final class ServiceInvoice
|
||||
|
||||
/**
|
||||
* @param InvoiceQuery $query
|
||||
* @return array<string, InvoiceItemInterface[]>
|
||||
* @return InvoiceItemInterface[]
|
||||
*/
|
||||
private function findInvoiceItemsWithRepository(InvoiceQuery $query): array
|
||||
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 [];
|
||||
}
|
||||
|
||||
$factory = $this->getDateTimeFactory($query);
|
||||
|
||||
if (null === $query->getBegin()) {
|
||||
$query->setBegin($factory->getStartOfMonth());
|
||||
}
|
||||
if (null === $query->getEnd()) {
|
||||
$query->setEnd($factory->getEndOfMonth());
|
||||
}
|
||||
$query->getBegin()->setTime(0, 0, 0);
|
||||
$query->getEnd()->setTime(23, 59, 59);
|
||||
|
||||
$repositories = $this->getInvoiceItemRepositories();
|
||||
$items = [];
|
||||
|
||||
foreach ($repositories as $repository) {
|
||||
$items[\get_class($repository)] = $repository->getInvoiceItemsForQuery($query);
|
||||
}
|
||||
|
||||
return $items;
|
||||
return $this->getInvoiceItems($query);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param InvoiceQuery $query
|
||||
* @return InvoiceItemInterface[]
|
||||
*/
|
||||
public function findInvoiceItems(InvoiceQuery $query): array
|
||||
public function getInvoiceItems(InvoiceQuery $query): array
|
||||
{
|
||||
$entries = [];
|
||||
$temp = $this->findInvoiceItemsWithRepository($query);
|
||||
$items = [];
|
||||
|
||||
foreach ($temp as $repo => $items) {
|
||||
$entries = array_merge($entries, $items);
|
||||
foreach ($this->getInvoiceItemRepositories() as $repository) {
|
||||
$items = array_merge($items, $repository->getInvoiceItemsForQuery($query));
|
||||
}
|
||||
|
||||
return $entries;
|
||||
return $items;
|
||||
}
|
||||
|
||||
private function getDateTimeFactory(InvoiceQuery $query): DateTimeFactory
|
||||
@@ -316,29 +303,17 @@ final class ServiceInvoice
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, InvoiceItemInterface[]> $entries
|
||||
* @param InvoiceItemInterface[] $entries
|
||||
*/
|
||||
private function markEntriesAsExported(iterable $entries)
|
||||
private function markEntriesAsExported(array $entries)
|
||||
{
|
||||
$repositories = $this->getInvoiceItemRepositories();
|
||||
|
||||
foreach ($entries as $repo => $items) {
|
||||
foreach ($repositories as $repository) {
|
||||
if (\get_class($repository) === $repo) {
|
||||
$repository->setExported($items);
|
||||
}
|
||||
}
|
||||
foreach ($this->getInvoiceItemRepositories() as $repository) {
|
||||
$repository->setExported($entries);
|
||||
}
|
||||
}
|
||||
|
||||
public function renderInvoice(InvoiceQuery $query, EventDispatcherInterface $dispatcher): Response
|
||||
public function renderInvoiceWithModel(InvoiceModel $model, EventDispatcherInterface $dispatcher): Response
|
||||
{
|
||||
$entries = $this->findInvoiceItemsWithRepository($query);
|
||||
$model = $this->createModel($query);
|
||||
foreach ($entries as $repo => $items) {
|
||||
$model->addEntries($items);
|
||||
}
|
||||
|
||||
$document = $this->getDocumentByName($model->getTemplate()->getRenderer());
|
||||
if (null === $document) {
|
||||
throw new \Exception('Unknown invoice document: ' . $model->getTemplate()->getRenderer());
|
||||
@@ -361,20 +336,21 @@ final class ServiceInvoice
|
||||
);
|
||||
}
|
||||
|
||||
public function renderInvoice(InvoiceQuery $query, EventDispatcherInterface $dispatcher): Response
|
||||
{
|
||||
$model = $this->createModel($query);
|
||||
|
||||
return $this->renderInvoiceWithModel($model, $dispatcher);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param InvoiceQuery $query
|
||||
* @param InvoiceModel $model
|
||||
* @param EventDispatcherInterface $dispatcher
|
||||
* @return Invoice
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function createInvoice(InvoiceQuery $query, EventDispatcherInterface $dispatcher): Invoice
|
||||
public function createInvoiceFromModel(InvoiceModel $model, EventDispatcherInterface $dispatcher): Invoice
|
||||
{
|
||||
$entries = $this->findInvoiceItemsWithRepository($query);
|
||||
$model = $this->createModel($query);
|
||||
foreach ($entries as $repo => $items) {
|
||||
$model->addEntries($items);
|
||||
}
|
||||
|
||||
$document = $this->getDocumentByName($model->getTemplate()->getRenderer());
|
||||
if (null === $document) {
|
||||
throw new \Exception('Unknown invoice document: ' . $model->getTemplate()->getRenderer());
|
||||
@@ -386,8 +362,8 @@ final class ServiceInvoice
|
||||
|
||||
$response = $renderer->render($document, $model);
|
||||
|
||||
if ($query->isMarkAsExported()) {
|
||||
$this->markEntriesAsExported($entries);
|
||||
if ($model->getQuery()->isMarkAsExported()) {
|
||||
$this->markEntriesAsExported($model->getEntries());
|
||||
}
|
||||
|
||||
$event = new InvoicePostRenderEvent($model, $document, $renderer, $response);
|
||||
@@ -411,6 +387,37 @@ 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)
|
||||
{
|
||||
$invoiceDirectory = $this->getInvoicesDirectory();
|
||||
@@ -426,9 +433,23 @@ final class ServiceInvoice
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function createModel(InvoiceQuery $query): InvoiceModel
|
||||
{
|
||||
$model = $this->createModelWithoutEntries($query);
|
||||
$model->addEntries($this->getInvoiceItems($query));
|
||||
|
||||
$this->prepareModelQueryDates($model);
|
||||
|
||||
return $model;
|
||||
}
|
||||
|
||||
private function createModelWithoutEntries(InvoiceQuery $query): InvoiceModel
|
||||
{
|
||||
$template = $query->getTemplate();
|
||||
|
||||
if (!$query->hasCustomers()) {
|
||||
throw new \Exception('Cannot create invoice model without customer');
|
||||
}
|
||||
|
||||
if (null === $template) {
|
||||
throw new \Exception('Cannot create invoice model without template');
|
||||
}
|
||||
@@ -449,9 +470,7 @@ final class ServiceInvoice
|
||||
$model->setUser($query->getCurrentUser());
|
||||
}
|
||||
|
||||
if ($query->hasCustomers()) {
|
||||
$model->setCustomer($query->getCustomers()[0]);
|
||||
}
|
||||
$model->setCustomer($query->getCustomers()[0]);
|
||||
|
||||
$generator = $this->getNumberGeneratorByName($query->getTemplate()->getNumberGenerator());
|
||||
if (null === $generator) {
|
||||
@@ -468,4 +487,95 @@ final class ServiceInvoice
|
||||
|
||||
return $model;
|
||||
}
|
||||
|
||||
private function prepareModelQueryDates(InvoiceModel $model)
|
||||
{
|
||||
$begin = $model->getQuery()->getBegin();
|
||||
$end = $model->getQuery()->getEnd();
|
||||
|
||||
if ($begin !== null && $end !== null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (\count($model->getEntries()) === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
$tmpBegin = null;
|
||||
$tmpEnd = null;
|
||||
|
||||
foreach ($model->getEntries() as $entry) {
|
||||
if ($begin === null) {
|
||||
if ($tmpBegin === null) {
|
||||
$tmpBegin = $entry->getBegin();
|
||||
} else {
|
||||
$tmpBegin = min($entry->getBegin(), $tmpBegin);
|
||||
}
|
||||
}
|
||||
|
||||
if ($end === null) {
|
||||
if ($tmpEnd === null) {
|
||||
$tmpEnd = $entry->getEnd();
|
||||
} else {
|
||||
$tmpEnd = max($entry->getEnd(), $tmpEnd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($begin === null && $tmpBegin !== null) {
|
||||
$model->getQuery()->setBegin($tmpBegin);
|
||||
}
|
||||
|
||||
if ($end === null && $tmpEnd !== null) {
|
||||
$model->getQuery()->setEnd($tmpEnd);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param InvoiceQuery $query
|
||||
* @return InvoiceModel[]
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function createModels(InvoiceQuery $query): array
|
||||
{
|
||||
$models = [];
|
||||
$customerEntries = [];
|
||||
$items = $this->getInvoiceItems($query);
|
||||
|
||||
foreach ($items as $entry) {
|
||||
$customer = $entry->getProject()->getCustomer();
|
||||
$id = $customer->getId();
|
||||
if (!\array_key_exists($id, $customerEntries)) {
|
||||
$customerEntries[$id] = [
|
||||
'customer' => $customer,
|
||||
'entries' => [],
|
||||
];
|
||||
}
|
||||
$customerEntries[$id]['entries'][] = $entry;
|
||||
}
|
||||
|
||||
if (empty($customerEntries)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
uasort($customerEntries, function ($a, $b) {
|
||||
return strcmp($a['customer']->getName(), $b['customer']->getName());
|
||||
});
|
||||
|
||||
foreach ($customerEntries as $id => $settings) {
|
||||
if (empty($settings['entries'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$customerQuery = clone $query;
|
||||
$customerQuery->setCustomers([$settings['customer']]);
|
||||
$model = $this->createModelWithoutEntries($customerQuery);
|
||||
$model->addEntries($settings['entries']);
|
||||
$this->prepareModelQueryDates($model);
|
||||
|
||||
$models[] = $model;
|
||||
}
|
||||
|
||||
return $models;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user