added pdf template and other invoice improvements (#1693)
This commit is contained in:
4
.github/workflows/linting.yaml
vendored
4
.github/workflows/linting.yaml
vendored
@@ -19,10 +19,10 @@ jobs:
|
|||||||
php-version: ${{ matrix.php }}
|
php-version: ${{ matrix.php }}
|
||||||
coverage: none
|
coverage: none
|
||||||
extensions: intl
|
extensions: intl
|
||||||
tools: cs2pr:1.0.1
|
tools: cs2pr:1.1.0
|
||||||
- run: composer install --no-progress
|
- run: composer install --no-progress
|
||||||
- run: composer validate --no-check-all --strict
|
- run: composer validate --no-check-all --strict
|
||||||
- run: vendor/bin/php-cs-fixer fix --dry-run --verbose --config=.php_cs.dist --using-cache=no --show-progress=none --format=checkstyle | cs2pr
|
- run: vendor/bin/php-cs-fixer fix --dry-run --verbose --config=.php_cs.dist --using-cache=no --show-progress=none --format=checkstyle | cs2pr
|
||||||
- run: vendor/bin/phpstan analyse src -c phpstan.neon --level=4 --no-progress --error-format=checkstyle | cs2pr
|
- run: vendor/bin/phpstan analyse src -c phpstan.neon --level=5 --no-progress --error-format=checkstyle | cs2pr
|
||||||
- run: vendor/bin/phpstan analyse tests -c tests/phpstan.neon --level=4 --no-progress --error-format=checkstyle | cs2pr
|
- run: vendor/bin/phpstan analyse tests -c tests/phpstan.neon --level=4 --no-progress --error-format=checkstyle | cs2pr
|
||||||
- run: composer kimai:code-lint
|
- run: composer kimai:code-lint
|
||||||
|
|||||||
14
UPGRADING.md
14
UPGRADING.md
@@ -12,23 +12,29 @@ Perform EACH version specific task between your version and the new one, otherwi
|
|||||||
|
|
||||||
- Invoice renderer `CSV` was removed
|
- Invoice renderer `CSV` was removed
|
||||||
|
|
||||||
|
### Developer
|
||||||
|
|
||||||
|
- **BC break**: interface method signature `HtmlToPdfConverter::convertToPdf` changed
|
||||||
|
|
||||||
## [1.9](https://github.com/kevinpapst/kimai2/releases/tag/1.9)
|
## [1.9](https://github.com/kevinpapst/kimai2/releases/tag/1.9)
|
||||||
|
|
||||||
**New database tables and fields were created, don't forget to [run the updater](https://www.kimai.org/documentation/updates.html).**
|
**New database tables and fields were created, don't forget to [run the updater](https://www.kimai.org/documentation/updates.html).**
|
||||||
|
|
||||||
- The directory `var/data/invoices/` will be used to store archived invoice files (check file permissions)
|
- The directory `var/data/invoices/` will be used to store archived invoice files (check file permissions)
|
||||||
- The default invoice number format changed. If you want to use the old one: configure `{date}` as format - see [invoice documentation](https://www.kimai.org/documentation/invoices.html)
|
- The default invoice number format changed. If you want to use the old one: configure `{date}` as format - see [invoice documentation](https://www.kimai.org/documentation/invoices.html)
|
||||||
- HTML invoice templates are now treated like other files and offered as download. If you are using relative URLs for including
|
- HTML invoice templates are now treated like other files and offered as download. If you are using relative URLs for including assets (CSS, images) you need to either inline them (see the default templates) or use absolute URLs
|
||||||
assets (CSS, images) you need to either inline them (see the default templates) or use absolute URLs.
|
- Invoice templates that use the templates variables `${activity.X}` or `${project.X}` should be checked and possibly adapted, as multi-select is now possible for filtering
|
||||||
- Invoice templates that use the templates variables `${activity.X}` or `${project.X}` should be checked and possibly adapted, as multi-select is now possible for filtering
|
|
||||||
- Invoice templates have access to all meta-fields as variables, not only the ones marked as visible
|
- Invoice templates have access to all meta-fields as variables, not only the ones marked as visible
|
||||||
|
- Rates configuration/structure changed for customer, project and activity
|
||||||
|
- **Invoice templates**: rates variables were removed
|
||||||
|
|
||||||
Permission changes:
|
Permission changes:
|
||||||
- `history_invoice` - NEW: grants all features for the new invoice archive (by default for all admins)
|
- `history_invoice` - NEW: grants all features for the new invoice archive (by default for all admins)
|
||||||
|
|
||||||
### Developer
|
### Developer
|
||||||
|
|
||||||
- BC break: `InvoiceItemInterface` has new methods `getType()` and `getCategory()`
|
- **BC break**: `InvoiceItemInterface` has new methods `getType()` and `getCategory()`
|
||||||
|
- **BC break**: API fields changed - see new `/rates` endpoints
|
||||||
|
|
||||||
## [1.8](https://github.com/kevinpapst/kimai2/releases/tag/1.8)
|
## [1.8](https://github.com/kevinpapst/kimai2/releases/tag/1.8)
|
||||||
|
|
||||||
|
|||||||
2
assets/invoice-pdf.js
Normal file
2
assets/invoice-pdf.js
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
|
||||||
|
require('./sass/invoice-pdf.scss');
|
||||||
104
assets/sass/invoice-pdf.scss
Normal file
104
assets/sass/invoice-pdf.scss
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: sans-serif;
|
||||||
|
font-size: 10pt;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
table, tr, td, p {
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
th {
|
||||||
|
border: none;
|
||||||
|
font-weight: bold;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
td {
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
.text-right {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
.text-nowrap {
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
.date {
|
||||||
|
color: #666;
|
||||||
|
font-size: 80%;
|
||||||
|
text-align: right;
|
||||||
|
padding-top: 6px;
|
||||||
|
}
|
||||||
|
.wrapper {
|
||||||
|
margin: 4px;
|
||||||
|
}
|
||||||
|
table.addresses, p {
|
||||||
|
line-height: 14pt;
|
||||||
|
}
|
||||||
|
/* The address table */
|
||||||
|
table.addresses {
|
||||||
|
width: 100%;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
/* The PDF header */
|
||||||
|
table.header {
|
||||||
|
border-spacing: 0;
|
||||||
|
padding-bottom: 4px;
|
||||||
|
border-bottom: 1px solid #ccc;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
table.header .title {
|
||||||
|
font-weight: normal;
|
||||||
|
font-size: 140%;
|
||||||
|
}
|
||||||
|
/* The PDF footer */
|
||||||
|
table.footer {
|
||||||
|
border-spacing: 0;
|
||||||
|
border-top: 1px solid #ccc;
|
||||||
|
font-size: 80%;
|
||||||
|
padding-top: 8px;
|
||||||
|
width: 100%;
|
||||||
|
line-height: 14px;
|
||||||
|
}
|
||||||
|
table.footer td {
|
||||||
|
vertical-align: bottom;
|
||||||
|
}
|
||||||
|
/* The invoice items list */
|
||||||
|
table.items {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
margin-top: 50px;
|
||||||
|
margin-bottom: 50px;
|
||||||
|
}
|
||||||
|
table.items thead th,
|
||||||
|
table.items tbody td {
|
||||||
|
border-bottom: 1px solid #ccc;
|
||||||
|
}
|
||||||
|
table.items thead th.first,
|
||||||
|
table.items tbody td.first {
|
||||||
|
padding-left: 0;
|
||||||
|
}
|
||||||
|
table.items thead th.last,
|
||||||
|
table.items tbody td.last,
|
||||||
|
table.items tfoot td.last{
|
||||||
|
padding-right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
table.items td,
|
||||||
|
table.items th {
|
||||||
|
padding: 8px 6px;
|
||||||
|
}
|
||||||
|
table.items tfoot td,
|
||||||
|
table.items tfoot th {
|
||||||
|
padding: 20px 0 0 0;
|
||||||
|
line-height: 1px;
|
||||||
|
}
|
||||||
|
table.items tr.odd {
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
}
|
||||||
@@ -38,7 +38,7 @@ body.invoice_print {
|
|||||||
max-width: 210mm;
|
max-width: 210mm;
|
||||||
box-shadow: 0 0 20px rgba(80,80,80,0.7);
|
box-shadow: 0 0 20px rgba(80,80,80,0.7);
|
||||||
background: #fff;
|
background: #fff;
|
||||||
border: 1px solid #f4f4f4;
|
border: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.page-header {
|
.page-header {
|
||||||
|
|||||||
@@ -17,7 +17,16 @@
|
|||||||
"build/invoice.50473330.js"
|
"build/invoice.50473330.js"
|
||||||
],
|
],
|
||||||
"css": [
|
"css": [
|
||||||
"build/invoice.3764169b.css"
|
"build/invoice.13d8ef4e.css"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"invoice-pdf": {
|
||||||
|
"js": [
|
||||||
|
"build/runtime.664a9501.js",
|
||||||
|
"build/invoice-pdf.1db2b744.js"
|
||||||
|
],
|
||||||
|
"css": [
|
||||||
|
"build/invoice-pdf.340721a4.css"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"chart": {
|
"chart": {
|
||||||
@@ -33,10 +42,10 @@
|
|||||||
"build/runtime.664a9501.js",
|
"build/runtime.664a9501.js",
|
||||||
"build/0.f7fae2b9.js",
|
"build/0.f7fae2b9.js",
|
||||||
"build/1.c24a9c6f.js",
|
"build/1.c24a9c6f.js",
|
||||||
"build/calendar.d2540934.js"
|
"build/calendar.33aa6a16.js"
|
||||||
],
|
],
|
||||||
"css": [
|
"css": [
|
||||||
"build/calendar.8ede69b5.css"
|
"build/calendar.c8f3c1fd.css"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -47,10 +56,12 @@
|
|||||||
"build/app.d8c0b5e5.js": "sha384-VwB8dUrwHl8kZCpZjOlqXKfC39MtJNY/TSQj0oEwyFuqKGhrf0LS5++I06EOZo9b",
|
"build/app.d8c0b5e5.js": "sha384-VwB8dUrwHl8kZCpZjOlqXKfC39MtJNY/TSQj0oEwyFuqKGhrf0LS5++I06EOZo9b",
|
||||||
"build/app.a2628675.css": "sha384-0MsxtEEys+bTKR6Qi9LvMRg21TPh2x7JxTdVFTmySyjct4BpB9xuffwyk1BDsZDe",
|
"build/app.a2628675.css": "sha384-0MsxtEEys+bTKR6Qi9LvMRg21TPh2x7JxTdVFTmySyjct4BpB9xuffwyk1BDsZDe",
|
||||||
"build/invoice.50473330.js": "sha384-2BXic5Sgorf2tXai6zSAN4wLY2dbg06L03/xMKW6itMcszvtnRArKzfBh6DNcF3f",
|
"build/invoice.50473330.js": "sha384-2BXic5Sgorf2tXai6zSAN4wLY2dbg06L03/xMKW6itMcszvtnRArKzfBh6DNcF3f",
|
||||||
"build/invoice.3764169b.css": "sha384-oILxBeIu3sNXxVnxhHmyHxqvLY2Rm5P2lzuJVtbxBIcnSdLS31SUSW6nVWNej1eg",
|
"build/invoice.13d8ef4e.css": "sha384-B6RN/wZJToSBCZk2JeLokIqWEhbh+Eb9arYbt9dM+YoC2Z6PnCeTwTqSGyexWWJh",
|
||||||
|
"build/invoice-pdf.1db2b744.js": "sha384-bSdIeRCtEJiYYuc2reb0e5CpJ1Kbd1lQNEkElMTiq1SX0IINzdwJJYf6WnCcHrNC",
|
||||||
|
"build/invoice-pdf.340721a4.css": "sha384-+dk8iSafBIWj8bKpeEAkhkOR6G6o2OyL1ZeGuaIHuECtidl5cqoGbLSw+yKfSc0e",
|
||||||
"build/2.a47ad919.js": "sha384-1h2P/tsl+bh8qgJd7S9irQHKuns7UATVxeFGLOCH85GPFXfAdRzgzx3nk5MrxzrV",
|
"build/2.a47ad919.js": "sha384-1h2P/tsl+bh8qgJd7S9irQHKuns7UATVxeFGLOCH85GPFXfAdRzgzx3nk5MrxzrV",
|
||||||
"build/chart.c9943f57.js": "sha384-I57c9DtU3AOG2kzKqIZkIu0hi1aGYHRZ5QG4LKC9+9slzJnAMttPGXoL2cQG3m6y",
|
"build/chart.c9943f57.js": "sha384-I57c9DtU3AOG2kzKqIZkIu0hi1aGYHRZ5QG4LKC9+9slzJnAMttPGXoL2cQG3m6y",
|
||||||
"build/calendar.d2540934.js": "sha384-+lalaW5vVwveu7esC/1Ys27a50kO9Nd5MiWGi0yih3GNNliOKE+hY9X0Eq47RflV",
|
"build/calendar.33aa6a16.js": "sha384-+lalaW5vVwveu7esC/1Ys27a50kO9Nd5MiWGi0yih3GNNliOKE+hY9X0Eq47RflV",
|
||||||
"build/calendar.8ede69b5.css": "sha384-ta519W5+NY1a8FBh7rD4zORYTfUXCYTLci0Z25nf4BfodmuXqyPVNEe7dSe2/fRd"
|
"build/calendar.c8f3c1fd.css": "sha384-ta519W5+NY1a8FBh7rD4zORYTfUXCYTLci0Z25nf4BfodmuXqyPVNEe7dSe2/fRd"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
1
public/build/invoice-pdf.1db2b744.js
Normal file
1
public/build/invoice-pdf.1db2b744.js
Normal file
@@ -0,0 +1 @@
|
|||||||
|
(window.webpackJsonp=window.webpackJsonp||[]).push([["invoice-pdf"],{G2iK:function(n,i,o){},"ls+r":function(n,i,o){o("G2iK")}},[["ls+r","runtime"]]]);
|
||||||
1
public/build/invoice-pdf.340721a4.css
Normal file
1
public/build/invoice-pdf.340721a4.css
Normal file
@@ -0,0 +1 @@
|
|||||||
|
body{font-family:sans-serif;font-size:10pt}body,p,table,td,tr{margin:0;padding:0}th{border:none;font-weight:700;text-align:left}td{vertical-align:top}.text-right{text-align:right}.text-nowrap{white-space:nowrap}.date{color:#666;font-size:80%;text-align:right;padding-top:6px}.wrapper{margin:4px}p,table.addresses{line-height:14pt}table.addresses{width:100%;margin-bottom:30px}table.header{border-spacing:0;padding-bottom:4px;border-bottom:1px solid #ccc;width:100%}table.header .title{font-weight:400;font-size:140%}table.footer{border-spacing:0;border-top:1px solid #ccc;font-size:80%;padding-top:8px;width:100%;line-height:14px}table.footer td{vertical-align:bottom}table.items{width:100%;border-collapse:collapse;margin-top:50px;margin-bottom:50px}table.items tbody td,table.items thead th{border-bottom:1px solid #ccc}table.items tbody td.first,table.items thead th.first{padding-left:0}table.items tbody td.last,table.items tfoot td.last,table.items thead th.last{padding-right:0}table.items td,table.items th{padding:8px 6px}table.items tfoot td,table.items tfoot th{padding:20px 0 0;line-height:1px}table.items tr.odd{background-color:#f5f5f5}
|
||||||
File diff suppressed because one or more lines are too long
@@ -4,11 +4,13 @@
|
|||||||
"build/2.a47ad919.js": "build/2.a47ad919.js",
|
"build/2.a47ad919.js": "build/2.a47ad919.js",
|
||||||
"build/app.css": "build/app.a2628675.css",
|
"build/app.css": "build/app.a2628675.css",
|
||||||
"build/app.js": "build/app.d8c0b5e5.js",
|
"build/app.js": "build/app.d8c0b5e5.js",
|
||||||
"build/calendar.css": "build/calendar.8ede69b5.css",
|
"build/calendar.css": "build/calendar.c8f3c1fd.css",
|
||||||
"build/calendar.js": "build/calendar.d2540934.js",
|
"build/calendar.js": "build/calendar.33aa6a16.js",
|
||||||
"build/chart.js": "build/chart.c9943f57.js",
|
"build/chart.js": "build/chart.c9943f57.js",
|
||||||
"build/invoice.css": "build/invoice.3764169b.css",
|
"build/invoice.css": "build/invoice.13d8ef4e.css",
|
||||||
"build/invoice.js": "build/invoice.50473330.js",
|
"build/invoice.js": "build/invoice.50473330.js",
|
||||||
|
"build/invoice-pdf.css": "build/invoice-pdf.340721a4.css",
|
||||||
|
"build/invoice-pdf.js": "build/invoice-pdf.1db2b744.js",
|
||||||
"build/runtime.js": "build/runtime.664a9501.js",
|
"build/runtime.js": "build/runtime.664a9501.js",
|
||||||
"build/fonts/fa-brands-400.eot": "build/fonts/fa-brands-400.088a34f7.eot",
|
"build/fonts/fa-brands-400.eot": "build/fonts/fa-brands-400.088a34f7.eot",
|
||||||
"build/fonts/fa-brands-400.ttf": "build/fonts/fa-brands-400.273dc9bf.ttf",
|
"build/fonts/fa-brands-400.ttf": "build/fonts/fa-brands-400.273dc9bf.ttf",
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ use App\Entity\Project;
|
|||||||
use App\Invoice\ServiceInvoice;
|
use App\Invoice\ServiceInvoice;
|
||||||
use App\Repository\CustomerRepository;
|
use App\Repository\CustomerRepository;
|
||||||
use App\Repository\InvoiceTemplateRepository;
|
use App\Repository\InvoiceTemplateRepository;
|
||||||
|
use App\Repository\ProjectRepository;
|
||||||
use App\Repository\Query\InvoiceQuery;
|
use App\Repository\Query\InvoiceQuery;
|
||||||
use App\Repository\Query\TimesheetQuery;
|
use App\Repository\Query\TimesheetQuery;
|
||||||
use App\Repository\TimesheetRepository;
|
use App\Repository\TimesheetRepository;
|
||||||
@@ -46,6 +47,10 @@ class InvoiceCreateCommand extends Command
|
|||||||
* @var CustomerRepository
|
* @var CustomerRepository
|
||||||
*/
|
*/
|
||||||
private $customerRepository;
|
private $customerRepository;
|
||||||
|
/**
|
||||||
|
* @var ProjectRepository
|
||||||
|
*/
|
||||||
|
private $projectRepository;
|
||||||
/**
|
/**
|
||||||
* @var InvoiceTemplateRepository
|
* @var InvoiceTemplateRepository
|
||||||
*/
|
*/
|
||||||
@@ -67,6 +72,7 @@ class InvoiceCreateCommand extends Command
|
|||||||
ServiceInvoice $serviceInvoice,
|
ServiceInvoice $serviceInvoice,
|
||||||
TimesheetRepository $timesheetRepository,
|
TimesheetRepository $timesheetRepository,
|
||||||
CustomerRepository $customerRepository,
|
CustomerRepository $customerRepository,
|
||||||
|
ProjectRepository $projectRepository,
|
||||||
InvoiceTemplateRepository $invoiceTemplateRepository,
|
InvoiceTemplateRepository $invoiceTemplateRepository,
|
||||||
UserRepository $userRepository,
|
UserRepository $userRepository,
|
||||||
EventDispatcherInterface $eventDispatcher
|
EventDispatcherInterface $eventDispatcher
|
||||||
@@ -74,6 +80,7 @@ class InvoiceCreateCommand extends Command
|
|||||||
$this->serviceInvoice = $serviceInvoice;
|
$this->serviceInvoice = $serviceInvoice;
|
||||||
$this->timesheetRepository = $timesheetRepository;
|
$this->timesheetRepository = $timesheetRepository;
|
||||||
$this->customerRepository = $customerRepository;
|
$this->customerRepository = $customerRepository;
|
||||||
|
$this->projectRepository = $projectRepository;
|
||||||
$this->invoiceTemplateRepository = $invoiceTemplateRepository;
|
$this->invoiceTemplateRepository = $invoiceTemplateRepository;
|
||||||
$this->userRepository = $userRepository;
|
$this->userRepository = $userRepository;
|
||||||
$this->eventDispatcher = $eventDispatcher;
|
$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('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('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('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-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('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')
|
->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');
|
$customersIDs = $input->getOption('customer');
|
||||||
if (!$byActiveCustomer && !$byActiveProject && empty($customersIDs)) {
|
$projectIDs = $input->getOption('project');
|
||||||
$io->error('Could not determine generation mode, you need to set one of: customer, by-customer, by-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;
|
return 1;
|
||||||
}
|
}
|
||||||
@@ -235,15 +244,15 @@ class InvoiceCreateCommand extends Command
|
|||||||
$defaultQuery->setCurrentUser($user);
|
$defaultQuery->setCurrentUser($user);
|
||||||
$defaultQuery->setSearchTerm($searchTerm);
|
$defaultQuery->setSearchTerm($searchTerm);
|
||||||
$defaultQuery->setMarkAsExported($markAsExported);
|
$defaultQuery->setMarkAsExported($markAsExported);
|
||||||
$defaultQuery->setState($exportedFilter);
|
$defaultQuery->setExported($exportedFilter);
|
||||||
|
|
||||||
/** @var Invoice[] $invoices */
|
/** @var Invoice[] $invoices */
|
||||||
$invoices = [];
|
$invoices = [];
|
||||||
|
|
||||||
/** @var Customer[] $customers */
|
|
||||||
$customers = [];
|
|
||||||
|
|
||||||
if (!empty($customersIDs)) {
|
if (!empty($customersIDs)) {
|
||||||
|
/** @var Customer[] $customers */
|
||||||
|
$customers = [];
|
||||||
|
|
||||||
$customersIDs = explode(',', $customersIDs);
|
$customersIDs = explode(',', $customersIDs);
|
||||||
foreach ($customersIDs as $id) {
|
foreach ($customersIDs as $id) {
|
||||||
$tmp = $this->customerRepository->find($id);
|
$tmp = $this->customerRepository->find($id);
|
||||||
@@ -255,6 +264,21 @@ class InvoiceCreateCommand extends Command
|
|||||||
$customers[] = $tmp;
|
$customers[] = $tmp;
|
||||||
}
|
}
|
||||||
$invoices = $this->createInvoicesForCustomer($customers, $defaultQuery, $input, $output);
|
$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) {
|
} elseif ($byActiveCustomer) {
|
||||||
$customers = $this->getActiveCustomers($start, $end);
|
$customers = $this->getActiveCustomers($start, $end);
|
||||||
$invoices = $this->createInvoicesForCustomer($customers, $defaultQuery, $input, $output);
|
$invoices = $this->createInvoicesForCustomer($customers, $defaultQuery, $input, $output);
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
namespace App\Entity;
|
namespace App\Entity;
|
||||||
|
|
||||||
use Doctrine\ORM\Mapping as ORM;
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
|
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
|
||||||
use Symfony\Component\Validator\Constraints as Assert;
|
use Symfony\Component\Validator\Constraints as Assert;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -19,6 +20,7 @@ use Symfony\Component\Validator\Constraints as Assert;
|
|||||||
* @ORM\UniqueConstraint(columns={"name"})
|
* @ORM\UniqueConstraint(columns={"name"})
|
||||||
* }
|
* }
|
||||||
* )
|
* )
|
||||||
|
* @UniqueEntity("name")
|
||||||
*/
|
*/
|
||||||
class InvoiceTemplate
|
class InvoiceTemplate
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -77,7 +77,13 @@ class InvoiceRendererType extends AbstractType
|
|||||||
array_pop($parts);
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ class InvoiceItemDefaultHydrator implements InvoiceItemHydrator
|
|||||||
'entry.currency' => $currency,
|
'entry.currency' => $currency,
|
||||||
'entry.duration' => $item->getDuration(),
|
'entry.duration' => $item->getDuration(),
|
||||||
'entry.duration_decimal' => $formatter->getFormattedDecimalDuration($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' => $formatter->getFormattedDateTime($begin),
|
||||||
'entry.begin_time' => $formatter->getFormattedTime($begin),
|
'entry.begin_time' => $formatter->getFormattedTime($begin),
|
||||||
'entry.begin_timestamp' => $begin->getTimestamp(),
|
'entry.begin_timestamp' => $begin->getTimestamp(),
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ class InvoiceModelProjectHydrator implements InvoiceModelHydrator
|
|||||||
$prefix . 'budget_money_plain' => $project->getBudget(),
|
$prefix . 'budget_money_plain' => $project->getBudget(),
|
||||||
$prefix . 'budget_time' => $project->getTimeBudget(),
|
$prefix . 'budget_time' => $project->getTimeBudget(),
|
||||||
$prefix . 'budget_time_decimal' => $formatter->getFormattedDecimalDuration($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) {
|
foreach ($project->getVisibleMetaFields() as $metaField) {
|
||||||
|
|||||||
@@ -45,7 +45,14 @@ final class PdfRenderer implements RendererInterface
|
|||||||
$content = $this->twig->render('@invoice/' . basename($document->getFilename()), [
|
$content = $this->twig->render('@invoice/' . basename($document->getFilename()), [
|
||||||
'model' => $model
|
'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);
|
$filename = (string) new InvoiceFilename($model);
|
||||||
|
|
||||||
$response = new Response($content);
|
$response = new Response($content);
|
||||||
|
|||||||
@@ -91,7 +91,11 @@ class DateExtensions extends AbstractExtension
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!$date instanceof DateTime) {
|
if (!$date instanceof DateTime) {
|
||||||
$date = new DateTime($date);
|
try {
|
||||||
|
$date = new DateTime($date);
|
||||||
|
} catch (\Exception $ex) {
|
||||||
|
return $date;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return date_format($date, $this->dateFormat);
|
return date_format($date, $this->dateFormat);
|
||||||
@@ -109,7 +113,11 @@ class DateExtensions extends AbstractExtension
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!$date instanceof DateTime) {
|
if (!$date instanceof DateTime) {
|
||||||
$date = new DateTime($date);
|
try {
|
||||||
|
$date = new DateTime($date);
|
||||||
|
} catch (\Exception $ex) {
|
||||||
|
return $date;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $date->format($this->dateTimeFormat);
|
return $date->format($this->dateTimeFormat);
|
||||||
@@ -128,7 +136,11 @@ class DateExtensions extends AbstractExtension
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!$date instanceof DateTime) {
|
if (!$date instanceof DateTime) {
|
||||||
$date = new DateTime($date);
|
try {
|
||||||
|
$date = new DateTime($date);
|
||||||
|
} catch (\Exception $ex) {
|
||||||
|
return $date;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$timezone = date_default_timezone_get();
|
$timezone = date_default_timezone_get();
|
||||||
@@ -158,7 +170,11 @@ class DateExtensions extends AbstractExtension
|
|||||||
public function dateFormat($date, string $format)
|
public function dateFormat($date, string $format)
|
||||||
{
|
{
|
||||||
if (!$date instanceof DateTime) {
|
if (!$date instanceof DateTime) {
|
||||||
$date = new DateTime($date);
|
try {
|
||||||
|
$date = new DateTime($date);
|
||||||
|
} catch (\Exception $ex) {
|
||||||
|
return $date;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return date_format($date, $format);
|
return date_format($date, $format);
|
||||||
|
|||||||
@@ -193,7 +193,12 @@ class Extensions extends AbstractExtension
|
|||||||
*/
|
*/
|
||||||
public function currency($currency)
|
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)
|
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)
|
public function country($country)
|
||||||
{
|
{
|
||||||
$country = strtoupper($country);
|
try {
|
||||||
if (Countries::exists($country)) {
|
return Countries::getName(strtoupper($country));
|
||||||
return Countries::getName($country);
|
} catch (\Exception $ex) {
|
||||||
}
|
}
|
||||||
|
|
||||||
return $country;
|
return $country;
|
||||||
|
|||||||
@@ -14,7 +14,8 @@ interface HtmlToPdfConverter
|
|||||||
/**
|
/**
|
||||||
* Returns the binary content of the PDF, which can be saved as file or send via Reponse.
|
* Returns the binary content of the PDF, which can be saved as file or send via Reponse.
|
||||||
* @param string $html
|
* @param string $html
|
||||||
|
* @param array $options
|
||||||
* @return mixed
|
* @return mixed
|
||||||
*/
|
*/
|
||||||
public function convertToPdf(string $html);
|
public function convertToPdf(string $html, array $options = []);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,12 +27,14 @@ class MPdfConverter implements HtmlToPdfConverter
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string $html
|
* @param string $html
|
||||||
|
* @param array $options
|
||||||
* @return mixed|string
|
* @return mixed|string
|
||||||
* @throws \Mpdf\MpdfException
|
* @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;
|
$mpdf->creator = Constants::SOFTWARE;
|
||||||
|
|
||||||
// some OS do not follow the PHP default settings
|
// some OS do not follow the PHP default settings
|
||||||
|
|||||||
@@ -32,7 +32,7 @@
|
|||||||
{% block main %}
|
{% block main %}
|
||||||
|
|
||||||
{% if entries.count == 0 %}
|
{% if entries.count == 0 %}
|
||||||
{{ widgets.callout('warning', 'error.no_entries_found') }}
|
{{ widgets.nothing_found('kimai.activityUpdate') }}
|
||||||
{% else %}
|
{% else %}
|
||||||
{{ tables.datatable_header(tableName, columns, query, {'reload': 'kimai.activityUpdate'}) }}
|
{{ tables.datatable_header(tableName, columns, query, {'reload': 'kimai.activityUpdate'}) }}
|
||||||
|
|
||||||
|
|||||||
@@ -44,7 +44,7 @@
|
|||||||
{% block main %}
|
{% block main %}
|
||||||
|
|
||||||
{% if entries.count == 0 %}
|
{% if entries.count == 0 %}
|
||||||
{{ widgets.callout('warning', 'error.no_entries_found') }}
|
{{ widgets.nothing_found('kimai.customerUpdate kimai.customerTeamUpdate') }}
|
||||||
{% else %}
|
{% else %}
|
||||||
{{ tables.datatable_header(tableName, columns, query, {'reload': 'kimai.customerUpdate kimai.customerTeamUpdate'}) }}
|
{{ tables.datatable_header(tableName, columns, query, {'reload': 'kimai.customerUpdate kimai.customerTeamUpdate'}) }}
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
{% block main %}
|
{% block main %}
|
||||||
|
|
||||||
{% if widgets is empty %}
|
{% if widgets is empty %}
|
||||||
{{ widgets.callout('warning', 'error.no_entries_found') }}
|
{{ widgets.nothing_found() }}
|
||||||
{% else %}
|
{% else %}
|
||||||
{% for row in widgets %}
|
{% for row in widgets %}
|
||||||
{{ render_widget(row) }}
|
{{ render_widget(row) }}
|
||||||
|
|||||||
@@ -85,7 +85,7 @@
|
|||||||
|
|
||||||
{% if preview_show %}
|
{% if preview_show %}
|
||||||
{% if entries is empty %}
|
{% if entries is empty %}
|
||||||
{{ widgets.callout('warning', 'error.no_entries_found') }}
|
{{ widgets.nothing_found() }}
|
||||||
{% else %}
|
{% else %}
|
||||||
{{ tables.datatable_header(tableName, columns, query, {}) }}
|
{{ tables.datatable_header(tableName, columns, query, {}) }}
|
||||||
{% set totalAmount = {} %}
|
{% set totalAmount = {} %}
|
||||||
|
|||||||
@@ -33,7 +33,7 @@
|
|||||||
{% set actions = actions|merge({'visibility': '#modal_invoice_template'}) %}
|
{% set actions = actions|merge({'visibility': '#modal_invoice_template'}) %}
|
||||||
|
|
||||||
{% if is_granted('manage_invoice_template') %}
|
{% if is_granted('manage_invoice_template') %}
|
||||||
{% set actions = actions|merge({'create': path('admin_invoice_template_create')}) %}
|
{% set actions = actions|merge({'create': {'url': path('admin_invoice_template_create'), 'class': 'modal-ajax-form'}}) %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if is_granted('upload_invoice_template') %}
|
{% if is_granted('upload_invoice_template') %}
|
||||||
|
|||||||
@@ -71,7 +71,7 @@
|
|||||||
|
|
||||||
{% if preview %}
|
{% if preview %}
|
||||||
{% if model.calculator is empty or model.calculator.entries is empty %}
|
{% if model.calculator is empty or model.calculator.entries is empty %}
|
||||||
{{ widgets.callout('warning', 'error.no_entries_found') }}
|
{{ widgets.nothing_found() }}
|
||||||
{% else %}
|
{% else %}
|
||||||
{% set isDecimal = model.template.decimalDuration|default(false) %}
|
{% set isDecimal = model.template.decimalDuration|default(false) %}
|
||||||
{% set entries = model.calculator.entries %}
|
{% set entries = model.calculator.entries %}
|
||||||
|
|||||||
@@ -29,11 +29,11 @@
|
|||||||
{% block main %}
|
{% block main %}
|
||||||
|
|
||||||
{% if entries is empty %}
|
{% if entries is empty %}
|
||||||
{{ widgets.callout('warning', 'error.no_entries_found') }}
|
{{ widgets.nothing_found() }}
|
||||||
{% else %}
|
{% else %}
|
||||||
{{ tables.datatable_header(tableName, columns, query, {}) }}
|
{{ tables.datatable_header(tableName, columns, query, {}) }}
|
||||||
{% for entry in entries %}
|
{% for entry in entries %}
|
||||||
<tr>
|
<tr class="alternative-link open-edit" data-href="{{ path('admin_invoice_download', {'id': entry.id}) }}">
|
||||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'date') }}">{{ entry.createdAt|date_short }}</td>
|
<td class="{{ tables.data_table_column_class(tableName, columns, 'date') }}">{{ entry.createdAt|date_short }}</td>
|
||||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'user') }}">{{ widgets.user_avatar(entry.user) }} {{ widgets.username(entry.user) }}</td>
|
<td class="{{ tables.data_table_column_class(tableName, columns, 'user') }}">{{ widgets.user_avatar(entry.user) }} {{ widgets.username(entry.user) }}</td>
|
||||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'customer') }}">{{ widgets.label_customer(entry.customer) }}</td>
|
<td class="{{ tables.data_table_column_class(tableName, columns, 'customer') }}">{{ widgets.label_customer(entry.customer) }}</td>
|
||||||
|
|||||||
160
templates/invoice/renderer/default-pdf.pdf.twig
Normal file
160
templates/invoice/renderer/default-pdf.pdf.twig
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
{% set fallback = app.request is not null ? app.request.locale : 'en' %}
|
||||||
|
{% set language = model.template.language|default(fallback) %}
|
||||||
|
{% set isDecimal = model.template.decimalDuration|default(false) %}
|
||||||
|
{% set currency = model.currency %}
|
||||||
|
<html lang="{{ language }}">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" name="viewport">
|
||||||
|
<title>{% block title %}{{ model.invoiceNumber }}-{{ model.customer.company|default(model.customer.name)|u.snake }}{% endblock %}</title>
|
||||||
|
<style type="text/css">
|
||||||
|
{{ encore_entry_css_source('invoice-pdf')|raw }}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<!--mpdf
|
||||||
|
<htmlpageheader name="header">
|
||||||
|
<table class="header">
|
||||||
|
<tr>
|
||||||
|
<td class="title">
|
||||||
|
{{ model.template.title }}
|
||||||
|
</td>
|
||||||
|
<td class="date">
|
||||||
|
{{ 'label.date'|trans({}, 'messages', language) }}: {{ model.invoiceDate|date_short }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</htmlpageheader>
|
||||||
|
<sethtmlpageheader name="header" page="ALL" value="on" show-this-page="1" />
|
||||||
|
<htmlpagefooter name="footer">
|
||||||
|
<table class="footer">
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<strong>{{ 'label.contact'|trans({}, 'messages', language) }}</strong>:
|
||||||
|
{{ model.template.contact|replace({"\n": ' – ', "\r\n": ' – ', "\r": ' – '})|raw }}
|
||||||
|
<br>
|
||||||
|
<strong>{{ 'label.invoice_bank_account'|trans({}, 'messages', language) }}</strong>:
|
||||||
|
{{ model.template.paymentDetails|replace({"\n": ' – ', "\r\n": ' – ', "\r": ' – '})|raw }}
|
||||||
|
</td>
|
||||||
|
<td align="right">
|
||||||
|
{{ 'export.page_of'|trans({'%page%': '{PAGENO}', '%pages%': '{nb}'}) }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</htmlpagefooter>
|
||||||
|
<sethtmlpagefooter page="ALL" value="on" name="footer" show-this-page="1" />
|
||||||
|
mpdf-->
|
||||||
|
<div class="wrapper">
|
||||||
|
<table class="addresses">
|
||||||
|
<tr>
|
||||||
|
<td width="60%">
|
||||||
|
{{ 'invoice.to'|trans({}, 'messages', language) }}
|
||||||
|
<br>
|
||||||
|
<strong>{{ model.customer.company|default(model.customer.name) }}</strong>
|
||||||
|
<br>
|
||||||
|
{{ model.customer.address|nl2br }}
|
||||||
|
{% if model.customer.vatId is not empty %}
|
||||||
|
<br>
|
||||||
|
{{ 'label.vat_id'|trans({}, 'messages', language) }}: {{ model.customer.vatId }}
|
||||||
|
{% endif %}
|
||||||
|
{% if model.customer.number is not empty %}
|
||||||
|
<br>
|
||||||
|
{{ 'label.number'|trans({}, 'messages', language) }}: {{ model.customer.number }}
|
||||||
|
{% endif %}
|
||||||
|
{% if model.query.project is not empty and model.query.project.orderNumber is not empty %}
|
||||||
|
<br>
|
||||||
|
{{ 'label.orderNumber'|trans({}, 'messages', language) }}: {{ model.query.project.orderNumber }}
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{{ 'invoice.from'|trans({}, 'messages', language) }}
|
||||||
|
<br>
|
||||||
|
<strong>{{ model.template.company }}</strong>
|
||||||
|
<br>
|
||||||
|
{{ model.template.address|trim|nl2br }}
|
||||||
|
{% if model.template.vatId is not empty %}
|
||||||
|
<br>
|
||||||
|
{{ 'label.vat_id'|trans({}, 'messages', language) }}:
|
||||||
|
{{ model.template.vatId }}
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<strong>{{ 'invoice.number'|trans({}, 'messages', language) }}:</strong>
|
||||||
|
{{ model.invoiceNumber }}
|
||||||
|
|
||||||
|
<br>
|
||||||
|
<strong>{{ 'invoice.due_days'|trans({}, 'messages', language) }}:</strong>
|
||||||
|
{{ model.dueDate|date_short }}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<table class="items">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="first">{{ 'label.date'|trans({}, 'messages', language) }}</th>
|
||||||
|
<th>{{ 'label.description'|trans({}, 'messages', language) }}</th>
|
||||||
|
<th class="text-right">{{ 'label.unit_price'|trans({}, 'messages', language) }}</th>
|
||||||
|
<th class="text-right">{{ 'label.amount'|trans({}, 'messages', language) }}</th>
|
||||||
|
<th class="last text-right">{{ 'label.total_rate'|trans({}, 'messages', language) }}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for id, entry in model.calculator.entries %}
|
||||||
|
{% set duration = entry.duration|duration(isDecimal) %}
|
||||||
|
{% if entry.fixedRate %}
|
||||||
|
{% set rate = entry.fixedRate %}
|
||||||
|
{% set duration = entry.amount|amount %}
|
||||||
|
{% else %}
|
||||||
|
{% set rate = entry.hourlyRate %}
|
||||||
|
{% endif %}
|
||||||
|
<tr{# class="{{ cycle(['odd', 'even'], id) }}"#}>
|
||||||
|
<td nowrap class="first text-nowrap">{{ entry.begin|date_short }}</td>
|
||||||
|
<td contenteditable="true">
|
||||||
|
{% if entry.description is not empty %}
|
||||||
|
{{ entry.description|nl2br }}
|
||||||
|
{% else %}
|
||||||
|
{% if entry.activity is not null %}{{ entry.activity.name }} / {% endif %}{{ entry.project.name }}
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td nowrap class="text-nowrap text-right">{{ rate|money(currency) }}</td>
|
||||||
|
<td nowrap class="text-nowrap text-right">{{ duration }}</td>
|
||||||
|
<td nowrap class="last text-nowrap text-right">{{ entry.rate|money(currency) }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
<tfoot>
|
||||||
|
<tr>
|
||||||
|
<td colspan="4" class="text-right">
|
||||||
|
{{ 'invoice.subtotal'|trans({}, 'messages', language) }}
|
||||||
|
</td>
|
||||||
|
<td class="last text-right">{{ model.calculator.subtotal|money(currency) }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td colspan="4" class="text-right">
|
||||||
|
{{ 'invoice.tax'|trans({}, 'messages', language) }} ({{ model.calculator.vat }}%)
|
||||||
|
</td>
|
||||||
|
<td class="last text-right">{{ model.calculator.tax|money(currency) }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td colspan="4" class="text-right text-nowrap">
|
||||||
|
<strong>{{ 'invoice.total'|trans({}, 'messages', language) }}</strong>
|
||||||
|
</td>
|
||||||
|
<td class="last text-right">
|
||||||
|
<strong>{{ model.calculator.total|money(currency) }}</strong>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tfoot>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
{% if model.template.paymentTerms is not empty %}
|
||||||
|
<p>
|
||||||
|
{{ model.template.paymentTerms|md2html }}
|
||||||
|
</p>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -29,7 +29,7 @@
|
|||||||
|
|
||||||
{% block main %}
|
{% block main %}
|
||||||
{% if entries.count == 0 %}
|
{% if entries.count == 0 %}
|
||||||
{{ widgets.callout('warning', 'error.no_entries_found') }}
|
{{ widgets.nothing_found('kimai.invoiceTemplateUpdate') }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{{ tables.datatable_header(tableName, columns, null, {'reload': 'kimai.invoiceTemplateUpdate'}) }}
|
{{ tables.datatable_header(tableName, columns, null, {'reload': 'kimai.invoiceTemplateUpdate'}) }}
|
||||||
|
|||||||
@@ -458,3 +458,14 @@
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
{% endmacro %}
|
{% endmacro %}
|
||||||
|
|
||||||
|
{% macro nothing_found(events) %}
|
||||||
|
{{ _self.callout('warning', 'error.no_entries_found') }}
|
||||||
|
{% if events is not empty %}
|
||||||
|
<script type="text/javascript">
|
||||||
|
document.addEventListener('kimai.initialized', function() {
|
||||||
|
KimaiReloadPageWidget.create('{{ events }}');
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endif %}
|
||||||
|
{% endmacro %}
|
||||||
|
|||||||
@@ -37,7 +37,7 @@
|
|||||||
{% block main %}
|
{% block main %}
|
||||||
|
|
||||||
{% if entries.count == 0 %}
|
{% if entries.count == 0 %}
|
||||||
{{ widgets.callout('warning', 'error.no_entries_found') }}
|
{{ widgets.nothing_found('kimai.projectUpdate kimai.projectTeamUpdate') }}
|
||||||
{% else %}
|
{% else %}
|
||||||
{{ tables.datatable_header(tableName, columns, query, {'reload': 'kimai.projectUpdate kimai.projectTeamUpdate'}) }}
|
{{ tables.datatable_header(tableName, columns, query, {'reload': 'kimai.projectUpdate kimai.projectTeamUpdate'}) }}
|
||||||
|
|
||||||
|
|||||||
@@ -11,12 +11,7 @@
|
|||||||
{% block main %}
|
{% block main %}
|
||||||
|
|
||||||
{% if tags|length == 0 %}
|
{% if tags|length == 0 %}
|
||||||
{{ widgets.callout('warning', 'error.no_entries_found') }}
|
{{ widgets.nothing_found('kimai.tagUpdate') }}
|
||||||
<script type="text/javascript">
|
|
||||||
document.addEventListener('kimai.initialized', function() {
|
|
||||||
KimaiReloadPageWidget.create('kimai.tagUpdate');
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
{% else %}
|
{% else %}
|
||||||
|
|
||||||
{% set columns = {
|
{% set columns = {
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
{% block main %}
|
{% block main %}
|
||||||
|
|
||||||
{% if teams|length == 0 %}
|
{% if teams|length == 0 %}
|
||||||
{{ widgets.callout('warning', 'error.no_entries_found') }}
|
{{ widgets.nothing_found('kimai.teamUpdate') }}
|
||||||
{% else %}
|
{% else %}
|
||||||
|
|
||||||
{% set columns = {
|
{% set columns = {
|
||||||
|
|||||||
@@ -57,12 +57,7 @@
|
|||||||
{% block main %}
|
{% block main %}
|
||||||
|
|
||||||
{% if entries.count == 0 %}
|
{% if entries.count == 0 %}
|
||||||
{{ widgets.callout('warning', 'error.no_entries_found') }}
|
{{ widgets.nothing_found('kimai.timesheetUpdate') }}
|
||||||
<script type="text/javascript">
|
|
||||||
document.addEventListener('kimai.initialized', function() {
|
|
||||||
KimaiReloadPageWidget.create('kimai.timesheetUpdate');
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
{% else %}
|
{% else %}
|
||||||
{{ tables.datatable_header(tableName, columns, query, {'striped': not showSummary, 'reload': 'kimai.timesheetUpdate'}) }}
|
{{ tables.datatable_header(tableName, columns, query, {'striped': not showSummary, 'reload': 'kimai.timesheetUpdate'}) }}
|
||||||
|
|
||||||
|
|||||||
@@ -35,7 +35,7 @@
|
|||||||
{% block main %}
|
{% block main %}
|
||||||
|
|
||||||
{% if entries.count == 0 %}
|
{% if entries.count == 0 %}
|
||||||
{{ widgets.callout('warning', 'error.no_entries_found') }}
|
{{ widgets.nothing_found('kimai.userUpdate') }}
|
||||||
{% else %}
|
{% else %}
|
||||||
{{ tables.datatable_header(tableName, columns, query, {'reload': 'kimai.userUpdate'}) }}
|
{{ tables.datatable_header(tableName, columns, query, {'reload': 'kimai.userUpdate'}) }}
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
{% block profile_content %}
|
{% block profile_content %}
|
||||||
|
|
||||||
{% if years is empty %}
|
{% if years is empty %}
|
||||||
{{ widgets.callout('warning', 'error.no_entries_found') }}
|
{{ widgets.nothing_found() }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ use App\Entity\Project;
|
|||||||
use App\Invoice\ServiceInvoice;
|
use App\Invoice\ServiceInvoice;
|
||||||
use App\Repository\CustomerRepository;
|
use App\Repository\CustomerRepository;
|
||||||
use App\Repository\InvoiceTemplateRepository;
|
use App\Repository\InvoiceTemplateRepository;
|
||||||
|
use App\Repository\ProjectRepository;
|
||||||
use App\Repository\TimesheetRepository;
|
use App\Repository\TimesheetRepository;
|
||||||
use App\Repository\UserRepository;
|
use App\Repository\UserRepository;
|
||||||
use App\Tests\DataFixtures\CustomerFixtures;
|
use App\Tests\DataFixtures\CustomerFixtures;
|
||||||
@@ -70,6 +71,7 @@ class InvoiceCreateCommandTest extends KernelTestCase
|
|||||||
$container->get(ServiceInvoice::class),
|
$container->get(ServiceInvoice::class),
|
||||||
$container->get(TimesheetRepository::class),
|
$container->get(TimesheetRepository::class),
|
||||||
$container->get(CustomerRepository::class),
|
$container->get(CustomerRepository::class),
|
||||||
|
$container->get(ProjectRepository::class),
|
||||||
$container->get(InvoiceTemplateRepository::class),
|
$container->get(InvoiceTemplateRepository::class),
|
||||||
$container->get(UserRepository::class),
|
$container->get(UserRepository::class),
|
||||||
$container->get('event_dispatcher')
|
$container->get('event_dispatcher')
|
||||||
@@ -167,6 +169,11 @@ class InvoiceCreateCommandTest extends KernelTestCase
|
|||||||
$this->assertCommandErrors(['--user' => UserFixtures::USERNAME_SUPER_ADMIN, '--customer' => 3, '--template' => 'x'], 'Unknown customer ID: 3');
|
$this->assertCommandErrors(['--user' => UserFixtures::USERNAME_SUPER_ADMIN, '--customer' => 3, '--template' => 'x'], 'Unknown customer ID: 3');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testCreateWithInvalidProject()
|
||||||
|
{
|
||||||
|
$this->assertCommandErrors(['--user' => UserFixtures::USERNAME_SUPER_ADMIN, '--project' => 3, '--template' => 'x'], 'Unknown project ID: 3');
|
||||||
|
}
|
||||||
|
|
||||||
public function testCreateInvoice()
|
public function testCreateInvoice()
|
||||||
{
|
{
|
||||||
$fixture = new InvoiceFixtures();
|
$fixture = new InvoiceFixtures();
|
||||||
@@ -251,6 +258,19 @@ class InvoiceCreateCommandTest extends KernelTestCase
|
|||||||
$this->assertStringContainsString('Created 1 invoice(s) ', $output);
|
$this->assertStringContainsString('Created 1 invoice(s) ', $output);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testCreateInvoiceByProjectId()
|
||||||
|
{
|
||||||
|
$start = new \DateTime('-2 months');
|
||||||
|
$end = new \DateTime();
|
||||||
|
|
||||||
|
$this->prepareFixtures($start);
|
||||||
|
|
||||||
|
$commandTester = $this->createInvoice(['--user' => UserFixtures::USERNAME_SUPER_ADMIN, '--exported' => 'all', '--project' => '1', '--template' => 'Invoice', '--start' => $start->format('Y-m-d'), '--end' => $end->format('Y-m-d')]);
|
||||||
|
|
||||||
|
$output = $commandTester->getDisplay();
|
||||||
|
$this->assertStringContainsString('Created 1 invoice(s) ', $output);
|
||||||
|
}
|
||||||
|
|
||||||
public function testCreateInvoiceByProjectWithPreview()
|
public function testCreateInvoiceByProjectWithPreview()
|
||||||
{
|
{
|
||||||
$start = new \DateTime('-2 months');
|
$start = new \DateTime('-2 months');
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ class InvoiceDocumentRepositoryTest extends TestCase
|
|||||||
protected static $defaultDocuments = [
|
protected static $defaultDocuments = [
|
||||||
'company.docx',
|
'company.docx',
|
||||||
'default.html.twig',
|
'default.html.twig',
|
||||||
|
'default-pdf.pdf.twig',
|
||||||
'freelancer.html.twig',
|
'freelancer.html.twig',
|
||||||
'timesheet.html.twig',
|
'timesheet.html.twig',
|
||||||
'text.txt.twig',
|
'text.txt.twig',
|
||||||
|
|||||||
@@ -91,6 +91,7 @@ class DateExtensionsTest extends TestCase
|
|||||||
['de', new \DateTime('1980-12-14'), '14.12.1980'],
|
['de', new \DateTime('1980-12-14'), '14.12.1980'],
|
||||||
['ru', new \DateTime('1980-12-14'), '14.12.1980'],
|
['ru', new \DateTime('1980-12-14'), '14.12.1980'],
|
||||||
['ru', '1980-12-14', '14.12.1980'],
|
['ru', '1980-12-14', '14.12.1980'],
|
||||||
|
['ru', 1.2345, 1.2345],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,6 +116,7 @@ class DateExtensionsTest extends TestCase
|
|||||||
['en', new \DateTime('7 January 2010'), '2010-01-07 12:01 AM'],
|
['en', new \DateTime('7 January 2010'), '2010-01-07 12:01 AM'],
|
||||||
['de', (new \DateTime('1980-12-14'))->setTime(13, 27, 55), '14.12.1980 13:27:55'],
|
['de', (new \DateTime('1980-12-14'))->setTime(13, 27, 55), '14.12.1980 13:27:55'],
|
||||||
['de', '1980-12-14 13:27:55', '14.12.1980 13:27:55'],
|
['de', '1980-12-14 13:27:55', '14.12.1980 13:27:55'],
|
||||||
|
['de', 1.2345, 1.2345],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -144,6 +146,7 @@ class DateExtensionsTest extends TestCase
|
|||||||
$sut = $this->getSut('en', []);
|
$sut = $this->getSut('en', []);
|
||||||
$this->assertEquals('2010-01-07T17:43:21+01:00', $sut->dateFormat($date, 'c'));
|
$this->assertEquals('2010-01-07T17:43:21+01:00', $sut->dateFormat($date, 'c'));
|
||||||
$this->assertStringStartsWith('2010-01-07T17:43:21', $sut->dateFormat('7 January 2010 17:43:21', 'c'));
|
$this->assertStringStartsWith('2010-01-07T17:43:21', $sut->dateFormat('7 January 2010 17:43:21', 'c'));
|
||||||
|
$this->assertEquals(2010.0107, $sut->dateFormat(2010.0107, 'c'));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testTime()
|
public function testTime()
|
||||||
@@ -181,5 +184,6 @@ class DateExtensionsTest extends TestCase
|
|||||||
|
|
||||||
$this->assertEquals('2019-08-17 12:29:47', $sut->dateTimeFull($dateTime));
|
$this->assertEquals('2019-08-17 12:29:47', $sut->dateTimeFull($dateTime));
|
||||||
$this->assertEquals('2019-08-17 12:29:47', $sut->dateTimeFull('2019-08-17 12:29:47'));
|
$this->assertEquals('2019-08-17 12:29:47', $sut->dateTimeFull('2019-08-17 12:29:47'));
|
||||||
|
$this->assertEquals(189.45, $sut->dateTimeFull(189.45));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -95,6 +95,8 @@ class ExtensionsTest extends TestCase
|
|||||||
'EUR' => '€',
|
'EUR' => '€',
|
||||||
'USD' => '$',
|
'USD' => '$',
|
||||||
'RUB' => 'RUB',
|
'RUB' => 'RUB',
|
||||||
|
'rub' => 'RUB',
|
||||||
|
123 => 123,
|
||||||
];
|
];
|
||||||
|
|
||||||
$sut = $this->getSut($this->localeEn);
|
$sut = $this->getSut($this->localeEn);
|
||||||
@@ -109,6 +111,8 @@ class ExtensionsTest extends TestCase
|
|||||||
'DE' => 'Germany',
|
'DE' => 'Germany',
|
||||||
'RU' => 'Russia',
|
'RU' => 'Russia',
|
||||||
'ES' => 'Spain',
|
'ES' => 'Spain',
|
||||||
|
'es' => 'Spain',
|
||||||
|
'12' => '12',
|
||||||
];
|
];
|
||||||
|
|
||||||
$sut = $this->getSut($this->localeEn);
|
$sut = $this->getSut($this->localeEn);
|
||||||
@@ -123,6 +127,8 @@ class ExtensionsTest extends TestCase
|
|||||||
'de' => 'German',
|
'de' => 'German',
|
||||||
'ru' => 'Russian',
|
'ru' => 'Russian',
|
||||||
'es' => 'Spanish',
|
'es' => 'Spanish',
|
||||||
|
'ES' => 'Spanish',
|
||||||
|
'12' => '12',
|
||||||
];
|
];
|
||||||
|
|
||||||
$sut = $this->getSut($this->localeEn);
|
$sut = $this->getSut($this->localeEn);
|
||||||
|
|||||||
@@ -6,9 +6,59 @@
|
|||||||
<source>label.invoice_renderer</source>
|
<source>label.invoice_renderer</source>
|
||||||
<target>Rechnungsdokument</target>
|
<target>Rechnungsdokument</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
|
<trans-unit id="help.upload">
|
||||||
|
<source>help.upload</source>
|
||||||
|
<target>Achtung: existierende Dateien werden übschrieben. Erlaubte Dateitypen sind: DOCX, ODS, XLSX</target>
|
||||||
|
</trans-unit>
|
||||||
|
<!-- Optgroups -->
|
||||||
|
<trans-unit id="programmatic">
|
||||||
|
<source>programmatic</source>
|
||||||
|
<target>Zur Weiterverarbeitung</target>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="Html">
|
||||||
|
<source>Html</source>
|
||||||
|
<target>HTML (zum Drucken via Browser)</target>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="Docx">
|
||||||
|
<source>Docx</source>
|
||||||
|
<target>Microsoft Word (Docx)</target>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="Xls">
|
||||||
|
<source>Xls</source>
|
||||||
|
<target>Microsoft Excel (Xls)</target>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="Ods">
|
||||||
|
<source>Ods</source>
|
||||||
|
<target>Open Office (Tabelle)</target>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="Xlsx">
|
||||||
|
<source>Xlsx</source>
|
||||||
|
<target>Microsoft Excel (Xlsx)</target>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="Pdf">
|
||||||
|
<source>Pdf</source>
|
||||||
|
<target>PDF</target>
|
||||||
|
</trans-unit>
|
||||||
|
<!-- Renderer -->
|
||||||
<trans-unit id="default">
|
<trans-unit id="default">
|
||||||
<source>default</source>
|
<source>default</source>
|
||||||
<target>Rechnung (Standard)</target>
|
<target>Rechnung</target>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="default-pdf">
|
||||||
|
<source>default-pdf</source>
|
||||||
|
<target>Rechnung</target>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="xml">
|
||||||
|
<source>xml</source>
|
||||||
|
<target>XML</target>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="javascript">
|
||||||
|
<source>javascript</source>
|
||||||
|
<target>JSON</target>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="text">
|
||||||
|
<source>text</source>
|
||||||
|
<target>Text</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="timesheet">
|
<trans-unit id="timesheet">
|
||||||
<source>timesheet</source>
|
<source>timesheet</source>
|
||||||
@@ -20,23 +70,15 @@
|
|||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="open-spreadsheet">
|
<trans-unit id="open-spreadsheet">
|
||||||
<source>open-spreadsheet</source>
|
<source>open-spreadsheet</source>
|
||||||
<target>Calc Tabellendokument</target>
|
<target>Rechnung</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="spreadsheet">
|
<trans-unit id="spreadsheet">
|
||||||
<source>spreadsheet</source>
|
<source>spreadsheet</source>
|
||||||
<target>Excel Tabelle</target>
|
<target>Rechnung</target>
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="export">
|
|
||||||
<source>export</source>
|
|
||||||
<target>Datenexport</target>
|
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="company">
|
<trans-unit id="company">
|
||||||
<source>company</source>
|
<source>company</source>
|
||||||
<target>Firmen-Rechnung</target>
|
<target>Rechnung</target>
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="help.upload">
|
|
||||||
<source>help.upload</source>
|
|
||||||
<target>Achtung: existierende Dateien werden übschrieben. Erlaubte Dateitypen sind: DOCX, ODS, XLSX</target>
|
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
</body>
|
</body>
|
||||||
</file>
|
</file>
|
||||||
|
|||||||
@@ -6,9 +6,59 @@
|
|||||||
<source>label.invoice_renderer</source>
|
<source>label.invoice_renderer</source>
|
||||||
<target>Invoice document</target>
|
<target>Invoice document</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
|
<trans-unit id="help.upload">
|
||||||
|
<source>help.upload</source>
|
||||||
|
<target>Attention: existing files will be overwritten. Allowed file types are: DOCX, ODS, XLSX.</target>
|
||||||
|
</trans-unit>
|
||||||
|
<!-- Optgroups -->
|
||||||
|
<trans-unit id="programmatic">
|
||||||
|
<source>programmatic</source>
|
||||||
|
<target>For further processing</target>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="Html">
|
||||||
|
<source>Html</source>
|
||||||
|
<target>HTML (for printing via Browser)</target>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="Docx">
|
||||||
|
<source>Docx</source>
|
||||||
|
<target>Microsoft Word (Docx)</target>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="Xls">
|
||||||
|
<source>Xls</source>
|
||||||
|
<target>Microsoft Excel (Xls)</target>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="Ods">
|
||||||
|
<source>Ods</source>
|
||||||
|
<target>Open Office (Spreadsheet)</target>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="Xlsx">
|
||||||
|
<source>Xlsx</source>
|
||||||
|
<target>Microsoft Excel (Xlsx)</target>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="Pdf">
|
||||||
|
<source>Pdf</source>
|
||||||
|
<target>PDF</target>
|
||||||
|
</trans-unit>
|
||||||
|
<!-- Renderer -->
|
||||||
<trans-unit id="default">
|
<trans-unit id="default">
|
||||||
<source>default</source>
|
<source>default</source>
|
||||||
<target>Invoice (default)</target>
|
<target>Invoice</target>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="default-pdf">
|
||||||
|
<source>default-pdf</source>
|
||||||
|
<target>Invoice</target>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="xml">
|
||||||
|
<source>xml</source>
|
||||||
|
<target>XML</target>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="javascript">
|
||||||
|
<source>javascript</source>
|
||||||
|
<target>JSON</target>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="text">
|
||||||
|
<source>text</source>
|
||||||
|
<target>Text</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="timesheet">
|
<trans-unit id="timesheet">
|
||||||
<source>timesheet</source>
|
<source>timesheet</source>
|
||||||
@@ -20,23 +70,15 @@
|
|||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="open-spreadsheet">
|
<trans-unit id="open-spreadsheet">
|
||||||
<source>open-spreadsheet</source>
|
<source>open-spreadsheet</source>
|
||||||
<target>Calc Spreadsheet</target>
|
<target>Invoice</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="spreadsheet">
|
<trans-unit id="spreadsheet">
|
||||||
<source>spreadsheet</source>
|
<source>spreadsheet</source>
|
||||||
<target>Excel</target>
|
<target>Invoice</target>
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="export">
|
|
||||||
<source>export</source>
|
|
||||||
<target>Data export</target>
|
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="company">
|
<trans-unit id="company">
|
||||||
<source>company</source>
|
<source>company</source>
|
||||||
<target>Company invoice</target>
|
<target>Invoice</target>
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="help.upload">
|
|
||||||
<source>help.upload</source>
|
|
||||||
<target>Attention: existing files will be overwritten. Allowed file types are: DOCX, ODS, XLSX.</target>
|
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
</body>
|
</body>
|
||||||
</file>
|
</file>
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ Encore
|
|||||||
|
|
||||||
.addEntry('app', './assets/app.js')
|
.addEntry('app', './assets/app.js')
|
||||||
.addEntry('invoice', './assets/invoice.js')
|
.addEntry('invoice', './assets/invoice.js')
|
||||||
|
.addEntry('invoice-pdf', './assets/invoice-pdf.js')
|
||||||
.addEntry('chart', './assets/chart.js')
|
.addEntry('chart', './assets/chart.js')
|
||||||
.addEntry('calendar', './assets/calendar.js')
|
.addEntry('calendar', './assets/calendar.js')
|
||||||
.copyFiles({ from: './assets/images', to: 'images/[path][name].[ext]' })
|
.copyFiles({ from: './assets/images', to: 'images/[path][name].[ext]' })
|
||||||
@@ -26,7 +27,7 @@ Encore
|
|||||||
.enableSassLoader()
|
.enableSassLoader()
|
||||||
.autoProvidejQuery()
|
.autoProvidejQuery()
|
||||||
|
|
||||||
// prevent that moment locales will be included which we don't need
|
// prevent that unused moment locales will be included
|
||||||
.addPlugin(new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/))
|
.addPlugin(new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/))
|
||||||
|
|
||||||
.configureBabel(null, {
|
.configureBabel(null, {
|
||||||
|
|||||||
Reference in New Issue
Block a user