add subtotal and payment date to invoices (#2450)

This commit is contained in:
Philipp
2021-03-29 00:51:34 +02:00
committed by GitHub
parent 7029bde555
commit d84beb957c
11 changed files with 222 additions and 5 deletions

View File

@@ -17,6 +17,7 @@ use App\Export\Spreadsheet\AnnotatedObjectExporter;
use App\Export\Spreadsheet\Writer\BinaryFileResponseWriter;
use App\Export\Spreadsheet\Writer\XlsxWriter;
use App\Form\InvoiceDocumentUploadForm;
use App\Form\InvoicePaymentDateForm;
use App\Form\InvoiceTemplateForm;
use App\Form\Toolbar\InvoiceArchiveForm;
use App\Form\Toolbar\InvoiceToolbarForm;
@@ -228,10 +229,22 @@ final class InvoiceController extends AbstractController
}
/**
* @Route(path="/change-status/{id}/{status}", name="admin_invoice_status", methods={"GET"})
* @Route(path="/change-status/{id}/{status}", name="admin_invoice_status", methods={"GET", "POST"})
*/
public function changeStatusAction(Invoice $invoice, string $status): Response
public function changeStatusAction(Invoice $invoice, string $status, Request $request): Response
{
if ($status === Invoice::STATUS_PAID) {
$form = $this->createPaymentDateForm($invoice, $request);
$form->handleRequest($request);
if (!$form->isSubmitted() || !$form->isValid()) {
return $this->render('invoice/payment_date_edit.html.twig', [
'invoice' => $invoice,
'form' => $form->createView()
]);
}
}
try {
$this->service->changeInvoiceStatus($invoice, $status);
$this->flashSuccess('action.update.success');
@@ -512,4 +525,17 @@ final class InvoiceController extends AbstractController
'method' => 'POST'
]);
}
private function createPaymentDateForm(Invoice $invoice, Request $request): FormInterface
{
if (null === $invoice->getPaymentDate()) {
$invoice->setPaymentDate($this->getDateTimeFactory()->createDateTime());
}
return $this->createForm(InvoicePaymentDateForm::class, $invoice, [
'action' => $request->getUri(),
'method' => 'POST',
'timezone' => $this->getDateTimeFactory()->getTimezone()->getName(),
]);
}
}

View File

@@ -26,10 +26,11 @@ use Symfony\Component\Validator\Constraints as Assert;
* @UniqueEntity("invoiceNumber")
* @UniqueEntity("invoiceFilename")
*
* @Exporter\Order({"id", "createdAt", "invoiceNumber", "status", "customer", "total", "tax", "currency", "vat", "dueDays", "dueDate", "user", "invoiceFilename"})
* @Exporter\Order({"id", "createdAt", "invoiceNumber", "status", "customer", "subtotal", "total", "tax", "currency", "vat", "dueDays", "dueDate", "paymentDate", "user", "invoiceFilename"})
* @Exporter\Expose("customer", label="label.customer", exp="object.getCustomer() === null ? null : object.getCustomer().getName()")
* @Exporter\Expose("dueDate", label="invoice.due_days", type="datetime", exp="object.getDueDate() === null ? null : object.getDueDate()")
* @Exporter\Expose("user", label="label.username", type="string", exp="object.getUser() === null ? null : object.getUser().getDisplayName()")
* @Exporter\Expose("paymentDate", label="invoice.payment_date", type="date", exp="object.getPaymentDate() === null ? null : object.getPaymentDate()")
*/
class Invoice
{
@@ -172,6 +173,13 @@ class Invoice
*/
private $localized = false;
/**
* @var \DateTime
*
* @ORM\Column(name="payment_date", type="date", nullable=true)
*/
private $paymentDate;
public function getId(): ?int
{
return $this->id;
@@ -265,6 +273,7 @@ class Invoice
public function setIsNew(): Invoice
{
$this->setPaymentDate(null);
$this->status = self::STATUS_NEW;
return $this;
@@ -277,6 +286,7 @@ class Invoice
public function setIsPending(): Invoice
{
$this->setPaymentDate(null);
$this->status = self::STATUS_PENDING;
return $this;
@@ -318,4 +328,25 @@ class Invoice
{
return $this->invoiceFilename;
}
/**
* @Exporter\Expose(label="invoice.subtotal", type="float", name="subtotal")
* @return float|null
*/
public function getSubtotal(): ?float
{
return $this->total - $this->tax;
}
public function getPaymentDate(): ?\DateTime
{
return $this->paymentDate;
}
public function setPaymentDate(?\DateTime $paymentDate): Invoice
{
$this->paymentDate = $paymentDate;
return $this;
}
}

View File

@@ -30,10 +30,10 @@ class InvoiceSubscriber extends AbstractActionsSubscriber
return;
}
if ($invoice->isNew()) {
if ($invoice->isNew() || $invoice->isPaid()) {
$event->addAction('invoice.pending', ['url' => $this->path('admin_invoice_status', ['id' => $invoice->getId(), 'status' => 'pending'])]);
} elseif ($invoice->isPending()) {
$event->addAction('invoice.paid', ['url' => $this->path('admin_invoice_status', ['id' => $invoice->getId(), 'status' => 'paid'])]);
$event->addAction('invoice.paid', ['url' => $this->path('admin_invoice_status', ['id' => $invoice->getId(), 'status' => 'paid']), 'class' => 'modal-ajax-form']);
}
$event->addAction('download', ['url' => $this->path('admin_invoice_download', ['id' => $invoice->getId()]), 'target' => '_blank']);

View File

@@ -0,0 +1,50 @@
<?php
/*
* 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 App\Form;
use App\Entity\Invoice;
use App\Form\Type\DatePickerType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class InvoicePaymentDateForm extends AbstractType
{
/**
* {@inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$dateTimeOptions = [
'model_timezone' => $options['timezone'],
'view_timezone' => $options['timezone'],
];
$builder
->add('paymentDate', DatePickerType::class, array_merge($dateTimeOptions, [
'label' => 'invoice.payment_date',
'required' => true,
]));
}
/**
* {@inheritdoc}
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Invoice::class,
'timezone' => date_default_timezone_get(),
'attr' => [
'data-form-event' => 'kimai.invoiceUpdate'
],
]);
}
}

View File

@@ -0,0 +1,40 @@
<?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 Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Update invoice with payment date
*
* @version 1.14
*/
final class Version20210320162820 extends AbstractMigration
{
public function getDescription(): string
{
return 'Update invoice with payment date';
}
public function up(Schema $schema): void
{
$invoices = $schema->getTable('kimai2_invoices');
$invoices->addColumn('payment_date', 'date', ['default' => null, 'notnull' => false]);
}
public function down(Schema $schema): void
{
$invoices = $schema->getTable('kimai2_invoices');
$invoices->dropColumn('payment_date');
}
}