added further timesheet related invoice fields (#411)
This commit is contained in:
@@ -138,17 +138,23 @@ trait RendererTrait
|
|||||||
$project = $timesheet->getProject();
|
$project = $timesheet->getProject();
|
||||||
$customer = $project->getCustomer();
|
$customer = $project->getCustomer();
|
||||||
|
|
||||||
|
$begin = $timesheet->getBegin();
|
||||||
|
$end = $timesheet->getEnd();
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'entry.description' => $description,
|
'entry.description' => $description,
|
||||||
'entry.amount' => $amount,
|
'entry.amount' => $amount,
|
||||||
'entry.rate' => $this->getFormattedMoney($hourlyRate),
|
'entry.rate' => $this->getFormattedMoney($hourlyRate),
|
||||||
'entry.total' => $this->getFormattedMoney($rate),
|
'entry.total' => $this->getFormattedMoney($rate),
|
||||||
'entry.duration' => $timesheet->getDuration(),
|
'entry.duration' => $timesheet->getDuration(),
|
||||||
'entry.begin' => $this->getFormattedDateTime($timesheet->getBegin()),
|
'entry.duration_minutes' => number_format($timesheet->getDuration() / 60),
|
||||||
'entry.begin_timestamp' => $timesheet->getBegin()->getTimestamp(),
|
'entry.begin' => $this->getFormattedDateTime($begin),
|
||||||
'entry.end' => $this->getFormattedDateTime($timesheet->getEnd()),
|
'entry.begin_time' => date("H:i", $begin->getTimestamp()),
|
||||||
'entry.end_timestamp' => $timesheet->getEnd()->getTimestamp(),
|
'entry.begin_timestamp' => $begin->getTimestamp(),
|
||||||
'entry.date' => $this->getFormattedDateTime($timesheet->getBegin()),
|
'entry.end' => $this->getFormattedDateTime($end),
|
||||||
|
'entry.end_time' => date("H:i", $end->getTimestamp()),
|
||||||
|
'entry.end_timestamp' => $end->getTimestamp(),
|
||||||
|
'entry.date' => $this->getFormattedDateTime($begin),
|
||||||
'entry.user_id' => $user->getId(),
|
'entry.user_id' => $user->getId(),
|
||||||
'entry.user_name' => $user->getUsername(),
|
'entry.user_name' => $user->getUsername(),
|
||||||
'entry.user_alias' => $user->getAlias(),
|
'entry.user_alias' => $user->getAlias(),
|
||||||
|
|||||||
94
tests/Invoice/Renderer/DebugRenderer.php
Normal file
94
tests/Invoice/Renderer/DebugRenderer.php
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
<?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\Tests\Invoice\Renderer;
|
||||||
|
|
||||||
|
use App\Entity\InvoiceDocument;
|
||||||
|
use App\Invoice\Renderer\RendererTrait;
|
||||||
|
use App\Invoice\RendererInterface;
|
||||||
|
use App\Model\InvoiceModel;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
|
||||||
|
class DebugRenderer implements RendererInterface
|
||||||
|
{
|
||||||
|
use RendererTrait;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string[]
|
||||||
|
*/
|
||||||
|
protected function getFileExtensions()
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
protected function getContentType()
|
||||||
|
{
|
||||||
|
return 'array';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param \DateTime $date
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
protected function getFormattedDateTime(\DateTime $date)
|
||||||
|
{
|
||||||
|
return $date->format('d.m.Y');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param $amount
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
protected function getFormattedMoney($amount)
|
||||||
|
{
|
||||||
|
return $amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param \DateTime $date
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
protected function getFormattedMonthName(\DateTime $date)
|
||||||
|
{
|
||||||
|
return $date->format('m');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param $seconds
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
protected function getFormattedDuration($seconds)
|
||||||
|
{
|
||||||
|
return $seconds;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render the given InvoiceDocument with the data from the InvoiceModel into a stupid array for testing only.
|
||||||
|
*
|
||||||
|
* @param InvoiceDocument $document
|
||||||
|
* @param InvoiceModel $model
|
||||||
|
* @return Response
|
||||||
|
*/
|
||||||
|
public function render(InvoiceDocument $document, InvoiceModel $model): Response
|
||||||
|
{
|
||||||
|
$result = [
|
||||||
|
'model' => $this->modelToReplacer($model),
|
||||||
|
'entries' => [],
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach ($model->getCalculator()->getEntries() as $entry) {
|
||||||
|
$result['entries'][] = $this->timesheetToArray($entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Response(json_encode($result));
|
||||||
|
}
|
||||||
|
}
|
||||||
130
tests/Invoice/Renderer/DebugRendererTest.php
Normal file
130
tests/Invoice/Renderer/DebugRendererTest.php
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
<?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\Tests\Invoice\Renderer;
|
||||||
|
|
||||||
|
use App\Entity\InvoiceDocument;
|
||||||
|
use App\Model\InvoiceModel;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
|
||||||
|
class DebugRendererTest extends AbstractRendererTest
|
||||||
|
{
|
||||||
|
public function getTestModel()
|
||||||
|
{
|
||||||
|
yield [$this->getInvoiceModel(), '1,947.99', 5, 5, 1, 2, 2];
|
||||||
|
yield [$this->getInvoiceModelOneEntry(), '293.27', 1, 1, 0, 1, 0];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dataProvider getTestModel
|
||||||
|
*/
|
||||||
|
public function testRender(InvoiceModel $model, $expectedRate, $expectedRows, $expectedDescriptions, $expectedUser1, $expectedUser2, $expectedUser3)
|
||||||
|
{
|
||||||
|
$document = new InvoiceDocument(new \SplFileInfo(__DIR__ . '/DebugRenderer.php'));
|
||||||
|
$sut = new DebugRenderer();
|
||||||
|
/** @var Response $response */
|
||||||
|
$response = $sut->render($document, $model);
|
||||||
|
$data = json_decode($response->getContent(), true);
|
||||||
|
|
||||||
|
$this->assertModelStructure($data['model']);
|
||||||
|
$rows = $data['entries'];
|
||||||
|
$this->assertEquals($expectedRows, count($rows));
|
||||||
|
|
||||||
|
foreach($rows as $row) {
|
||||||
|
$this->assertEntryStructure($row);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO check values or formats?
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function assertModelStructure(array $model)
|
||||||
|
{
|
||||||
|
$keys = [
|
||||||
|
'invoice.due_date',
|
||||||
|
'invoice.date',
|
||||||
|
'invoice.number',
|
||||||
|
'invoice.currency',
|
||||||
|
'invoice.vat',
|
||||||
|
'invoice.tax',
|
||||||
|
'invoice.total_time',
|
||||||
|
'invoice.total',
|
||||||
|
'invoice.subtotal',
|
||||||
|
'template.name',
|
||||||
|
'template.company',
|
||||||
|
'template.address',
|
||||||
|
'template.title',
|
||||||
|
'template.payment_terms',
|
||||||
|
'template.due_days',
|
||||||
|
'query.begin',
|
||||||
|
'query.end',
|
||||||
|
'query.month',
|
||||||
|
'query.year',
|
||||||
|
'customer.address',
|
||||||
|
'customer.name',
|
||||||
|
'customer.contact',
|
||||||
|
'customer.company',
|
||||||
|
'customer.country',
|
||||||
|
'customer.number',
|
||||||
|
'customer.homepage',
|
||||||
|
'customer.comment',
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach($keys as $key) {
|
||||||
|
$this->assertArrayHasKey($key, $model);
|
||||||
|
}
|
||||||
|
|
||||||
|
$expectedKeys = array_merge([], $keys);
|
||||||
|
sort($expectedKeys);
|
||||||
|
$givenKeys = array_keys($model);
|
||||||
|
sort($givenKeys);
|
||||||
|
|
||||||
|
$this->assertEquals(count($keys), count($givenKeys));
|
||||||
|
$this->assertEquals($expectedKeys, $givenKeys);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function assertEntryStructure(array $model)
|
||||||
|
{
|
||||||
|
$keys = [
|
||||||
|
'entry.description',
|
||||||
|
'entry.amount',
|
||||||
|
'entry.rate',
|
||||||
|
'entry.total',
|
||||||
|
'entry.duration',
|
||||||
|
'entry.duration_minutes',
|
||||||
|
'entry.begin',
|
||||||
|
'entry.begin_time',
|
||||||
|
'entry.begin_timestamp',
|
||||||
|
'entry.end',
|
||||||
|
'entry.end_time',
|
||||||
|
'entry.end_timestamp',
|
||||||
|
'entry.date',
|
||||||
|
'entry.user_id',
|
||||||
|
'entry.user_name',
|
||||||
|
'entry.user_alias',
|
||||||
|
'entry.activity',
|
||||||
|
'entry.activity_id',
|
||||||
|
'entry.project',
|
||||||
|
'entry.customer',
|
||||||
|
'entry.project_id',
|
||||||
|
'entry.customer_id',
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach($keys as $key) {
|
||||||
|
$this->assertArrayHasKey($key, $model);
|
||||||
|
}
|
||||||
|
|
||||||
|
$expectedKeys = array_merge([], $keys);
|
||||||
|
sort($expectedKeys);
|
||||||
|
$givenKeys = array_keys($model);
|
||||||
|
sort($givenKeys);
|
||||||
|
|
||||||
|
$this->assertEquals(count($keys), count($givenKeys));
|
||||||
|
$this->assertEquals($expectedKeys, $givenKeys);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -93,61 +93,64 @@ See below in `Template variables` to find out which variables you can use in you
|
|||||||
|
|
||||||
You can use the following global variables in your templates:
|
You can use the following global variables in your templates:
|
||||||
|
|
||||||
| Key | Description |
|
| Key | Description | Example |
|
||||||
|---|---|
|
|---|---|---|
|
||||||
| ${invoice.due_date} | The due date for the invoice payment |
|
| ${invoice.due_date} | The due date for the invoice payment | |
|
||||||
| ${invoice.date} | The creation date of this invoice |
|
| ${invoice.date} | The creation date of this invoice | |
|
||||||
| ${invoice.number} | The generated invoice number |
|
| ${invoice.number} | The generated invoice number | |
|
||||||
| ${invoice.currency} | The invoice currency |
|
| ${invoice.currency} | The invoice currency | |
|
||||||
| ${invoice.total_time} | The total working time (entries with a fixed rate are always calculated with 1) |
|
| ${invoice.total_time} | The total working time (entries with a fixed rate are always calculated with 1) | |
|
||||||
| ${invoice.total} | The invoices total (including tax) |
|
| ${invoice.total} | The invoices total (including tax) | |
|
||||||
| ${invoice.subtotal} | The invoices subtotal (excluding tax) |
|
| ${invoice.subtotal} | The invoices subtotal (excluding tax) | |
|
||||||
| ${invoice.vat} | The VAT in percent for this invoice |
|
| ${invoice.vat} | The VAT in percent for this invoice | |
|
||||||
| ${invoice.tax} | The tax of the invoice amount |
|
| ${invoice.tax} | The tax of the invoice amount | |
|
||||||
| ${template.name} | The invoice name, as configured in your template |
|
| ${template.name} | The invoice name, as configured in your template | |
|
||||||
| ${template.company} | The company name, as configured in your template |
|
| ${template.company} | The company name, as configured in your template | |
|
||||||
| ${template.address} | The invoicing address, as configured in your template |
|
| ${template.address} | The invoicing address, as configured in your template | |
|
||||||
| ${template.title} | The invoice title, as configured in your template |
|
| ${template.title} | The invoice title, as configured in your template | |
|
||||||
| ${template.payment_terms} | Your payment terms, usage might differ from template to template |
|
| ${template.payment_terms} | Your payment terms, usage might differ from template to template | |
|
||||||
| ${template.due_days} | The amount of days for the payment, starting with the day of creating the invoice |
|
| ${template.due_days} | The amount of days for the payment, starting with the day of creating the invoice | |
|
||||||
| ${query.begin} | The query begin as formatted short date |
|
| ${query.begin} | The query begin as formatted short date | |
|
||||||
| ${query.end} | The query end as formatted short date |
|
| ${query.end} | The query end as formatted short date | |
|
||||||
| ${query.month} | The month for this query (begin date) |
|
| ${query.month} | The month for this query (begin date) | |
|
||||||
| ${query.year} | The year for this query (begin date) |
|
| ${query.year} | The year for this query (begin date) | |
|
||||||
| ${customer.address} | The customer address |
|
| ${customer.address} | The customer address | |
|
||||||
| ${customer.name} | The customer name |
|
| ${customer.name} | The customer name | |
|
||||||
| ${customer.contact} | The customer contac |
|
| ${customer.contact} | The customer contact | |
|
||||||
| ${customer.company} | The customer company |
|
| ${customer.company} | The customer company | |
|
||||||
| ${customer.number} | The customer number |
|
| ${customer.number} | The customer number | |
|
||||||
| ${customer.country} | The customer country |
|
| ${customer.country} | The customer country | |
|
||||||
| ${customer.homepage} | The customer homepage |
|
| ${customer.homepage} | The customer homepage | |
|
||||||
| ${customer.comment} | The customer comment |
|
| ${customer.comment} | The customer comment | |
|
||||||
|
|
||||||
### Timesheet entry variables
|
### Timesheet entry variables
|
||||||
|
|
||||||
For each timesheet entry you can use the following variables:
|
For each timesheet entry you can use the variables from the following table.
|
||||||
|
|
||||||
| Key | Description |
|
| Key | Description | Example |
|
||||||
|---|---|
|
|---|---|---|
|
||||||
| ${entry.description} | The entries description |
|
| ${entry.description} | The entries description | _foo bar_ |
|
||||||
| ${entry.amount} | The amount for this entry (normally the amount of hours) |
|
| ${entry.amount} | The format duration/amount for this entry | 02:47 h |
|
||||||
| ${entry.rate} | The rate for one unit of the entry (normally one hour) |
|
| ${entry.rate} | The rate for one unit of the entry (normally one hour) | 100 |
|
||||||
| ${entry.total} | The total rate for this entry |
|
| ${entry.total} | The total rate for this entry | 278,33 |
|
||||||
| ${entry.duration} | The duration in seconds |
|
| ${entry.duration} | The duration in seconds | 10020 |
|
||||||
| ${entry.begin} | The begin date - _format may change and include the time in the future_ |
|
| ${entry.duration_minutes} | The duration in minutes with no decimals | 167 |
|
||||||
| ${entry.begin_timestamp} | The timestamp for the begin of this entry |
|
| ${entry.begin} | The begin date (format depends on the users language) | 27.10.2018 |
|
||||||
| ${entry.end} | The begin date - _format may change and include the time in the future_ |
|
| ${entry.begin_time} | The formatted time for the begin of this entry | 14:57 |
|
||||||
| ${entry.end_timestamp} | The timestamp for the end of this entry |
|
| ${entry.begin_timestamp} | The timestamp for the begin of this entry | 1542016273 |
|
||||||
| ${entry.date} | The start date when this record was created |
|
| ${entry.end} | The begin date (format depends on the users language) | 27.10.2018 |
|
||||||
| ${entry.user_id} | The user ID |
|
| ${entry.end_time} | The formatted time for the end of this entry | 17:44 |
|
||||||
| ${entry.user_name} | The username |
|
| ${entry.end_timestamp} | The timestamp for the end of this entry | 1542016273 |
|
||||||
| ${entry.user_alias} | The user alias |
|
| ${entry.date} | The start date when this record was created | 27.10.2018 |
|
||||||
| ${entry.activity} | Activity name |
|
| ${entry.user_id} | The user ID | 1 |
|
||||||
| ${entry.activity_id} | Activity ID |
|
| ${entry.user_name} | The username | susan_super |
|
||||||
| ${entry.project} | Project name |
|
| ${entry.user_alias} | The user alias | Susan Miller |
|
||||||
| ${entry.project_id} | Project ID |
|
| ${entry.activity} | Activity name | Post production |
|
||||||
| ${entry.customer} | Customer name |
|
| ${entry.activity_id} | Activity ID | 124 |
|
||||||
| ${entry.customer_id} | Customer ID |
|
| ${entry.project} | Project name | Nemesis |
|
||||||
|
| ${entry.project_id} | Project ID | 10 |
|
||||||
|
| ${entry.customer} | Customer name | Acme Studios |
|
||||||
|
| ${entry.customer_id} | Customer ID | 3 |
|
||||||
|
|
||||||
## Configure search path
|
## Configure search path
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user