added new invoice fields, improved invoice templates (#1258)
This commit is contained in:
@@ -17,11 +17,11 @@ class Constants
|
||||
/**
|
||||
* The current release version
|
||||
*/
|
||||
public const VERSION = '1.5';
|
||||
public const VERSION = '1.6';
|
||||
/**
|
||||
* The current release status, either "stable" or "dev"
|
||||
*/
|
||||
public const STATUS = 'stable';
|
||||
public const STATUS = 'dev';
|
||||
/**
|
||||
* The software name
|
||||
*/
|
||||
|
||||
@@ -302,10 +302,6 @@ class ProfileController extends AbstractController
|
||||
|
||||
private function createPreferencesForm(User $user): FormInterface
|
||||
{
|
||||
// we need to prepare the user preferences, which is done via an EventSubscriber
|
||||
$event = new PrepareUserEvent($user);
|
||||
$this->dispatcher->dispatch($event);
|
||||
|
||||
return $this->createForm(
|
||||
UserPreferencesForm::class,
|
||||
$user,
|
||||
|
||||
@@ -46,7 +46,7 @@ class CustomerFixtures extends Fixture
|
||||
*/
|
||||
public function load(ObjectManager $manager)
|
||||
{
|
||||
$faker = Factory::create();
|
||||
$faker = Factory::create('at_AT');
|
||||
|
||||
$amountCustomers = rand(self::MIN_CUSTOMERS, self::MAX_CUSTOMERS);
|
||||
for ($c = 1; $c <= $amountCustomers; $c++) {
|
||||
@@ -100,6 +100,7 @@ class CustomerFixtures extends Fixture
|
||||
->setCountry($faker->countryCode)
|
||||
->setTimezone($faker->timezone)
|
||||
->setVisible($visible)
|
||||
->setVatId($faker->vat)
|
||||
;
|
||||
|
||||
if (rand(0, 3) % 3) {
|
||||
|
||||
@@ -31,7 +31,9 @@ class InvoiceFixtures extends Fixture
|
||||
*/
|
||||
public function load(ObjectManager $manager)
|
||||
{
|
||||
foreach ($this->getInvoiceConfigs() as $invoiceConfig) {
|
||||
$faker = Factory::create('at_AT');
|
||||
|
||||
foreach ($this->getInvoiceConfigs($faker) as $invoiceConfig) {
|
||||
$template = new InvoiceTemplate();
|
||||
|
||||
// name, title, renderer, calculator, numberGenerator, company, vat, dueDays, address, paymentTerms
|
||||
@@ -44,8 +46,11 @@ class InvoiceFixtures extends Fixture
|
||||
->setCompany($invoiceConfig[5])
|
||||
->setVat($invoiceConfig[6])
|
||||
->setDueDays($invoiceConfig[7])
|
||||
->setAddress($invoiceConfig[8])
|
||||
->setPaymentTerms($invoiceConfig[9])
|
||||
->setPaymentTerms($invoiceConfig[8])
|
||||
->setVatId($faker->vat)
|
||||
->setAddress($this->generateAddress($faker))
|
||||
->setContact($this->generateContact($faker))
|
||||
->setPaymentDetails($this->generatePaymentDetails($faker))
|
||||
;
|
||||
|
||||
$manager->persist($template);
|
||||
@@ -53,10 +58,8 @@ class InvoiceFixtures extends Fixture
|
||||
}
|
||||
}
|
||||
|
||||
private function getInvoiceConfigs()
|
||||
private function getInvoiceConfigs(Generator $faker)
|
||||
{
|
||||
$faker = Factory::create();
|
||||
|
||||
$paymentTerms =
|
||||
'I would like to thank you for your confidence and will gladly be there for you in the future.' .
|
||||
PHP_EOL .
|
||||
@@ -64,11 +67,9 @@ class InvoiceFixtures extends Fixture
|
||||
'as reference.'
|
||||
;
|
||||
|
||||
$address =
|
||||
$faker->streetAddress . PHP_EOL .
|
||||
$faker->city . ', ' . $faker->stateAbbr . ' ' . $faker->postcode . PHP_EOL .
|
||||
'Phone: ' . $faker->phoneNumber . PHP_EOL .
|
||||
'Email: ' . $faker->safeEmail
|
||||
$paymentTerms_alt =
|
||||
$faker->firstName . ', thank you very much. We really appreciate your business.' . PHP_EOL .
|
||||
'Please send payments before the due date.I would like to thank you for your confidence and will gladly be there for you in the future.'
|
||||
;
|
||||
|
||||
$paymentTerms_de =
|
||||
@@ -85,17 +86,14 @@ class InvoiceFixtures extends Fixture
|
||||
|
||||
// name, title, renderer, calculator, numberGenerator, company, vat, dueDays, address, paymentTerms
|
||||
return [
|
||||
['Invoice (HTML)', 'Company name', 'default', 'default', 'default', $faker->company, 19, 30, $address, $paymentTerms],
|
||||
['Freelancer (HTML, short)', 'Invoice', 'freelancer', 'short', 'default', $faker->company, 19, 14, $this->generateAddress($faker), $paymentTerms_de],
|
||||
['Timesheet (HTML)', 'Timesheet', 'timesheet', 'default', 'default', $faker->company, 19, 7, $this->generateAddress($faker), ''],
|
||||
['Company invoice (DOCX)', 'Invoice', 'company', 'default', 'default', 'Kimai Inc.', 19, 14, $this->generateAddress($faker, true), $this->generatePaymentTerms($faker)],
|
||||
['Export (CSV, user-group)', 'User-grouped', 'export', 'user', 'default', $faker->company, 7, 28, '', ''],
|
||||
['Export (ODS)', 'Spreadsheet', 'open-spreadsheet', 'default', 'default', $faker->company, 19, 14, '', ''],
|
||||
['Export (XLSX, user-group)', 'Spreadsheet', 'spreadsheet', 'user', 'default', $faker->company, 13, 10, '', ''],
|
||||
['Invoice (HTML)', 'Company name', 'default', 'default', 'default', $faker->company, 19, 30, $paymentTerms],
|
||||
['Freelancer (HTML, short)', 'Invoice', 'freelancer', 'short', 'default', $faker->company, 19, 14, $paymentTerms_de],
|
||||
['Timesheet (HTML)', 'Timesheet', 'timesheet', 'default', 'default', $faker->company, 19, 7, $paymentTerms_alt],
|
||||
['Company invoice (DOCX)', 'Invoice', 'company', 'default', 'default', 'Kimai Inc.', 19, 14, $paymentTerms_alt],
|
||||
];
|
||||
}
|
||||
|
||||
protected function generatePaymentTerms(Generator $faker)
|
||||
protected function generatePaymentDetails(Generator $faker)
|
||||
{
|
||||
return
|
||||
'Acme Bank' . PHP_EOL .
|
||||
@@ -104,15 +102,17 @@ class InvoiceFixtures extends Fixture
|
||||
;
|
||||
}
|
||||
|
||||
protected function generateAddress(Generator $faker, $lineBreaks = false)
|
||||
protected function generateContact(Generator $faker)
|
||||
{
|
||||
if (!$lineBreaks) {
|
||||
return
|
||||
$faker->name . ' - ' .
|
||||
$faker->streetAddress . '-' .
|
||||
$faker->postcode . ' ' . $faker->city;
|
||||
}
|
||||
return
|
||||
'Phone: ' . $faker->phoneNumber . PHP_EOL .
|
||||
'Email: ' . $faker->safeEmail . PHP_EOL .
|
||||
'Web: www.' . $faker->domainName
|
||||
;
|
||||
}
|
||||
|
||||
protected function generateAddress(Generator $faker)
|
||||
{
|
||||
return
|
||||
'Kimai Inc.' . PHP_EOL .
|
||||
$faker->streetAddress . PHP_EOL .
|
||||
|
||||
@@ -55,7 +55,7 @@ abstract class AbstractMigration extends BaseAbstractMigration implements Contai
|
||||
/**
|
||||
* Whether we should deactivate foreign key support for SQLite.
|
||||
* This is required, if columns are changed.
|
||||
* SQLite will drop the table and therefor all referenced data if we don't deactivate this.
|
||||
* SQLite will drop the table and all referenced data, if we don't deactivate this.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
|
||||
@@ -79,6 +79,14 @@ class Customer implements EntityWithMetaFields
|
||||
*/
|
||||
private $company;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*
|
||||
* @ORM\Column(name="vat_id", type="string", length=50, nullable=true)
|
||||
* @Assert\Length(max=50)
|
||||
*/
|
||||
private $vatId;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*
|
||||
@@ -272,6 +280,18 @@ class Customer implements EntityWithMetaFields
|
||||
return $this->company;
|
||||
}
|
||||
|
||||
public function getVatId(): ?string
|
||||
{
|
||||
return $this->vatId;
|
||||
}
|
||||
|
||||
public function setVatId(?string $vatId): Customer
|
||||
{
|
||||
$this->vatId = $vatId;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setContact(?string $contact): Customer
|
||||
{
|
||||
$this->contact = $contact;
|
||||
|
||||
@@ -56,6 +56,14 @@ class InvoiceTemplate
|
||||
*/
|
||||
private $company;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*
|
||||
* @ORM\Column(name="vat_id", type="string", length=50, nullable=true)
|
||||
* @Assert\Length(max=50)
|
||||
*/
|
||||
private $vatId;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*
|
||||
@@ -63,6 +71,13 @@ class InvoiceTemplate
|
||||
*/
|
||||
private $address;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*
|
||||
* @ORM\Column(name="contact", type="text", nullable=true)
|
||||
*/
|
||||
private $contact;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*
|
||||
@@ -109,6 +124,13 @@ class InvoiceTemplate
|
||||
*/
|
||||
private $paymentTerms;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*
|
||||
* @ORM\Column(name="payment_details", type="text", nullable=true)
|
||||
*/
|
||||
private $paymentDetails;
|
||||
|
||||
public function getId(): ?int
|
||||
{
|
||||
return $this->id;
|
||||
@@ -234,6 +256,42 @@ class InvoiceTemplate
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getVatId(): ?string
|
||||
{
|
||||
return $this->vatId;
|
||||
}
|
||||
|
||||
public function setVatId(?string $vatId): InvoiceTemplate
|
||||
{
|
||||
$this->vatId = $vatId;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getContact(): ?string
|
||||
{
|
||||
return $this->contact;
|
||||
}
|
||||
|
||||
public function setContact(?string $contact): InvoiceTemplate
|
||||
{
|
||||
$this->contact = $contact;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getPaymentDetails(): ?string
|
||||
{
|
||||
return $this->paymentDetails;
|
||||
}
|
||||
|
||||
public function setPaymentDetails(?string $paymentDetails): InvoiceTemplate
|
||||
{
|
||||
$this->paymentDetails = $paymentDetails;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
|
||||
@@ -71,9 +71,9 @@ final class MenuSubscriber implements EventSubscriberInterface
|
||||
}
|
||||
|
||||
if ($auth->isGranted('view_invoice')) {
|
||||
$menu->addItem(
|
||||
new MenuItemModel('invoice', 'menu.invoice', 'invoice', [], $this->getIcon('invoice'))
|
||||
);
|
||||
$invoice = new MenuItemModel('invoice', 'menu.invoice', 'invoice', [], $this->getIcon('invoice'));
|
||||
$invoice->setChildRoutes(['admin_invoice_template', 'admin_invoice_template_edit', 'admin_invoice_template_create']);
|
||||
$menu->addItem($invoice);
|
||||
}
|
||||
|
||||
if ($auth->isGranted('create_export')) {
|
||||
|
||||
@@ -56,6 +56,10 @@ class CustomerEditForm extends AbstractType
|
||||
'label' => 'label.company',
|
||||
'required' => false,
|
||||
])
|
||||
->add('vatId', TextType::class, [
|
||||
'label' => 'label.vat_id',
|
||||
'required' => false,
|
||||
])
|
||||
->add('contact', TextType::class, [
|
||||
'label' => 'label.contact',
|
||||
'required' => false,
|
||||
|
||||
@@ -40,14 +40,26 @@ class InvoiceTemplateForm extends AbstractType
|
||||
->add('company', TextType::class, [
|
||||
'label' => 'label.company',
|
||||
])
|
||||
->add('vatId', TextType::class, [
|
||||
'label' => 'label.vat_id',
|
||||
'required' => false,
|
||||
])
|
||||
->add('address', TextareaType::class, [
|
||||
'label' => 'label.address',
|
||||
'required' => false,
|
||||
])
|
||||
->add('contact', TextareaType::class, [
|
||||
'label' => 'label.contact',
|
||||
'required' => false,
|
||||
])
|
||||
->add('paymentTerms', TextareaType::class, [
|
||||
'label' => 'label.payment_terms',
|
||||
'required' => false,
|
||||
])
|
||||
->add('paymentDetails', TextareaType::class, [
|
||||
'label' => 'label.invoice_bank_account',
|
||||
'required' => false,
|
||||
])
|
||||
->add('dueDays', TextType::class, [
|
||||
'label' => 'label.due_days',
|
||||
])
|
||||
@@ -71,6 +83,11 @@ class InvoiceTemplateForm extends AbstractType
|
||||
'csrf_protection' => true,
|
||||
'csrf_field_name' => '_token',
|
||||
'csrf_token_id' => 'admin_invoice_template',
|
||||
'attr' => [
|
||||
'data-form-event' => 'kimai.invoiceTemplateUpdate',
|
||||
'data-msg-success' => 'action.update.success',
|
||||
'data-msg-error' => 'action.update.error',
|
||||
],
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ class InvoiceToolbarForm extends AbstractToolbarForm
|
||||
]);
|
||||
$builder->add('create', SubmitType::class, [
|
||||
'label' => 'button.print',
|
||||
'attr' => ['formtarget' => 'invoice'],
|
||||
'attr' => ['formtarget' => '_blank'],
|
||||
]);
|
||||
$builder->add('preview', SubmitType::class, [
|
||||
'label' => 'button.preview',
|
||||
|
||||
@@ -50,6 +50,7 @@ class InvoiceCalculatorType extends AbstractType
|
||||
return $renderer;
|
||||
},
|
||||
'translation_domain' => 'invoice-calculator',
|
||||
'search' => false,
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@@ -50,6 +50,7 @@ class InvoiceNumberGeneratorType extends AbstractType
|
||||
return $renderer;
|
||||
},
|
||||
'translation_domain' => 'invoice-numbergenerator',
|
||||
'search' => false,
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@@ -56,6 +56,7 @@ class InvoiceRendererType extends AbstractType
|
||||
},
|
||||
'translation_domain' => 'invoice-renderer',
|
||||
'docu_chapter' => 'invoices.html',
|
||||
'search' => false,
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ class YesNoType extends AbstractType
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'value' => true,
|
||||
'false_values' => [null, 0, false, 'false'],
|
||||
'false_values' => [null, 0, false, 'false', '', '0'],
|
||||
'required' => false,
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -114,6 +114,9 @@ trait RendererTrait
|
||||
'template.title' => $model->getTemplate()->getTitle(),
|
||||
'template.payment_terms' => $model->getTemplate()->getPaymentTerms(),
|
||||
'template.due_days' => $model->getTemplate()->getDueDays(),
|
||||
'template.vat_id' => $model->getTemplate()->getVatId(),
|
||||
'template.contact' => $model->getTemplate()->getContact(),
|
||||
'template.payment_details' => $model->getTemplate()->getPaymentDetails(),
|
||||
|
||||
'query.begin' => $this->getFormattedDateTime($model->getQuery()->getBegin()),
|
||||
'query.day' => $model->getQuery()->getBegin()->format('d'),
|
||||
@@ -164,6 +167,7 @@ trait RendererTrait
|
||||
'customer.name' => $customer->getName(),
|
||||
'customer.contact' => $customer->getContact(),
|
||||
'customer.company' => $customer->getCompany(),
|
||||
'customer.vat' => $customer->getVatId(),
|
||||
'customer.number' => $customer->getNumber(),
|
||||
'customer.country' => $customer->getCountry(),
|
||||
'customer.homepage' => $customer->getHomepage(),
|
||||
|
||||
@@ -15,35 +15,23 @@ use App\Invoice\RendererInterface;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Twig\Environment;
|
||||
|
||||
class TwigRenderer implements RendererInterface
|
||||
final class TwigRenderer implements RendererInterface
|
||||
{
|
||||
/**
|
||||
* @var Environment
|
||||
*/
|
||||
protected $twig;
|
||||
private $twig;
|
||||
|
||||
/**
|
||||
* @param Environment $twig
|
||||
*/
|
||||
public function __construct(Environment $twig)
|
||||
{
|
||||
$this->twig = $twig;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param InvoiceDocument $document
|
||||
* @return bool
|
||||
*/
|
||||
public function supports(InvoiceDocument $document): bool
|
||||
{
|
||||
return stripos($document->getFilename(), '.twig') !== false;
|
||||
return stripos($document->getFilename(), '.html.twig') !== false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param InvoiceDocument $document
|
||||
* @param InvoiceModel $model
|
||||
* @return Response
|
||||
*/
|
||||
public function render(InvoiceDocument $document, InvoiceModel $model): Response
|
||||
{
|
||||
$content = $this->twig->render('@invoice/' . basename($document->getFilename()), [
|
||||
|
||||
67
src/Migrations/Version20191116110124.php
Normal file
67
src/Migrations/Version20191116110124.php
Normal file
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the Kimai time-tracking app.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use App\Doctrine\AbstractMigration;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
|
||||
/**
|
||||
* New Vat ID columns and invoice template improvements
|
||||
*
|
||||
* @version 1.6
|
||||
*/
|
||||
final class Version20191116110124 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'New Vat ID columns and invoice template improvements';
|
||||
}
|
||||
|
||||
protected function isSupportingForeignKeys(): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function isTransactional(): bool
|
||||
{
|
||||
if ($this->isPlatformSqlite()) {
|
||||
// does fail if we use transactions, as tables are re-created and foreign keys would fail
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$customers = $schema->getTable('kimai2_customers');
|
||||
$customers->addColumn('vat_id', 'string', ['length' => 50, 'notnull' => false]);
|
||||
|
||||
$invoiceTemplates = $schema->getTable('kimai2_invoice_templates');
|
||||
$invoiceTemplates->addColumn('vat_id', 'string', ['length' => 50, 'notnull' => false, 'default' => null]);
|
||||
$invoiceTemplates->addColumn('contact', 'text', ['notnull' => false, 'default' => null]);
|
||||
$invoiceTemplates->addColumn('payment_details', 'text', ['notnull' => false, 'default' => null]);
|
||||
|
||||
$this->addSql("UPDATE kimai2_invoice_templates SET renderer = 'default' WHERE renderer IN ('export', 'open-spreadsheet', 'spreadsheet')");
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$invoiceTemplates = $schema->getTable('kimai2_invoice_templates');
|
||||
$invoiceTemplates->dropColumn('payment_details');
|
||||
$invoiceTemplates->dropColumn('contact');
|
||||
$invoiceTemplates->dropColumn('vat_id');
|
||||
|
||||
$customers = $schema->getTable('kimai2_customers');
|
||||
$customers->dropColumn('vat_id');
|
||||
}
|
||||
}
|
||||
@@ -243,6 +243,8 @@ class CustomerRepository extends EntityRepository
|
||||
$qb->expr()->orX(
|
||||
$qb->expr()->like('c.name', ':searchTerm'),
|
||||
$qb->expr()->like('c.comment', ':searchTerm'),
|
||||
$qb->expr()->like('c.company', ':searchTerm'),
|
||||
$qb->expr()->like('c.vatId', ':searchTerm'),
|
||||
$qb->expr()->like('c.number', ':searchTerm'),
|
||||
$qb->expr()->like('c.contact', ':searchTerm'),
|
||||
$qb->expr()->like('c.phone', ':searchTerm'),
|
||||
|
||||
Reference in New Issue
Block a user