diff --git a/.env b/.env index f7326445..91208e06 100644 --- a/.env +++ b/.env @@ -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" # } # }' # @@ -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='' diff --git a/CHANGELOG.md b/CHANGELOG.md index 91c2aea7..67ec3c87 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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) diff --git a/assets/styles/app.css b/assets/styles/app.css index 711482e0..588015ba 100644 --- a/assets/styles/app.css +++ b/assets/styles/app.css @@ -405,6 +405,7 @@ content: '▼'; @apply pr-4 text-xs; } + .loading { position: relative; } @@ -419,4 +420,10 @@ z-index: 9999; left: 0; } + + .hours, + .price, + .quantity { + text-align: right; + } } diff --git a/composer.json b/composer.json index 97ad9cdf..cb4cf0d6 100644 --- a/composer.json +++ b/composer.json @@ -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", diff --git a/composer.lock b/composer.lock index 32839804..2aec3567 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "6271cc27ab9aee98952e86350fe6017c", + "content-hash": "6f4a16c107f13421a6c5702d65ac5319", "packages": [ { "name": "behat/transliterator", @@ -10298,12 +10298,12 @@ "version": "v5.2.13", "source": { "type": "git", - "url": "https://github.com/justinrainbow/json-schema.git", + "url": "https://github.com/jsonrainbow/json-schema.git", "reference": "fbbe7e5d79f618997bc3332a6f49246036c45793" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/justinrainbow/json-schema/zipball/fbbe7e5d79f618997bc3332a6f49246036c45793", + "url": "https://api.github.com/repos/jsonrainbow/json-schema/zipball/fbbe7e5d79f618997bc3332a6f49246036c45793", "reference": "fbbe7e5d79f618997bc3332a6f49246036c45793", "shasum": "" }, @@ -13220,7 +13220,9 @@ "platform": { "php": ">=8.1", "ext-ctype": "*", - "ext-iconv": "*" + "ext-dom": "*", + "ext-iconv": "*", + "ext-mbstring": "*" }, "platform-dev": [], "plugin-api-version": "2.6.0" diff --git a/config/packages/twig.yaml b/config/packages/twig.yaml index d4661ff3..36863345 100644 --- a/config/packages/twig.yaml +++ b/config/packages/twig.yaml @@ -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: diff --git a/config/services.yaml b/config/services.yaml index 58b7be10..6f45f6b2 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -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: diff --git a/migrations/Version20240701080654.php b/migrations/Version20240701080654.php new file mode 100644 index 00000000..203314b1 --- /dev/null +++ b/migrations/Version20240701080654.php @@ -0,0 +1,31 @@ +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'); + } +} diff --git a/migrations/Version20240701104400.php b/migrations/Version20240701104400.php new file mode 100644 index 00000000..ad04f42a --- /dev/null +++ b/migrations/Version20240701104400.php @@ -0,0 +1,31 @@ +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'); + } +} diff --git a/src/Controller/InvoiceController.php b/src/Controller/InvoiceController.php index 9ec40cdc..d14b8377 100644 --- a/src/Controller/InvoiceController.php +++ b/src/Controller/InvoiceController.php @@ -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; @@ -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, ) { } diff --git a/src/Controller/InvoiceEntryController.php b/src/Controller/InvoiceEntryController.php index e5d4c2c2..c087237c 100644 --- a/src/Controller/InvoiceEntryController.php +++ b/src/Controller/InvoiceEntryController.php @@ -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; @@ -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, ) { } @@ -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; } diff --git a/src/Controller/ProjectBillingController.php b/src/Controller/ProjectBillingController.php index 88e3e06c..5c800ae2 100644 --- a/src/Controller/ProjectBillingController.php +++ b/src/Controller/ProjectBillingController.php @@ -116,6 +116,7 @@ public function edit(Request $request, ProjectBilling $projectBilling, ProjectBi 'projectBilling' => $projectBilling, 'form' => $form, 'issuesWithoutAccounts' => $issuesWithoutAccounts, + 'issuesInTheFarPast' => $projectBillingService->getIssuesNotIncludedInProjectBillingFromTheFarPast($projectBilling), ]); } diff --git a/src/Entity/Invoice.php b/src/Entity/Invoice.php index e0eb235c..be19de8c 100644 --- a/src/Entity/Invoice.php +++ b/src/Entity/Invoice.php @@ -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] diff --git a/src/Entity/Issue.php b/src/Entity/Issue.php index 08fc92d3..afb6fe35 100644 --- a/src/Entity/Issue.php +++ b/src/Entity/Issue.php @@ -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; @@ -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; diff --git a/src/Model/Invoices/IssueData.php b/src/Model/Invoices/IssueData.php index bc91e8d9..13c2e54d 100644 --- a/src/Model/Invoices/IssueData.php +++ b/src/Model/Invoices/IssueData.php @@ -9,6 +9,7 @@ class IssueData { public \DateTime $started; public string $name; + public string $description; public string $status; public string $projectTrackerId; public string $projectTrackerKey; diff --git a/src/Repository/IssueRepository.php b/src/Repository/IssueRepository.php index 0879700d..14296b9c 100644 --- a/src/Repository/IssueRepository.php +++ b/src/Repository/IssueRepository.php @@ -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. * diff --git a/src/Service/BillingService.php b/src/Service/BillingService.php index e0e5ac3e..f7de8ec8 100644 --- a/src/Service/BillingService.php +++ b/src/Service/BillingService.php @@ -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); @@ -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)); @@ -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 */ diff --git a/src/Service/DataSynchronizationService.php b/src/Service/DataSynchronizationService.php index 0ba6eb4a..f27da4e4 100644 --- a/src/Service/DataSynchronizationService.php +++ b/src/Service/DataSynchronizationService.php @@ -13,6 +13,7 @@ use App\Entity\Worklog; use App\Exception\EconomicsException; use App\Exception\UnsupportedDataProviderException; +use App\Model\Invoices\IssueData; use App\Model\SprintReport\SprintReportVersion; use App\Repository\AccountRepository; use App\Repository\ClientRepository; @@ -214,6 +215,7 @@ public function syncIssuesForProject(int $projectId, callable $progressCallback $pagedIssueData = $service->getIssuesDataForProjectPaged($projectTrackerId, $startAt, self::MAX_RESULTS); $total = $pagedIssueData->total; + /** @var IssueData $issueDatum */ foreach ($pagedIssueData->items as $issueDatum) { $issue = $this->issueRepository->findOneBy(['projectTrackerId' => $issueDatum->projectTrackerId]); @@ -224,6 +226,7 @@ public function syncIssuesForProject(int $projectId, callable $progressCallback $this->entityManager->persist($issue); } $issue->setName($issueDatum->name); + $issue->setDescription($issueDatum->description); $issue->setAccountId($issueDatum->accountId); $issue->setAccountKey($issueDatum->accountKey); $issue->setEpicKey($issueDatum->epicKey); @@ -236,6 +239,7 @@ public function syncIssuesForProject(int $projectId, callable $progressCallback $issue->setPlanHours($issueDatum->planHours); $issue->setHoursRemaining($issueDatum->hourRemaining); $issue->setDueDate($issueDatum->dueDate); + assert(null !== $issueDatum->worker); $issue->setWorker($issueDatum->worker); $issue->setLinkToIssue($issueDatum->linkToIssue); @@ -244,11 +248,13 @@ public function syncIssuesForProject(int $projectId, callable $progressCallback $issue->getVersions()->clear(); } - foreach ($issueDatum->versions as $versionData) { - $version = $this->versionRepository->findOneBy(['projectTrackerId' => $versionData->projectTrackerId]); + if (null !== $issueDatum->versions) { + foreach ($issueDatum->versions as $versionData) { + $version = $this->versionRepository->findOneBy(['projectTrackerId' => $versionData->projectTrackerId]); - if (null !== $version) { - $issue->addVersion($version); + if (null !== $version) { + $issue->addVersion($version); + } } } diff --git a/src/Service/HtmlHelper.php b/src/Service/HtmlHelper.php new file mode 100644 index 00000000..4d3e88f1 --- /dev/null +++ b/src/Service/HtmlHelper.php @@ -0,0 +1,79 @@ +loadHTML('
{{ 'project_billing.issue.key'|trans }} | +{{ 'project_billing.issue.name'|trans }} | +{{ 'project_billing.issue.versions'|trans }} | +{{ 'project_billing.issue.worklog_time_spent'|trans }} | +{{ 'project_billing.issue.status'|trans }} | +
---|---|---|---|---|
{{ issue.projectTrackerKey }} | +{{ issue.name }} | +{{ issue.versions|map(p => p.name)|join(', ') }} | +{{ issue.worklogs|reduce((carry, v, k) => carry + v.timeSpentSeconds / 3600)|format_hours }} | +{{ issue.status }} | +
{{ 'project_billing.issue.key'|trans }} | -{{ 'project_billing.issue.name'|trans }} | -{{ 'project_billing.issue.versions'|trans }} | -{{ 'project_billing.issue.worklog_time_spent'|trans }} | -{{ 'project_billing.issue.status'|trans }} | -
---|---|---|---|---|
{{ issue.projectTrackerKey }} | -{{ issue.name }} | -{{ issue.versions|map(p => p.name)|join(', ') }} | -{{ issue.worklogs|reduce((carry, v, k) => carry + v.timeSpentSeconds / 3600) }} | -{{ issue.status }} | -
Titel: Tryk af viftekort LIV guide
Produkt
viftekort- som ledetrådene- lille lommeformat: 200 stk.
Kommentar til opgaven: Fil er uden skæremærker. Efter opsætning hos jer, send gerne retur til godkendelse.
Uploadede filer
ny-liv-viftekort-til-tryk-2.0.pdf (OK)
xxxxxx
xxx
xxx
xxx
xxx
xxxxxx
xxx
xxx
xxx
xxx
xxx
xxx
xxx
xxx
xxx
xxx
xxx
xxx
xxx
xxxxxx
xxx
xxx
xxx
xxx
xxxxxx
xxx
xxx
xxx
xxx
xxx
xxx
xxx
xxx
xxx
xxx
xxx
xxx
xxx
xxxxxx
xxx
xxx
xxx
xxx
xxxxxx
xxx
xxx
xxx
xxx
xxx
xxx
xxx
xxx
xxx
xxx
xxx
xxx
xxx
xxx: xxx
xxx:
xxx
xxx
xxx
xxx: xxx
xxx
xxx
xxx
xxx
xxx
xxx
xxx
xxx
xxx
xxx:
xxx
xxx
xxx
xxxxxx
xxx
xxx
xxx
xxx
xxxxxx
xxx
xxx
xxx
xxx
xxx
xxx
xxx
xxx
xxx
xxx
xxx
xxx
xxx
xxxxxx; xxx; xxx; xxx; xxx; xxxxxx; xxx; xxx; xxx; xxx; xxx; xxx; xxx; xxx; xxx; xxx; xxx; xxx; xxx
', + ]; + + yield [ + 'xxxxxx
xxx
xxx
xxx
xxx
xxxxxx
xxx
xxx
xxx
xxx
xxx
xxx
xxx
xxx
xxx
xxx
xxx
xxx
xxx
xxxxxx; xxx; xxx; xxx; xxx; xxxxxx; xxx; xxx; xxx; xxx; xxx; xxx; xxx; xxx; xxx; xxx; xxx; xxx; xxx
', + ]; + } + + /** + * Provider for testGetSection. + * + * @return iterable + */ + public static function dataGetSection(): iterable + { + yield [ + 'Navn: Test Testersen
+ +Titel: Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
+ +Titel: Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
+ +Titel: Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
+ +Titel: Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
+', + 'Opgavebeskrivelse', + null, + null, + null, + + ' +Titel: Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
+ +', + ]; + + yield [ + 'Navn: Test Testersen
+ +Titel: Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
+ +Titel: Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
+ +Titel: Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
+ +Titel: Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
+', + 'Hest', + null, + null, + null, + + null, + ]; + + yield [ + 'Navn: Test Testersen
+ +Titel: Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
+ +Titel: Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
+ +Titel: Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
+ +Titel: Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
+', + '/beskrivelse/', + null, + true, + null, + + ' +Titel: Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
+ +', + ]; + } +} diff --git a/translations/messages.da.yaml b/translations/messages.da.yaml index f235db7e..5049dde2 100644 --- a/translations/messages.da.yaml +++ b/translations/messages.da.yaml @@ -459,6 +459,9 @@ project_billing: on_record_errors_invoice_link: "Link til faktura" action_record_internal: "Eksportér interne" action_record_external: "Eksportér eksterne" + issues_in_the_far_past: 'Opgaver fra fortiden' + issues_in_the_far_past_alert: 'Nogle opgaver er lukket før %date%.' + issues_in_the_far_past_info: '{1}Bemærk: Denne opgave er lukket før %date%.|]1,Inf[Bemærk: Disse opgaver er lukket før %date%.' product: title: "Produkter"