From 87eb3caa29fa0c8504641daae475403175dab445 Mon Sep 17 00:00:00 2001 From: Mikkel Ricky Date: Wed, 12 Jun 2024 10:53:01 +0200 Subject: [PATCH] 1635: Reduced product invoice entries to a single entry --- .env | 4 +- CHANGELOG.md | 2 + src/Service/InvoiceEntryHelper.php | 11 +++++ src/Service/ProjectBillingService.php | 62 +++++++++++++++++---------- 4 files changed, 56 insertions(+), 23 deletions(-) diff --git a/.env b/.env index a0a1684fc..2a8a2a38e 100644 --- a/.env +++ b/.env @@ -81,7 +81,9 @@ PRODUCT_QUANTITY_SCALE=2 # }, # "product": { # "label": "The real PSP element", -# "product": true +# "product": true, +# // Optional invoice entry product name prefix +# "invoice_entry_prefix": "Tryk" # } # }' # diff --git a/CHANGELOG.md b/CHANGELOG.md index ce1a652db..8520b5531 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +* [PR-130](https://github.com/itk-dev/economics/pull/130) + 1635: Reduced product invoice entries to a single entry * NOTE: APP_DEFAULT_PLANNING_DATA_PROVIDER has been changed to APP_DEFAULT_DATA_PROVIDER. This has to be changed when releasing. * [PR-117](https://github.com/itk-dev/economics/pull/117) 1211: Added hour report diff --git a/src/Service/InvoiceEntryHelper.php b/src/Service/InvoiceEntryHelper.php index f75861306..34f57e660 100644 --- a/src/Service/InvoiceEntryHelper.php +++ b/src/Service/InvoiceEntryHelper.php @@ -86,6 +86,16 @@ public function getAccountLabel(string $account): string return $accounts[$account]['label'] ?? $account; } + /** + * Get account invoice entry pretix based on configured accounts. + */ + public function getAccountInvoiceEntryPrefix(string $account): ?string + { + $accounts = $this->getAccounts(null); + + return $accounts[$account]['invoice_entry_prefix'] ?? null; + } + /** * Decide if an invoice entry is editable. */ @@ -127,6 +137,7 @@ private function resolveOptions(array $options): array ->setDefaults([ 'default' => false, 'product' => false, + 'invoice_entry_prefix' => null, ]) ->setAllowedTypes('default', 'bool') ->setAllowedTypes('product', 'bool'); diff --git a/src/Service/ProjectBillingService.php b/src/Service/ProjectBillingService.php index cef1b1c38..272b3abd6 100644 --- a/src/Service/ProjectBillingService.php +++ b/src/Service/ProjectBillingService.php @@ -6,6 +6,7 @@ use App\Entity\Invoice; use App\Entity\InvoiceEntry; use App\Entity\Issue; +use App\Entity\IssueProduct; use App\Entity\ProjectBilling; use App\Entity\Worklog; use App\Enum\ClientTypeEnum; @@ -180,6 +181,23 @@ public function createProjectBilling(int $projectBillingId): void } } + $defaultAccount = $this->invoiceEntryHelper->getDefaultAccount(); + $productAccount = $this->invoiceEntryHelper->getProductAccount(); + + // Add invoice entry account prefix, if any, to (product) name. + $prefixWithAccount = function (?string $account, ?string $name): ?string { + if (null === $name) { + return null; + } + + $prefix = $account ? $this->invoiceEntryHelper->getAccountInvoiceEntryPrefix($account) : null; + if (null !== $prefix) { + $name = $prefix.': '.$name; + } + + return $name; + }; + foreach ($invoices as $invoiceArray) { /** @var Client $client */ $client = $invoiceArray['client']; @@ -198,7 +216,7 @@ public function createProjectBilling(int $projectBillingId): void // TODO: MaterialNumberEnum::EXTERNAL_WITH_MOMS or MaterialNumberEnum::EXTERNAL_WITHOUT_MOMS? $invoice->setDefaultMaterialNumber($internal ? MaterialNumberEnum::INTERNAL : MaterialNumberEnum::EXTERNAL_WITH_MOMS); - $invoice->setDefaultReceiverAccount($this->invoiceEntryHelper->getDefaultAccount()); + $invoice->setDefaultReceiverAccount($defaultAccount); /** @var Issue $issue */ foreach ($invoiceArray['issues'] as $issue) { @@ -206,7 +224,10 @@ public function createProjectBilling(int $projectBillingId): void $invoiceEntry->setEntryType(InvoiceEntryTypeEnum::WORKLOG); $invoiceEntry->setDescription(''); - $product = $this->getInvoiceEntryProduct($issue); + $product = $prefixWithAccount( + $defaultAccount, + $this->getInvoiceEntryProduct($issue) + ); $price = $this->clientHelper->getStandardPrice($client); $invoiceEntry->setProduct($product); @@ -236,29 +257,26 @@ public function createProjectBilling(int $projectBillingId): void $this->entityManager->persist($invoiceEntry); } - $invoiceEntryProductName = $invoiceEntry->getProduct(); - // Add invoice entries for each product. - foreach ($issue->getProducts() as $productIssue) { - $product = $productIssue->getProduct(); - if (null === $product) { - continue; - } - - $productName = $product->getName() ?? ''; + // Add a single product entry summing all product expenses. + $products = $issue->getProducts(); + if (!$products->isEmpty()) { + $price = $products->reduce(static fn (?float $sum, IssueProduct $product) => $sum + $product->getTotal(), 0.0); + $product = $prefixWithAccount( + $productAccount, + $issue->getName() + ); $productInvoiceEntry = (new InvoiceEntry()) ->setEntryType(InvoiceEntryTypeEnum::PRODUCT) - ->setDescription($productIssue->getDescription()) - ->setProduct(null === $invoiceEntryProductName - ? $productName - : sprintf('%s: %s', $invoiceEntryProductName, $productName) - ) - ->setPrice($product->getPriceAsFloat()) - ->setAmount($productIssue->getQuantity()) - ->setTotalPrice($productIssue->getQuantity() * $product->getPriceAsFloat()) + ->setDescription($issue->getName()) + ->setProduct($product) + ->setPrice($price) + ->setAmount(1) + ->setTotalPrice($price) ->setMaterialNumber($invoice->getDefaultMaterialNumber()) - ->setAccount($this->invoiceEntryHelper->getProductAccount() - ?? $this->invoiceEntryHelper->getDefaultAccount()) - ->addIssueProduct($productIssue); + ->setAccount($productAccount ?? $this->invoiceEntryHelper->getDefaultAccount()); + foreach ($issue->getProducts() as $productIssue) { + $productInvoiceEntry->addIssueProduct($productIssue); + } // We don't add worklogs here, since they're already attached to the main invoice entry // (and only used to detect if an entry has been added to an invoice).