added pdf template and other invoice improvements (#1693)

This commit is contained in:
Kevin Papst
2020-05-10 16:19:59 +02:00
committed by GitHub
parent 4e1ffedc20
commit e3749c8e8f
45 changed files with 561 additions and 89 deletions

View File

@@ -16,6 +16,7 @@ use App\Entity\Project;
use App\Invoice\ServiceInvoice;
use App\Repository\CustomerRepository;
use App\Repository\InvoiceTemplateRepository;
use App\Repository\ProjectRepository;
use App\Repository\Query\InvoiceQuery;
use App\Repository\Query\TimesheetQuery;
use App\Repository\TimesheetRepository;
@@ -46,6 +47,10 @@ class InvoiceCreateCommand extends Command
* @var CustomerRepository
*/
private $customerRepository;
/**
* @var ProjectRepository
*/
private $projectRepository;
/**
* @var InvoiceTemplateRepository
*/
@@ -67,6 +72,7 @@ class InvoiceCreateCommand extends Command
ServiceInvoice $serviceInvoice,
TimesheetRepository $timesheetRepository,
CustomerRepository $customerRepository,
ProjectRepository $projectRepository,
InvoiceTemplateRepository $invoiceTemplateRepository,
UserRepository $userRepository,
EventDispatcherInterface $eventDispatcher
@@ -74,6 +80,7 @@ class InvoiceCreateCommand extends Command
$this->serviceInvoice = $serviceInvoice;
$this->timesheetRepository = $timesheetRepository;
$this->customerRepository = $customerRepository;
$this->projectRepository = $projectRepository;
$this->invoiceTemplateRepository = $invoiceTemplateRepository;
$this->userRepository = $userRepository;
$this->eventDispatcher = $eventDispatcher;
@@ -94,6 +101,7 @@ class InvoiceCreateCommand extends Command
->addOption('end', null, InputOption::VALUE_OPTIONAL, 'End date (format: 2020-01-31, default: end of the month)', null)
->addOption('timezone', null, InputOption::VALUE_OPTIONAL, 'Timezone for start and end date query', date_default_timezone_get())
->addOption('customer', null, InputOption::VALUE_OPTIONAL, 'Comma separated list of customer IDs', null)
->addOption('project', null, InputOption::VALUE_OPTIONAL, 'Comma separated list of project IDs', null)
->addOption('by-customer', null, InputOption::VALUE_NONE, 'If set, one invoice for each active customer in the given timerange is created')
->addOption('by-project', null, InputOption::VALUE_NONE, 'If set, one invoice for each active project in the given timerange is created')
->addOption('set-exported', null, InputOption::VALUE_NONE, 'Whether the invoice items should be marked as exported')
@@ -168,8 +176,9 @@ class InvoiceCreateCommand extends Command
}
$customersIDs = $input->getOption('customer');
if (!$byActiveCustomer && !$byActiveProject && empty($customersIDs)) {
$io->error('Could not determine generation mode, you need to set one of: customer, by-customer, by-project');
$projectIDs = $input->getOption('project');
if (!$byActiveCustomer && !$byActiveProject && empty($customersIDs) && empty($projectIDs)) {
$io->error('Could not determine generation mode, you need to set one of: customer, project, by-customer, by-project');
return 1;
}
@@ -235,15 +244,15 @@ class InvoiceCreateCommand extends Command
$defaultQuery->setCurrentUser($user);
$defaultQuery->setSearchTerm($searchTerm);
$defaultQuery->setMarkAsExported($markAsExported);
$defaultQuery->setState($exportedFilter);
$defaultQuery->setExported($exportedFilter);
/** @var Invoice[] $invoices */
$invoices = [];
/** @var Customer[] $customers */
$customers = [];
if (!empty($customersIDs)) {
/** @var Customer[] $customers */
$customers = [];
$customersIDs = explode(',', $customersIDs);
foreach ($customersIDs as $id) {
$tmp = $this->customerRepository->find($id);
@@ -255,6 +264,21 @@ class InvoiceCreateCommand extends Command
$customers[] = $tmp;
}
$invoices = $this->createInvoicesForCustomer($customers, $defaultQuery, $input, $output);
} elseif (!empty($projectIDs)) {
/** @var Project[] $projects */
$projects = [];
$projectIDs = explode(',', $projectIDs);
foreach ($projectIDs as $id) {
$tmp = $this->projectRepository->find($id);
if (null === $tmp) {
$io->error('Unknown project ID: ' . $id);
return 1;
}
$projects[] = $tmp;
}
$invoices = $this->createInvoicesForProjects($projects, $defaultQuery, $input, $output);
} elseif ($byActiveCustomer) {
$customers = $this->getActiveCustomers($start, $end);
$invoices = $this->createInvoicesForCustomer($customers, $defaultQuery, $input, $output);

View File

@@ -10,6 +10,7 @@
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
use Symfony\Component\Validator\Constraints as Assert;
/**
@@ -19,6 +20,7 @@ use Symfony\Component\Validator\Constraints as Assert;
* @ORM\UniqueConstraint(columns={"name"})
* }
* )
* @UniqueEntity("name")
*/
class InvoiceTemplate
{

View File

@@ -77,7 +77,13 @@ class InvoiceRendererType extends AbstractType
array_pop($parts);
}
return ucfirst(array_pop($parts));
$type = array_pop($parts);
if (\in_array(strtolower($type), ['json', 'txt', 'xml'])) {
return 'programmatic';
}
return ucfirst($type);
}
/**

View File

@@ -75,7 +75,7 @@ class InvoiceItemDefaultHydrator implements InvoiceItemHydrator
'entry.currency' => $currency,
'entry.duration' => $item->getDuration(),
'entry.duration_decimal' => $formatter->getFormattedDecimalDuration($item->getDuration()),
'entry.duration_minutes' => number_format($item->getDuration() / 60),
'entry.duration_minutes' => (int) ($item->getDuration() / 60),
'entry.begin' => $formatter->getFormattedDateTime($begin),
'entry.begin_time' => $formatter->getFormattedTime($begin),
'entry.begin_timestamp' => $begin->getTimestamp(),

View File

@@ -57,7 +57,7 @@ class InvoiceModelProjectHydrator implements InvoiceModelHydrator
$prefix . 'budget_money_plain' => $project->getBudget(),
$prefix . 'budget_time' => $project->getTimeBudget(),
$prefix . 'budget_time_decimal' => $formatter->getFormattedDecimalDuration($project->getTimeBudget()),
$prefix . 'budget_time_minutes' => number_format($project->getTimeBudget() / 60),
$prefix . 'budget_time_minutes' => (int) ($project->getTimeBudget() / 60),
];
foreach ($project->getVisibleMetaFields() as $metaField) {

View File

@@ -45,7 +45,14 @@ final class PdfRenderer implements RendererInterface
$content = $this->twig->render('@invoice/' . basename($document->getFilename()), [
'model' => $model
]);
$content = $this->converter->convertToPdf($content);
$content = $this->converter->convertToPdf($content, [
'setAutoTopMargin' => 'pad',
'setAutoBottomMargin' => 'pad',
'margin_top' => 12,
'margin_bottom' => 8,
]);
$filename = (string) new InvoiceFilename($model);
$response = new Response($content);

View File

@@ -91,7 +91,11 @@ class DateExtensions extends AbstractExtension
}
if (!$date instanceof DateTime) {
$date = new DateTime($date);
try {
$date = new DateTime($date);
} catch (\Exception $ex) {
return $date;
}
}
return date_format($date, $this->dateFormat);
@@ -109,7 +113,11 @@ class DateExtensions extends AbstractExtension
}
if (!$date instanceof DateTime) {
$date = new DateTime($date);
try {
$date = new DateTime($date);
} catch (\Exception $ex) {
return $date;
}
}
return $date->format($this->dateTimeFormat);
@@ -128,7 +136,11 @@ class DateExtensions extends AbstractExtension
}
if (!$date instanceof DateTime) {
$date = new DateTime($date);
try {
$date = new DateTime($date);
} catch (\Exception $ex) {
return $date;
}
}
$timezone = date_default_timezone_get();
@@ -158,7 +170,11 @@ class DateExtensions extends AbstractExtension
public function dateFormat($date, string $format)
{
if (!$date instanceof DateTime) {
$date = new DateTime($date);
try {
$date = new DateTime($date);
} catch (\Exception $ex) {
return $date;
}
}
return date_format($date, $format);

View File

@@ -193,7 +193,12 @@ class Extensions extends AbstractExtension
*/
public function currency($currency)
{
return Currencies::getSymbol($currency);
try {
return Currencies::getSymbol(strtoupper($currency));
} catch (\Exception $ex) {
}
return $currency;
}
/**
@@ -202,7 +207,12 @@ class Extensions extends AbstractExtension
*/
public function language($language)
{
return Languages::getName($language, $this->locale);
try {
return Languages::getName(strtolower($language), $this->locale);
} catch (\Exception $ex) {
}
return $language;
}
/**
@@ -211,9 +221,9 @@ class Extensions extends AbstractExtension
*/
public function country($country)
{
$country = strtoupper($country);
if (Countries::exists($country)) {
return Countries::getName($country);
try {
return Countries::getName(strtoupper($country));
} catch (\Exception $ex) {
}
return $country;

View File

@@ -14,7 +14,8 @@ interface HtmlToPdfConverter
/**
* Returns the binary content of the PDF, which can be saved as file or send via Reponse.
* @param string $html
* @param array $options
* @return mixed
*/
public function convertToPdf(string $html);
public function convertToPdf(string $html, array $options = []);
}

View File

@@ -27,12 +27,14 @@ class MPdfConverter implements HtmlToPdfConverter
/**
* @param string $html
* @param array $options
* @return mixed|string
* @throws \Mpdf\MpdfException
*/
public function convertToPdf(string $html)
public function convertToPdf(string $html, array $options = [])
{
$mpdf = new Mpdf(['tempDir' => $this->cacheDirectory]);
$options = array_merge($options, ['tempDir' => $this->cacheDirectory]);
$mpdf = new Mpdf($options);
$mpdf->creator = Constants::SOFTWARE;
// some OS do not follow the PHP default settings