Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

1635: Reduced product invoice entries to a single entry #130

Open
wants to merge 14 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 25 additions & 1 deletion .env
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,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"
# }
# }'
#
Expand All @@ -93,3 +95,25 @@ INVOICE_ENTRY_ACCOUNTS='{
"label": "Define INVOICE_ENTRY_ACCOUNTS in .env.local"
}
}'

# If true, project billing will generate one invoice per issue per client.
# Otherwise, all issues will be added to a single invoice per client.
INVOICE_ONE_INVOICE_PER_ISSUE=false

# If true, the invoice description is set based on issue description
SET_INVOICE_DESCRIPTION_FROM_ISSUE_DESCRIPTION=false
INVOICE_DESCRIPTION_ISSUE_HEADING=Opgavebeskrivelse

# Replace elements in invoice descriptions (if `SET_INVOICE_DESCRIPTION_FROM_ISSUE_DESCRIPTION` is true)
#
# {
# elementName: [start, end]
# }
#
# INVOICE_DESCRIPTION_ELEMENT_REPLACEMENTS='{
# "p": ["", "; "]
# "strong": [": ", ""]
# }'
INVOICE_DESCRIPTION_ELEMENT_REPLACEMENTS='{}'

INVOICE_ISSUE_FAR_PAST_CUTOFF_DATE=''
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1812: Minor hour report improvements.
* [PR-134](https://github.com/itk-dev/economics/pull/134)
1632: Remove team report.
* [PR-130](https://github.com/itk-dev/economics/pull/130)
Reduced product invoice entries to a single entry
Added option to generate one invoice per issue
* [PR-133](https://github.com/itk-dev/economics/pull/133)
1742: Simplified hour report form.
* [PR-132](https://github.com/itk-dev/economics/pull/132)
Expand Down
7 changes: 7 additions & 0 deletions assets/styles/app.css
Original file line number Diff line number Diff line change
Expand Up @@ -405,6 +405,7 @@
content: '▼';
@apply pr-4 text-xs;
}

.loading {
position: relative;
}
Expand All @@ -419,4 +420,10 @@
z-index: 9999;
left: 0;
}

.hours,
.price,
.quantity {
text-align: right;
}
}
2 changes: 2 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
"require": {
"php": ">=8.1",
"ext-ctype": "*",
"ext-dom": "*",
"ext-iconv": "*",
"ext-mbstring": "*",
"doctrine/doctrine-bundle": "^2.7",
"doctrine/doctrine-migrations-bundle": "^3.2",
"doctrine/orm": "^2.13",
Expand Down
10 changes: 6 additions & 4 deletions composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion config/packages/twig.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ twig:
date:
format: 'd.m.Y'
globals:
invoice_entry_helper: '@App\Service\InvoiceEntryHelper'
invoice_helper: '@App\Service\InvoiceHelper'

when@test:
twig:
Expand Down
7 changes: 6 additions & 1 deletion config/services.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,15 @@ services:
issue_product_type_options:
quantity_scale: '%env(int:PRODUCT_QUANTITY_SCALE)%'

App\Service\InvoiceEntryHelper:
App\Service\InvoiceHelper:
arguments:
$options:
accounts: '%env(json:INVOICE_ENTRY_ACCOUNTS)%'
one_invoice_per_issue: '%env(bool:INVOICE_ONE_INVOICE_PER_ISSUE)%'
set_invoice_description_from_issue_description: '%env(bool:SET_INVOICE_DESCRIPTION_FROM_ISSUE_DESCRIPTION)%'
invoice_description_issue_heading: '%env(INVOICE_DESCRIPTION_ISSUE_HEADING)%'
invoice_description_element_replacements: '%env(json:INVOICE_DESCRIPTION_ELEMENT_REPLACEMENTS)%'
invoice_issue_far_past_cutoff_date: '%env(INVOICE_ISSUE_FAR_PAST_CUTOFF_DATE)%'

App\Service\PlanningService:
arguments:
Expand Down
31 changes: 31 additions & 0 deletions migrations/Version20240701080654.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php

declare(strict_types=1);

namespace DoctrineMigrations;

use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;

/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20240701080654 extends AbstractMigration
{
public function getDescription(): string
{
return '';
}

public function up(Schema $schema): void
{
// this up() migration is auto-generated, please modify it to your needs
$this->addSql('ALTER TABLE issue ADD description LONGTEXT DEFAULT NULL');
}

public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql('ALTER TABLE issue DROP description');
}
}
31 changes: 31 additions & 0 deletions migrations/Version20240701104400.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php

declare(strict_types=1);

namespace DoctrineMigrations;

use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;

/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20240701104400 extends AbstractMigration
{
public function getDescription(): string
{
return '';
}

public function up(Schema $schema): void
{
// this up() migration is auto-generated, please modify it to your needs
$this->addSql('ALTER TABLE invoice CHANGE description description VARCHAR(500) DEFAULT NULL');
}

public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql('ALTER TABLE invoice CHANGE description description VARCHAR(255) DEFAULT NULL');
}
}
4 changes: 2 additions & 2 deletions src/Controller/InvoiceController.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
use App\Repository\InvoiceRepository;
use App\Service\BillingService;
use App\Service\ClientHelper;
use App\Service\InvoiceEntryHelper;
use App\Service\InvoiceHelper;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
Expand All @@ -37,7 +37,7 @@ class InvoiceController extends AbstractController
public function __construct(
private readonly BillingService $billingService,
private readonly TranslatorInterface $translator,
private readonly InvoiceEntryHelper $invoiceEntryHelper,
private readonly InvoiceHelper $invoiceEntryHelper,
) {
}

Expand Down
6 changes: 3 additions & 3 deletions src/Controller/InvoiceEntryController.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
use App\Repository\InvoiceEntryRepository;
use App\Service\BillingService;
use App\Service\ClientHelper;
use App\Service\InvoiceEntryHelper;
use App\Service\InvoiceHelper;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
Expand All @@ -27,7 +27,7 @@ public function __construct(
private readonly BillingService $billingService,
private readonly TranslatorInterface $translator,
private readonly ClientHelper $clientHelper,
private readonly InvoiceEntryHelper $invoiceEntryHelper,
private readonly InvoiceHelper $invoiceHelper,
) {
}

Expand Down Expand Up @@ -90,7 +90,7 @@ public function edit(Request $request, Invoice $invoice, InvoiceEntry $invoiceEn
$options['disabled'] = true;
}

$accounts = $this->invoiceEntryHelper->getAccountOptions($invoiceEntry->getAccount());
$accounts = $this->invoiceHelper->getAccountOptions($invoiceEntry->getAccount());
if (!empty($accounts)) {
$options['invoice_entry_accounts'] = $accounts;
}
Expand Down
1 change: 1 addition & 0 deletions src/Controller/ProjectBillingController.php
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ public function edit(Request $request, ProjectBilling $projectBilling, ProjectBi
'projectBilling' => $projectBilling,
'form' => $form,
'issuesWithoutAccounts' => $issuesWithoutAccounts,
'issuesInTheFarPast' => $projectBillingService->getIssuesNotIncludedInProjectBillingFromTheFarPast($projectBilling),
]);
}

Expand Down
5 changes: 4 additions & 1 deletion src/Entity/Invoice.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,13 @@
#[ORM\Entity(repositoryClass: InvoiceRepository::class)]
class Invoice extends AbstractBaseEntity
{
// Opus allows at most 500 characters (cf. BillingService::exportInvoicesToSpreadsheet).
public const DESCRIPTION_MAX_LENGTH = 500;

#[ORM\Column(length: 255)]
private ?string $name = null;

#[ORM\Column(length: 255, nullable: true)]
#[ORM\Column(length: self::DESCRIPTION_MAX_LENGTH, nullable: true)]
private ?string $description = null;

#[ORM\Column]
Expand Down
15 changes: 15 additions & 0 deletions src/Entity/Issue.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ class Issue extends AbstractBaseEntity
#[ORM\Column(length: 255)]
private ?string $name = null;

#[ORM\Column(type: Types::TEXT, nullable: true)]
private ?string $description = null;

#[ORM\Column(length: 255)]
private ?string $status = null;

Expand Down Expand Up @@ -89,6 +92,18 @@ public function setName(string $name): self
return $this;
}

public function getDescription(): ?string
{
return $this->description;
}

public function setDescription(?string $description): static
{
$this->description = $description;

return $this;
}

public function getStatus(): ?string
{
return $this->status;
Expand Down
1 change: 1 addition & 0 deletions src/Model/Invoices/IssueData.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ class IssueData
{
public \DateTime $started;
public string $name;
public string $description;
public string $status;
public string $projectTrackerId;
public string $projectTrackerKey;
Expand Down
13 changes: 13 additions & 0 deletions src/Repository/IssueRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,19 @@ public function getClosedIssuesFromInterval(Project $project, \DateTimeInterface
return $qb->getQuery()->execute();
}

public function getIssuesClosedBefore(Project $project, \DateTimeInterface $date)
{
$qb = $this->createQueryBuilder('issue');
$qb->andWhere($qb->expr()->eq('issue.project', ':project'));
$qb->setParameter('project', $project);
$qb->andWhere('issue.resolutionDate < :date');
$qb->setParameter('date', $date);
$qb->andWhere('issue.status IN (:statuses)');
$qb->setParameter('statuses', $this->getClosedStatuses($project));

return $qb->getQuery()->execute();
}

/**
* Get "closed" statuses for a project.
*
Expand Down
21 changes: 19 additions & 2 deletions src/Service/BillingService.php
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@ public function generateSpreadsheetCsvResponse(array $ids): Response
/** @var Csv $writer */
$writer = IOFactory::createWriter($spreadsheet, 'Csv');
$writer->setDelimiter(';');
// SAP/Opus cannot read CSV!
$writer->setEnclosure('');
$writer->setLineEnding("\r\n");
$writer->setSheetIndex(0);
Expand Down Expand Up @@ -306,8 +307,8 @@ public function exportInvoicesToSpreadsheet(array $invoiceIds): Spreadsheet
// 15. "Kunderef.ID"
$setCellValue(15, $row, substr('Att: '.$contactName, 0, 35));
// 16. "Toptekst, yderligere spec i det hvide felt på fakturaen"
$description = $invoice->getDescription() ?? '';
$setCellValue(16, $row, substr($description, 0, 500));
$description = $this->formatCsvText($invoice->getDescription() ?? '');
$setCellValue(16, $row, substr($description, 0, Invoice::DESCRIPTION_MAX_LENGTH));
// 17. "Leverandør"
if ($internal) {
$setCellValue(17, $row, str_pad($this->invoiceSupplierAccount, 10, '0', \STR_PAD_LEFT));
Expand Down Expand Up @@ -372,6 +373,22 @@ public function exportInvoicesToSpreadsheet(array $invoiceIds): Spreadsheet
return $spreadsheet;
}

/**
* Does something to make SAP/Opus accept a text value.
*
* @param string $text
*
* @return string
*/
private function formatCsvText(string $text): string
{
return str_replace(
["\n", "\r", ';'],
[' ', '', '.'],
$text
);
}

/**
* @throws PhpSpreadsheetException
*/
Expand Down
Loading
Loading