added new invoice fields, improved invoice templates (#1258)
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -22,6 +22,8 @@ templates/invoice/renderer/.~lock*
|
|||||||
|
|
||||||
/var/invoices/*
|
/var/invoices/*
|
||||||
|
|
||||||
|
/var/packages/*
|
||||||
|
!var/packages/.gitkeep
|
||||||
/var/plugins/*
|
/var/plugins/*
|
||||||
!var/plugins/.gitkeep
|
!var/plugins/.gitkeep
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ Perform EACH version specific task between your version and the new one, otherwi
|
|||||||
|
|
||||||
## [1.6](https://github.com/kevinpapst/kimai2/releases/tag/1.6)
|
## [1.6](https://github.com/kevinpapst/kimai2/releases/tag/1.6)
|
||||||
|
|
||||||
|
- Moved CSV, ODS and XSLX invoice templates to [another repository](https://github.com/Keleo/kimai2-invoice-templates) - if you use them, add them manually (see [invoice documentation](https://www.kimai.org/documentation/invoices.html))
|
||||||
|
- Added new invoice fields (VAT, bank account) and customer fields (VAT), check if you want to update your settings
|
||||||
- New functionality to manage permissions via Admin UI were added. Please move your permission settings from [local.yaml to your database](https://www.kimai.org/documentation/permissions.html)
|
- New functionality to manage permissions via Admin UI were added. Please move your permission settings from [local.yaml to your database](https://www.kimai.org/documentation/permissions.html)
|
||||||
- New database tables were created, don't forget to [run the migrations](https://www.kimai.org/documentation/updates.html)
|
- New database tables were created, don't forget to [run the migrations](https://www.kimai.org/documentation/updates.html)
|
||||||
|
|
||||||
|
|||||||
@@ -197,6 +197,10 @@ export default class KimaiAjaxModalForm extends KimaiClickHandlerReducedInTableR
|
|||||||
err = '[' + xhr.status +'] ' + xhr.statusText;
|
err = '[' + xhr.status +'] ' + xhr.statusText;
|
||||||
}
|
}
|
||||||
alert.error(message, err);
|
alert.error(message, err);
|
||||||
|
// this is useful for changing form fields and retrying to save (and in development to test form changes)
|
||||||
|
setTimeout(function() {
|
||||||
|
btn.button('reset');
|
||||||
|
}, 1500);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -5,301 +5,284 @@
|
|||||||
* file that was distributed with this source code.
|
* file that was distributed with this source code.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
.invoice {
|
body.invoice_print {
|
||||||
margin: 0;
|
background-color: #eee;
|
||||||
border: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: $screen-sm-min) {
|
|
||||||
.invoice {
|
.invoice {
|
||||||
margin: 25px;
|
margin: 105px auto 30px auto;
|
||||||
|
padding: 50px 65px;
|
||||||
|
min-height: 297mm;
|
||||||
|
position: relative;
|
||||||
|
max-width: 210mm;
|
||||||
|
box-shadow: 0 0 20px rgba(80,80,80,0.7);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
.page-header h3 {
|
||||||
.page-header-small{
|
border-color: #ddd;
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.invoice-address {
|
|
||||||
margin-top: 30px;
|
|
||||||
margin-bottom: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.invoice-records {}
|
|
||||||
table.invoice-meta th {
|
|
||||||
padding-right: 40px;
|
|
||||||
}
|
|
||||||
table.invoice-sum th {
|
|
||||||
width: 70%;
|
|
||||||
}
|
|
||||||
table.invoice-sum th, table.invoice-sum td {
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ================================ FREELANCER INVOICE ================================ */
|
|
||||||
|
|
||||||
#freelancer-invoice {
|
|
||||||
font: 15px/1 $font-family-sans-serif;
|
|
||||||
box-sizing: border-box;
|
|
||||||
|
|
||||||
h1 {
|
|
||||||
font: bold 100% sans-serif;
|
|
||||||
letter-spacing: 0.5em;
|
|
||||||
text-align: center;
|
|
||||||
text-transform: uppercase;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
h2 {
|
div.invoice-address {
|
||||||
font: bold 1.5em sans-serif;
|
margin-top: 30px;
|
||||||
margin-bottom: 1em;
|
margin-bottom: 30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* table */
|
table.invoice-meta th {
|
||||||
|
padding-right: 40px;
|
||||||
table {
|
|
||||||
table-layout: fixed;
|
|
||||||
width: 100%;
|
|
||||||
}
|
}
|
||||||
|
table.invoice-sum th {
|
||||||
table {
|
width: 70%;
|
||||||
border-collapse: separate;
|
|
||||||
border-spacing: 0;
|
|
||||||
}
|
}
|
||||||
|
table.invoice-sum th, table.invoice-sum td {
|
||||||
/* header */
|
|
||||||
|
|
||||||
header {
|
|
||||||
}
|
|
||||||
|
|
||||||
header:after {
|
|
||||||
clear: both;
|
|
||||||
content: "";
|
|
||||||
display: table;
|
|
||||||
font-size: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
header address {
|
|
||||||
float: left;
|
|
||||||
font-style: normal;
|
|
||||||
line-height: 1.25;
|
|
||||||
margin: 0 1em 1em 0;
|
|
||||||
font-weight: bold;
|
|
||||||
font-size: 0.7em;
|
|
||||||
}
|
|
||||||
|
|
||||||
article address {
|
|
||||||
font-size: 1em;
|
|
||||||
float: left;
|
|
||||||
margin: 1em 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
article.address {
|
|
||||||
margin-bottom: 1.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
article.footer {
|
|
||||||
margin-top: 60px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* article */
|
|
||||||
|
|
||||||
article, table.meta, table.inventory {
|
|
||||||
margin: 1em 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
article:after {
|
|
||||||
clear: both;
|
|
||||||
content: "";
|
|
||||||
display: table;
|
|
||||||
}
|
|
||||||
|
|
||||||
article h1 {
|
|
||||||
clip: rect(0 0 0 0);
|
|
||||||
position: absolute;
|
|
||||||
}
|
|
||||||
|
|
||||||
article p {
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* table meta & balance */
|
|
||||||
|
|
||||||
table.meta:after, table.balance:after {
|
|
||||||
clear: both;
|
|
||||||
content: "";
|
|
||||||
display: table;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* table meta */
|
|
||||||
|
|
||||||
table.meta {
|
|
||||||
float: right;
|
|
||||||
width: 50%;
|
|
||||||
font-size: 85%;
|
|
||||||
|
|
||||||
th {
|
|
||||||
text-align: right;
|
|
||||||
font-weight: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
td {
|
|
||||||
width: 120px;
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* table balance */
|
|
||||||
|
|
||||||
table.balance {
|
|
||||||
float: right;
|
|
||||||
th {
|
|
||||||
text-align: right;
|
|
||||||
font-weight: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
td {
|
|
||||||
width: 140px;
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
.total {
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* table items */
|
|
||||||
|
|
||||||
table.inventory {
|
|
||||||
clear: both;
|
|
||||||
width: 100%;
|
|
||||||
margin-top: 2em;
|
|
||||||
border-bottom: 1px solid #000;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.inventory th {
|
|
||||||
font-weight: bold;
|
|
||||||
border-width: 0 0 1px 0;
|
|
||||||
border-bottom: 1px solid #000;
|
|
||||||
}
|
|
||||||
table.inventory th,
|
|
||||||
table.inventory td {
|
|
||||||
padding: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.inventory td:nth-child(1),
|
|
||||||
table.inventory th:nth-child(1) {
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.inventory td:nth-child(2),
|
|
||||||
table.inventory th:nth-child(2) {
|
|
||||||
text-align: center;
|
|
||||||
width: 20%;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.inventory td:nth-child(3),
|
|
||||||
table.inventory th:nth-child(3) {
|
|
||||||
text-align: center;
|
|
||||||
width: 20%;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.inventory td:nth-child(4),
|
|
||||||
table.inventory th:nth-child(4) {
|
|
||||||
text-align: right;
|
text-align: right;
|
||||||
width: 20%;
|
}
|
||||||
|
|
||||||
|
.invoice-items {
|
||||||
|
margin-top: 2em;
|
||||||
|
margin-bottom: 3em;
|
||||||
|
|
||||||
|
.table {
|
||||||
|
thead th {
|
||||||
|
font-weight: bold;
|
||||||
|
border-bottom: 1px solid #ddd;
|
||||||
|
padding-bottom: 15px
|
||||||
|
}
|
||||||
|
tfoot {
|
||||||
|
border-top: 1px solid #ddd;
|
||||||
|
|
||||||
|
tr:first-child th,
|
||||||
|
tr:first-child td {
|
||||||
|
padding-top: 15px
|
||||||
|
}
|
||||||
|
tr {
|
||||||
|
th,td {
|
||||||
|
border:none;
|
||||||
|
padding: 4px 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* footer */
|
.footer {
|
||||||
|
border-top: 1px solid #ccc;
|
||||||
footer {
|
|
||||||
margin-top: 50px;
|
margin-top: 50px;
|
||||||
}
|
|
||||||
|
|
||||||
footer table {
|
|
||||||
border-top: 1px solid #000;
|
|
||||||
font-size: 0.8em;
|
font-size: 0.8em;
|
||||||
padding-top: 5px;
|
padding-top: 10px;
|
||||||
}
|
width: 100%;
|
||||||
|
|
||||||
footer th {
|
|
||||||
text-align: left;
|
|
||||||
font-weight: bold;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
footer table th:nth-child(1) {
|
|
||||||
width: 32%;
|
|
||||||
}
|
|
||||||
|
|
||||||
footer table th:nth-child(3) {
|
|
||||||
width: 28%;
|
|
||||||
}
|
|
||||||
|
|
||||||
footer td {
|
|
||||||
text-align: left;
|
|
||||||
vertical-align: bottom;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* aside */
|
|
||||||
|
|
||||||
aside {
|
|
||||||
position: absolute;
|
|
||||||
top: 10in;
|
|
||||||
width: 7.5in;
|
|
||||||
}
|
|
||||||
|
|
||||||
aside h1 {
|
|
||||||
border: none;
|
|
||||||
border-width: 0 0 1px;
|
|
||||||
margin: 0 0 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
aside h1 {
|
|
||||||
border-color: #999;
|
|
||||||
border-bottom-style: solid;
|
|
||||||
}
|
|
||||||
|
|
||||||
aside p {
|
|
||||||
font-size: 85%;
|
|
||||||
}
|
|
||||||
|
|
||||||
aside div {
|
|
||||||
margin-top: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
aside div p {
|
|
||||||
line-height: 1.4;
|
|
||||||
}
|
|
||||||
|
|
||||||
aside p span {
|
|
||||||
width: 130px;
|
|
||||||
font-weight: bold;
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media print {
|
|
||||||
* {
|
|
||||||
-webkit-print-color-adjust: exact;
|
|
||||||
}
|
|
||||||
body, .wrapper, #freelancer-invoice {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ================================ FREELANCER INVOICE ================================ */
|
||||||
|
|
||||||
#freelancer-invoice {
|
#freelancer-invoice {
|
||||||
margin: 1em 2em;
|
|
||||||
|
box-sizing: border-box;
|
||||||
span:empty {
|
|
||||||
display: none;
|
h1 {
|
||||||
|
font: bold 100% sans-serif;
|
||||||
|
letter-spacing: 0.5em;
|
||||||
|
text-align: center;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
font: bold 1.5em sans-serif;
|
||||||
|
margin-bottom: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* table */
|
||||||
|
|
||||||
|
table {
|
||||||
|
table-layout: fixed;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
border-collapse: separate;
|
||||||
|
border-spacing: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* header */
|
||||||
|
|
||||||
|
header {
|
||||||
|
&:after {
|
||||||
|
clear: both;
|
||||||
|
content: "";
|
||||||
|
display: table;
|
||||||
|
font-size: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
address {
|
||||||
|
float: left;
|
||||||
|
font-style: normal;
|
||||||
|
line-height: 1.25;
|
||||||
|
margin: 0 1em 1em 0;
|
||||||
|
font-size: 0.7em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
article address {
|
||||||
|
font-size: 1em;
|
||||||
|
float: left;
|
||||||
|
margin: 1em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
article.address {
|
||||||
|
margin-bottom: 1.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* article */
|
||||||
|
|
||||||
|
article, table.meta, table.inventory {
|
||||||
|
margin: 1em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
article:after {
|
||||||
|
clear: both;
|
||||||
|
content: "";
|
||||||
|
display: table;
|
||||||
|
}
|
||||||
|
|
||||||
|
article h1 {
|
||||||
|
clip: rect(0 0 0 0);
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
article p {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* table meta & balance */
|
||||||
|
|
||||||
|
table.meta:after, table.balance:after {
|
||||||
|
clear: both;
|
||||||
|
content: "";
|
||||||
|
display: table;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* table meta */
|
||||||
|
|
||||||
|
table.meta {
|
||||||
|
float: right;
|
||||||
|
width: 50%;
|
||||||
|
font-size: 85%;
|
||||||
|
|
||||||
|
th {
|
||||||
|
text-align: right;
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
td {
|
||||||
|
width: 120px;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* table balance */
|
||||||
|
|
||||||
|
table.balance {
|
||||||
|
float: right;
|
||||||
|
th {
|
||||||
|
text-align: right;
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
td {
|
||||||
|
width: 140px;
|
||||||
|
text-align: right;
|
||||||
|
line-height: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.total {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* table items */
|
||||||
|
|
||||||
|
table.inventory {
|
||||||
|
clear: both;
|
||||||
|
width: 100%;
|
||||||
|
margin-top: 2em;
|
||||||
|
border-bottom: 1px solid #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
table.inventory th,
|
||||||
|
table.inventory td {
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
table.inventory thead th {
|
||||||
|
font-weight: bold;
|
||||||
|
border-width: 0 0 1px 0;
|
||||||
|
border-bottom: 1px solid #000;
|
||||||
|
padding-bottom: 15px
|
||||||
|
}
|
||||||
|
|
||||||
|
table.inventory tbody tr:first-child td {
|
||||||
|
padding-top: 15px
|
||||||
|
}
|
||||||
|
|
||||||
|
table.inventory tbody tr:last-child td {
|
||||||
|
padding-bottom: 15px
|
||||||
|
}
|
||||||
|
|
||||||
|
table.inventory td:nth-child(1),
|
||||||
|
table.inventory th:nth-child(1) {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
table.inventory td:nth-child(2),
|
||||||
|
table.inventory th:nth-child(2) {
|
||||||
|
text-align: center;
|
||||||
|
width: 20%;
|
||||||
|
}
|
||||||
|
|
||||||
|
table.inventory td:nth-child(3),
|
||||||
|
table.inventory th:nth-child(3) {
|
||||||
|
text-align: center;
|
||||||
|
width: 20%;
|
||||||
|
}
|
||||||
|
|
||||||
|
table.inventory td:nth-child(4),
|
||||||
|
table.inventory th:nth-child(4) {
|
||||||
|
text-align: right;
|
||||||
|
width: 20%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.add, .cut {
|
.footer {
|
||||||
display: none;
|
border-color: #000;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.invoice {
|
/* ================================ PRINTING RULES ================================ */
|
||||||
width: unset;
|
|
||||||
|
@media print {
|
||||||
|
* {
|
||||||
|
-webkit-print-color-adjust: exact;
|
||||||
|
}
|
||||||
|
body, .wrapper {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
background-color: white;
|
||||||
|
}
|
||||||
|
#freelancer-invoice {
|
||||||
|
span:empty {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.add, .cut {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.invoice {
|
||||||
|
margin: 1em 2em;
|
||||||
|
font-size: 90%;
|
||||||
|
width: unset;
|
||||||
|
padding: 0;
|
||||||
|
min-height: unset;
|
||||||
|
position: unset;
|
||||||
|
max-width: unset;
|
||||||
|
box-shadow: unset;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -76,6 +76,12 @@
|
|||||||
"symfony/thanks": "^1.0",
|
"symfony/thanks": "^1.0",
|
||||||
"symfony/web-server-bundle": "^4.0"
|
"symfony/web-server-bundle": "^4.0"
|
||||||
},
|
},
|
||||||
|
"repositories": [
|
||||||
|
{
|
||||||
|
"type": "artifact",
|
||||||
|
"url": "var/packages/"
|
||||||
|
}
|
||||||
|
],
|
||||||
"config": {
|
"config": {
|
||||||
"platform": {
|
"platform": {
|
||||||
"php": "7.2"
|
"php": "7.2"
|
||||||
|
|||||||
1393
composer.lock
generated
1393
composer.lock
generated
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -5,10 +5,10 @@
|
|||||||
"build/runtime.4ee6be68.js",
|
"build/runtime.4ee6be68.js",
|
||||||
"build/0.a87622f3.js",
|
"build/0.a87622f3.js",
|
||||||
"build/1.c1bee41f.js",
|
"build/1.c1bee41f.js",
|
||||||
"build/app.120bf1d1.js"
|
"build/app.c36bdaf3.js"
|
||||||
],
|
],
|
||||||
"css": [
|
"css": [
|
||||||
"build/app.ca6a8b7f.css"
|
"build/app.e5eb271e.css"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"chart": {
|
"chart": {
|
||||||
@@ -35,8 +35,8 @@
|
|||||||
"build/runtime.4ee6be68.js": "sha384-xNNrNinl64G3nCUrIskgSjU0mUXXCB9lj6XCSInBTwxSKXk8uTMafnLHtdWdIGtd",
|
"build/runtime.4ee6be68.js": "sha384-xNNrNinl64G3nCUrIskgSjU0mUXXCB9lj6XCSInBTwxSKXk8uTMafnLHtdWdIGtd",
|
||||||
"build/0.a87622f3.js": "sha384-ncT/BKhCsqH6jhxwdsSG95m1ei7ZZjeZtzH1262h+OPUU80TSFFE3dt+abcHHMok",
|
"build/0.a87622f3.js": "sha384-ncT/BKhCsqH6jhxwdsSG95m1ei7ZZjeZtzH1262h+OPUU80TSFFE3dt+abcHHMok",
|
||||||
"build/1.c1bee41f.js": "sha384-7UVWcP6Hefp2k/CrtGSITKXx4dSZqtvpAiU8WX7dClETkzMewrUjoCRtVXQ5j3KI",
|
"build/1.c1bee41f.js": "sha384-7UVWcP6Hefp2k/CrtGSITKXx4dSZqtvpAiU8WX7dClETkzMewrUjoCRtVXQ5j3KI",
|
||||||
"build/app.120bf1d1.js": "sha384-2Eohdb33hChqGmyJIUGgZFiqzPfUDIv74IoMAX+09VozhJDD/GtPIg5+N2CCOACl",
|
"build/app.c36bdaf3.js": "sha384-lU6L1apGsiE2oAJfhqW7YaQo+J1eA5ysuOCfERdfGp63Z9ouvhQ9Ov3DlN+6aYXK",
|
||||||
"build/app.ca6a8b7f.css": "sha384-NrITZnNaaIkzEjZkOjxciIbq6V6dKC9VlbJfxliLc2vi8n0113/yLfhbwi5rjzg2",
|
"build/app.e5eb271e.css": "sha384-BNUNuu56X2x+Xmn1JrQe6d3mt6R+QZu7OgR/4OXkBDueSE6vu0UqmRaDPkyiRzMw",
|
||||||
"build/2.7be60d8d.js": "sha384-txR0QG+838LKYtPQ99Gx4OU7WmgN9J3joZEyGwIskSz74EN1T4/IBVnmNaKiFN1q",
|
"build/2.7be60d8d.js": "sha384-txR0QG+838LKYtPQ99Gx4OU7WmgN9J3joZEyGwIskSz74EN1T4/IBVnmNaKiFN1q",
|
||||||
"build/chart.0af3f813.js": "sha384-I57c9DtU3AOG2kzKqIZkIu0hi1aGYHRZ5QG4LKC9+9slzJnAMttPGXoL2cQG3m6y",
|
"build/chart.0af3f813.js": "sha384-I57c9DtU3AOG2kzKqIZkIu0hi1aGYHRZ5QG4LKC9+9slzJnAMttPGXoL2cQG3m6y",
|
||||||
"build/calendar.6f94d071.js": "sha384-CnbCkgF1mbGOycK2A78CF1ZsQ4O07ol9U4gB0Tiec0wKu1mNw8lE22lvoRNzYs/8",
|
"build/calendar.6f94d071.js": "sha384-CnbCkgF1mbGOycK2A78CF1ZsQ4O07ol9U4gB0Tiec0wKu1mNw8lE22lvoRNzYs/8",
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
"build/0.a87622f3.js": "build/0.a87622f3.js",
|
"build/0.a87622f3.js": "build/0.a87622f3.js",
|
||||||
"build/1.c1bee41f.js": "build/1.c1bee41f.js",
|
"build/1.c1bee41f.js": "build/1.c1bee41f.js",
|
||||||
"build/2.7be60d8d.js": "build/2.7be60d8d.js",
|
"build/2.7be60d8d.js": "build/2.7be60d8d.js",
|
||||||
"build/app.css": "build/app.ca6a8b7f.css",
|
"build/app.css": "build/app.e5eb271e.css",
|
||||||
"build/app.js": "build/app.120bf1d1.js",
|
"build/app.js": "build/app.c36bdaf3.js",
|
||||||
"build/calendar.css": "build/calendar.36ef2a02.css",
|
"build/calendar.css": "build/calendar.36ef2a02.css",
|
||||||
"build/calendar.js": "build/calendar.6f94d071.js",
|
"build/calendar.js": "build/calendar.6f94d071.js",
|
||||||
"build/chart.js": "build/chart.0af3f813.js",
|
"build/chart.js": "build/chart.0af3f813.js",
|
||||||
|
|||||||
@@ -17,11 +17,11 @@ class Constants
|
|||||||
/**
|
/**
|
||||||
* The current release version
|
* The current release version
|
||||||
*/
|
*/
|
||||||
public const VERSION = '1.5';
|
public const VERSION = '1.6';
|
||||||
/**
|
/**
|
||||||
* The current release status, either "stable" or "dev"
|
* The current release status, either "stable" or "dev"
|
||||||
*/
|
*/
|
||||||
public const STATUS = 'stable';
|
public const STATUS = 'dev';
|
||||||
/**
|
/**
|
||||||
* The software name
|
* The software name
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -302,10 +302,6 @@ class ProfileController extends AbstractController
|
|||||||
|
|
||||||
private function createPreferencesForm(User $user): FormInterface
|
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(
|
return $this->createForm(
|
||||||
UserPreferencesForm::class,
|
UserPreferencesForm::class,
|
||||||
$user,
|
$user,
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ class CustomerFixtures extends Fixture
|
|||||||
*/
|
*/
|
||||||
public function load(ObjectManager $manager)
|
public function load(ObjectManager $manager)
|
||||||
{
|
{
|
||||||
$faker = Factory::create();
|
$faker = Factory::create('at_AT');
|
||||||
|
|
||||||
$amountCustomers = rand(self::MIN_CUSTOMERS, self::MAX_CUSTOMERS);
|
$amountCustomers = rand(self::MIN_CUSTOMERS, self::MAX_CUSTOMERS);
|
||||||
for ($c = 1; $c <= $amountCustomers; $c++) {
|
for ($c = 1; $c <= $amountCustomers; $c++) {
|
||||||
@@ -100,6 +100,7 @@ class CustomerFixtures extends Fixture
|
|||||||
->setCountry($faker->countryCode)
|
->setCountry($faker->countryCode)
|
||||||
->setTimezone($faker->timezone)
|
->setTimezone($faker->timezone)
|
||||||
->setVisible($visible)
|
->setVisible($visible)
|
||||||
|
->setVatId($faker->vat)
|
||||||
;
|
;
|
||||||
|
|
||||||
if (rand(0, 3) % 3) {
|
if (rand(0, 3) % 3) {
|
||||||
|
|||||||
@@ -31,7 +31,9 @@ class InvoiceFixtures extends Fixture
|
|||||||
*/
|
*/
|
||||||
public function load(ObjectManager $manager)
|
public function load(ObjectManager $manager)
|
||||||
{
|
{
|
||||||
foreach ($this->getInvoiceConfigs() as $invoiceConfig) {
|
$faker = Factory::create('at_AT');
|
||||||
|
|
||||||
|
foreach ($this->getInvoiceConfigs($faker) as $invoiceConfig) {
|
||||||
$template = new InvoiceTemplate();
|
$template = new InvoiceTemplate();
|
||||||
|
|
||||||
// name, title, renderer, calculator, numberGenerator, company, vat, dueDays, address, paymentTerms
|
// name, title, renderer, calculator, numberGenerator, company, vat, dueDays, address, paymentTerms
|
||||||
@@ -44,8 +46,11 @@ class InvoiceFixtures extends Fixture
|
|||||||
->setCompany($invoiceConfig[5])
|
->setCompany($invoiceConfig[5])
|
||||||
->setVat($invoiceConfig[6])
|
->setVat($invoiceConfig[6])
|
||||||
->setDueDays($invoiceConfig[7])
|
->setDueDays($invoiceConfig[7])
|
||||||
->setAddress($invoiceConfig[8])
|
->setPaymentTerms($invoiceConfig[8])
|
||||||
->setPaymentTerms($invoiceConfig[9])
|
->setVatId($faker->vat)
|
||||||
|
->setAddress($this->generateAddress($faker))
|
||||||
|
->setContact($this->generateContact($faker))
|
||||||
|
->setPaymentDetails($this->generatePaymentDetails($faker))
|
||||||
;
|
;
|
||||||
|
|
||||||
$manager->persist($template);
|
$manager->persist($template);
|
||||||
@@ -53,10 +58,8 @@ class InvoiceFixtures extends Fixture
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function getInvoiceConfigs()
|
private function getInvoiceConfigs(Generator $faker)
|
||||||
{
|
{
|
||||||
$faker = Factory::create();
|
|
||||||
|
|
||||||
$paymentTerms =
|
$paymentTerms =
|
||||||
'I would like to thank you for your confidence and will gladly be there for you in the future.' .
|
'I would like to thank you for your confidence and will gladly be there for you in the future.' .
|
||||||
PHP_EOL .
|
PHP_EOL .
|
||||||
@@ -64,11 +67,9 @@ class InvoiceFixtures extends Fixture
|
|||||||
'as reference.'
|
'as reference.'
|
||||||
;
|
;
|
||||||
|
|
||||||
$address =
|
$paymentTerms_alt =
|
||||||
$faker->streetAddress . PHP_EOL .
|
$faker->firstName . ', thank you very much. We really appreciate your business.' . PHP_EOL .
|
||||||
$faker->city . ', ' . $faker->stateAbbr . ' ' . $faker->postcode . 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.'
|
||||||
'Phone: ' . $faker->phoneNumber . PHP_EOL .
|
|
||||||
'Email: ' . $faker->safeEmail
|
|
||||||
;
|
;
|
||||||
|
|
||||||
$paymentTerms_de =
|
$paymentTerms_de =
|
||||||
@@ -85,17 +86,14 @@ class InvoiceFixtures extends Fixture
|
|||||||
|
|
||||||
// name, title, renderer, calculator, numberGenerator, company, vat, dueDays, address, paymentTerms
|
// name, title, renderer, calculator, numberGenerator, company, vat, dueDays, address, paymentTerms
|
||||||
return [
|
return [
|
||||||
['Invoice (HTML)', 'Company name', 'default', 'default', 'default', $faker->company, 19, 30, $address, $paymentTerms],
|
['Invoice (HTML)', 'Company name', 'default', 'default', 'default', $faker->company, 19, 30, $paymentTerms],
|
||||||
['Freelancer (HTML, short)', 'Invoice', 'freelancer', 'short', 'default', $faker->company, 19, 14, $this->generateAddress($faker), $paymentTerms_de],
|
['Freelancer (HTML, short)', 'Invoice', 'freelancer', 'short', 'default', $faker->company, 19, 14, $paymentTerms_de],
|
||||||
['Timesheet (HTML)', 'Timesheet', 'timesheet', 'default', 'default', $faker->company, 19, 7, $this->generateAddress($faker), ''],
|
['Timesheet (HTML)', 'Timesheet', 'timesheet', 'default', 'default', $faker->company, 19, 7, $paymentTerms_alt],
|
||||||
['Company invoice (DOCX)', 'Invoice', 'company', 'default', 'default', 'Kimai Inc.', 19, 14, $this->generateAddress($faker, true), $this->generatePaymentTerms($faker)],
|
['Company invoice (DOCX)', 'Invoice', 'company', 'default', 'default', 'Kimai Inc.', 19, 14, $paymentTerms_alt],
|
||||||
['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, '', ''],
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function generatePaymentTerms(Generator $faker)
|
protected function generatePaymentDetails(Generator $faker)
|
||||||
{
|
{
|
||||||
return
|
return
|
||||||
'Acme Bank' . PHP_EOL .
|
'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
|
||||||
return
|
'Phone: ' . $faker->phoneNumber . PHP_EOL .
|
||||||
$faker->name . ' - ' .
|
'Email: ' . $faker->safeEmail . PHP_EOL .
|
||||||
$faker->streetAddress . '-' .
|
'Web: www.' . $faker->domainName
|
||||||
$faker->postcode . ' ' . $faker->city;
|
;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function generateAddress(Generator $faker)
|
||||||
|
{
|
||||||
return
|
return
|
||||||
'Kimai Inc.' . PHP_EOL .
|
'Kimai Inc.' . PHP_EOL .
|
||||||
$faker->streetAddress . 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.
|
* Whether we should deactivate foreign key support for SQLite.
|
||||||
* This is required, if columns are changed.
|
* 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
|
* @return bool
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -79,6 +79,14 @@ class Customer implements EntityWithMetaFields
|
|||||||
*/
|
*/
|
||||||
private $company;
|
private $company;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*
|
||||||
|
* @ORM\Column(name="vat_id", type="string", length=50, nullable=true)
|
||||||
|
* @Assert\Length(max=50)
|
||||||
|
*/
|
||||||
|
private $vatId;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var string
|
* @var string
|
||||||
*
|
*
|
||||||
@@ -272,6 +280,18 @@ class Customer implements EntityWithMetaFields
|
|||||||
return $this->company;
|
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
|
public function setContact(?string $contact): Customer
|
||||||
{
|
{
|
||||||
$this->contact = $contact;
|
$this->contact = $contact;
|
||||||
|
|||||||
@@ -56,6 +56,14 @@ class InvoiceTemplate
|
|||||||
*/
|
*/
|
||||||
private $company;
|
private $company;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*
|
||||||
|
* @ORM\Column(name="vat_id", type="string", length=50, nullable=true)
|
||||||
|
* @Assert\Length(max=50)
|
||||||
|
*/
|
||||||
|
private $vatId;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var string
|
* @var string
|
||||||
*
|
*
|
||||||
@@ -63,6 +71,13 @@ class InvoiceTemplate
|
|||||||
*/
|
*/
|
||||||
private $address;
|
private $address;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*
|
||||||
|
* @ORM\Column(name="contact", type="text", nullable=true)
|
||||||
|
*/
|
||||||
|
private $contact;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var int
|
* @var int
|
||||||
*
|
*
|
||||||
@@ -109,6 +124,13 @@ class InvoiceTemplate
|
|||||||
*/
|
*/
|
||||||
private $paymentTerms;
|
private $paymentTerms;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*
|
||||||
|
* @ORM\Column(name="payment_details", type="text", nullable=true)
|
||||||
|
*/
|
||||||
|
private $paymentDetails;
|
||||||
|
|
||||||
public function getId(): ?int
|
public function getId(): ?int
|
||||||
{
|
{
|
||||||
return $this->id;
|
return $this->id;
|
||||||
@@ -234,6 +256,42 @@ class InvoiceTemplate
|
|||||||
return $this;
|
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
|
* @return string
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -71,9 +71,9 @@ final class MenuSubscriber implements EventSubscriberInterface
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ($auth->isGranted('view_invoice')) {
|
if ($auth->isGranted('view_invoice')) {
|
||||||
$menu->addItem(
|
$invoice = new MenuItemModel('invoice', 'menu.invoice', 'invoice', [], $this->getIcon('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')) {
|
if ($auth->isGranted('create_export')) {
|
||||||
|
|||||||
@@ -56,6 +56,10 @@ class CustomerEditForm extends AbstractType
|
|||||||
'label' => 'label.company',
|
'label' => 'label.company',
|
||||||
'required' => false,
|
'required' => false,
|
||||||
])
|
])
|
||||||
|
->add('vatId', TextType::class, [
|
||||||
|
'label' => 'label.vat_id',
|
||||||
|
'required' => false,
|
||||||
|
])
|
||||||
->add('contact', TextType::class, [
|
->add('contact', TextType::class, [
|
||||||
'label' => 'label.contact',
|
'label' => 'label.contact',
|
||||||
'required' => false,
|
'required' => false,
|
||||||
|
|||||||
@@ -40,14 +40,26 @@ class InvoiceTemplateForm extends AbstractType
|
|||||||
->add('company', TextType::class, [
|
->add('company', TextType::class, [
|
||||||
'label' => 'label.company',
|
'label' => 'label.company',
|
||||||
])
|
])
|
||||||
|
->add('vatId', TextType::class, [
|
||||||
|
'label' => 'label.vat_id',
|
||||||
|
'required' => false,
|
||||||
|
])
|
||||||
->add('address', TextareaType::class, [
|
->add('address', TextareaType::class, [
|
||||||
'label' => 'label.address',
|
'label' => 'label.address',
|
||||||
'required' => false,
|
'required' => false,
|
||||||
])
|
])
|
||||||
|
->add('contact', TextareaType::class, [
|
||||||
|
'label' => 'label.contact',
|
||||||
|
'required' => false,
|
||||||
|
])
|
||||||
->add('paymentTerms', TextareaType::class, [
|
->add('paymentTerms', TextareaType::class, [
|
||||||
'label' => 'label.payment_terms',
|
'label' => 'label.payment_terms',
|
||||||
'required' => false,
|
'required' => false,
|
||||||
])
|
])
|
||||||
|
->add('paymentDetails', TextareaType::class, [
|
||||||
|
'label' => 'label.invoice_bank_account',
|
||||||
|
'required' => false,
|
||||||
|
])
|
||||||
->add('dueDays', TextType::class, [
|
->add('dueDays', TextType::class, [
|
||||||
'label' => 'label.due_days',
|
'label' => 'label.due_days',
|
||||||
])
|
])
|
||||||
@@ -71,6 +83,11 @@ class InvoiceTemplateForm extends AbstractType
|
|||||||
'csrf_protection' => true,
|
'csrf_protection' => true,
|
||||||
'csrf_field_name' => '_token',
|
'csrf_field_name' => '_token',
|
||||||
'csrf_token_id' => 'admin_invoice_template',
|
'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, [
|
$builder->add('create', SubmitType::class, [
|
||||||
'label' => 'button.print',
|
'label' => 'button.print',
|
||||||
'attr' => ['formtarget' => 'invoice'],
|
'attr' => ['formtarget' => '_blank'],
|
||||||
]);
|
]);
|
||||||
$builder->add('preview', SubmitType::class, [
|
$builder->add('preview', SubmitType::class, [
|
||||||
'label' => 'button.preview',
|
'label' => 'button.preview',
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ class InvoiceCalculatorType extends AbstractType
|
|||||||
return $renderer;
|
return $renderer;
|
||||||
},
|
},
|
||||||
'translation_domain' => 'invoice-calculator',
|
'translation_domain' => 'invoice-calculator',
|
||||||
|
'search' => false,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ class InvoiceNumberGeneratorType extends AbstractType
|
|||||||
return $renderer;
|
return $renderer;
|
||||||
},
|
},
|
||||||
'translation_domain' => 'invoice-numbergenerator',
|
'translation_domain' => 'invoice-numbergenerator',
|
||||||
|
'search' => false,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ class InvoiceRendererType extends AbstractType
|
|||||||
},
|
},
|
||||||
'translation_domain' => 'invoice-renderer',
|
'translation_domain' => 'invoice-renderer',
|
||||||
'docu_chapter' => 'invoices.html',
|
'docu_chapter' => 'invoices.html',
|
||||||
|
'search' => false,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ class YesNoType extends AbstractType
|
|||||||
{
|
{
|
||||||
$resolver->setDefaults([
|
$resolver->setDefaults([
|
||||||
'value' => true,
|
'value' => true,
|
||||||
'false_values' => [null, 0, false, 'false'],
|
'false_values' => [null, 0, false, 'false', '', '0'],
|
||||||
'required' => false,
|
'required' => false,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -114,6 +114,9 @@ trait RendererTrait
|
|||||||
'template.title' => $model->getTemplate()->getTitle(),
|
'template.title' => $model->getTemplate()->getTitle(),
|
||||||
'template.payment_terms' => $model->getTemplate()->getPaymentTerms(),
|
'template.payment_terms' => $model->getTemplate()->getPaymentTerms(),
|
||||||
'template.due_days' => $model->getTemplate()->getDueDays(),
|
'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.begin' => $this->getFormattedDateTime($model->getQuery()->getBegin()),
|
||||||
'query.day' => $model->getQuery()->getBegin()->format('d'),
|
'query.day' => $model->getQuery()->getBegin()->format('d'),
|
||||||
@@ -164,6 +167,7 @@ trait RendererTrait
|
|||||||
'customer.name' => $customer->getName(),
|
'customer.name' => $customer->getName(),
|
||||||
'customer.contact' => $customer->getContact(),
|
'customer.contact' => $customer->getContact(),
|
||||||
'customer.company' => $customer->getCompany(),
|
'customer.company' => $customer->getCompany(),
|
||||||
|
'customer.vat' => $customer->getVatId(),
|
||||||
'customer.number' => $customer->getNumber(),
|
'customer.number' => $customer->getNumber(),
|
||||||
'customer.country' => $customer->getCountry(),
|
'customer.country' => $customer->getCountry(),
|
||||||
'customer.homepage' => $customer->getHomepage(),
|
'customer.homepage' => $customer->getHomepage(),
|
||||||
|
|||||||
@@ -15,35 +15,23 @@ use App\Invoice\RendererInterface;
|
|||||||
use Symfony\Component\HttpFoundation\Response;
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
use Twig\Environment;
|
use Twig\Environment;
|
||||||
|
|
||||||
class TwigRenderer implements RendererInterface
|
final class TwigRenderer implements RendererInterface
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* @var Environment
|
* @var Environment
|
||||||
*/
|
*/
|
||||||
protected $twig;
|
private $twig;
|
||||||
|
|
||||||
/**
|
|
||||||
* @param Environment $twig
|
|
||||||
*/
|
|
||||||
public function __construct(Environment $twig)
|
public function __construct(Environment $twig)
|
||||||
{
|
{
|
||||||
$this->twig = $twig;
|
$this->twig = $twig;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param InvoiceDocument $document
|
|
||||||
* @return bool
|
|
||||||
*/
|
|
||||||
public function supports(InvoiceDocument $document): 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
|
public function render(InvoiceDocument $document, InvoiceModel $model): Response
|
||||||
{
|
{
|
||||||
$content = $this->twig->render('@invoice/' . basename($document->getFilename()), [
|
$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()->orX(
|
||||||
$qb->expr()->like('c.name', ':searchTerm'),
|
$qb->expr()->like('c.name', ':searchTerm'),
|
||||||
$qb->expr()->like('c.comment', ':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.number', ':searchTerm'),
|
||||||
$qb->expr()->like('c.contact', ':searchTerm'),
|
$qb->expr()->like('c.contact', ':searchTerm'),
|
||||||
$qb->expr()->like('c.phone', ':searchTerm'),
|
$qb->expr()->like('c.phone', ':searchTerm'),
|
||||||
|
|||||||
@@ -25,7 +25,14 @@
|
|||||||
{{ form_row(form.comment) }}
|
{{ form_row(form.comment) }}
|
||||||
<fieldset>
|
<fieldset>
|
||||||
{#<legend>Company data</legend>#}
|
{#<legend>Company data</legend>#}
|
||||||
{{ form_row(form.company) }}
|
<div class="row">
|
||||||
|
<div class="col-md-8">
|
||||||
|
{{ form_row(form.company) }}
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
{{ form_row(form.vatId) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
{{ form_row(form.address) }}
|
{{ form_row(form.address) }}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-4">
|
<div class="col-md-4">
|
||||||
|
|||||||
@@ -10,56 +10,66 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row invoice-info">
|
<div class="row">
|
||||||
<div class="col-sm-4 invoice-col">
|
<div class="col-sm-5">
|
||||||
{{ 'invoice.from'|trans }}
|
{{ 'invoice.from'|trans }}
|
||||||
<address contenteditable="true">
|
<address contenteditable="true">
|
||||||
<strong>{{ model.template.company }}</strong><br>
|
<strong>{{ model.template.company }}</strong><br>
|
||||||
{{ model.template.address|trim|nl2br }}
|
{{ model.template.address|trim|nl2br }}
|
||||||
|
{% if model.template.vatId is not empty %}
|
||||||
|
<br>
|
||||||
|
{{ 'label.vat_id'|trans }}:
|
||||||
|
{{ model.template.vatId }}
|
||||||
|
{% endif %}
|
||||||
</address>
|
</address>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-sm-4 invoice-col">
|
<div class="col-sm-2"></div>
|
||||||
|
<div class="col-sm-5">
|
||||||
{% set customerPhone = model.customer.phone|default(model.customer.mobile) %}
|
{% set customerPhone = model.customer.phone|default(model.customer.mobile) %}
|
||||||
{{ 'invoice.to'|trans }}
|
{{ 'invoice.to'|trans }}
|
||||||
<address contenteditable="true">
|
<address contenteditable="true">
|
||||||
<strong>{{ model.customer.company|default(model.customer.name) }}</strong><br>
|
<strong>{{ model.customer.company|default(model.customer.name) }}</strong><br>
|
||||||
{{ model.customer.address|nl2br }}
|
{{ model.customer.address|nl2br }}
|
||||||
{% if model.customer.phone is not empty %}
|
{% if model.customer.vatId is not empty %}
|
||||||
<br>
|
<br>
|
||||||
{{ 'label.phone'|trans }}: {{ model.customer.phone }}
|
{{ 'label.vat_id'|trans }}: {{ model.customer.vatId }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if model.customer.email is not empty %}
|
|
||||||
<br>
|
|
||||||
{{ 'label.email'|trans }}: {{ model.customer.email }}
|
|
||||||
{% endif %}
|
|
||||||
</address>
|
|
||||||
</div>
|
|
||||||
<div class="col-sm-4 invoice-col">
|
|
||||||
<p contenteditable="true">
|
|
||||||
<b>{{ 'invoice.number'|trans }}: {{ model.numberGenerator.invoiceNumber }}</b>
|
|
||||||
</p>
|
|
||||||
<p contenteditable="true">
|
|
||||||
<b>{{ 'invoice.due_days'|trans }}:</b> {{ model.dueDate|date_short }}
|
|
||||||
{% if model.customer.number is not empty %}
|
{% if model.customer.number is not empty %}
|
||||||
<br><b>{{ 'label.number'|trans }}:</b> {{ model.customer.number }}
|
<br>
|
||||||
|
{{ 'label.number'|trans }}: {{ model.customer.number }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if model.query.project is not empty and model.query.project.orderNumber is not empty %}
|
{% if model.query.project is not empty and model.query.project.orderNumber is not empty %}
|
||||||
<br><b>{{ 'label.orderNumber'|trans }}:</b> {{ model.query.project.orderNumber }}
|
<br>
|
||||||
|
{{ 'label.orderNumber'|trans }}: {{ model.query.project.orderNumber }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</p>
|
</address>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
|
<div class="col-sm-5">
|
||||||
|
<p contenteditable="true">
|
||||||
|
<strong>{{ 'invoice.number'|trans }}:</strong>
|
||||||
|
{{ model.numberGenerator.invoiceNumber }}
|
||||||
|
|
||||||
|
<br>
|
||||||
|
<strong>{{ 'invoice.due_days'|trans }}:</strong>
|
||||||
|
{{ model.dueDate|date_short }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-7"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row invoice-items">
|
||||||
<div class="col-xs-12 table-responsive">
|
<div class="col-xs-12 table-responsive">
|
||||||
<table class="table table-striped">
|
<table class="table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>{{ 'label.date'|trans }}</th>
|
<th>{{ 'label.date'|trans }}</th>
|
||||||
<th>{{ 'label.description'|trans }}</th>
|
<th>{{ 'label.description'|trans }}</th>
|
||||||
<th>{{ 'label.unit_price'|trans }}</th>
|
<th>{{ 'label.unit_price'|trans }}</th>
|
||||||
<th>{{ 'label.hours'|trans }}</th>
|
<th class="text-center">{{ 'label.hours'|trans }}</th>
|
||||||
<th>{{ 'label.total_rate'|trans }}</th>
|
<th class="text-right">{{ 'label.total_rate'|trans }}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@@ -74,7 +84,7 @@
|
|||||||
{% set rate = app.user.getPreferenceValue('hourly_rate') %}
|
{% set rate = app.user.getPreferenceValue('hourly_rate') %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ entry.begin|date_short }}</td>
|
<td nowrap class="text-nowrap">{{ entry.begin|date_short }}</td>
|
||||||
<td contenteditable="true">
|
<td contenteditable="true">
|
||||||
{% if entry.description is not empty %}
|
{% if entry.description is not empty %}
|
||||||
{{ entry.description|nl2br }}
|
{{ entry.description|nl2br }}
|
||||||
@@ -82,48 +92,56 @@
|
|||||||
{{ entry.activity.name }} / {{ entry.project.name }}
|
{{ entry.activity.name }} / {{ entry.project.name }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td contenteditable="true">{{ rate|money(model.calculator.currency) }}</td>
|
<td nowrap class="text-nowrap" contenteditable="true">{{ rate|money(model.calculator.currency) }}</td>
|
||||||
<td>{{ duration }}</td>
|
<td nowrap class="text-nowrap text-center">{{ duration }}</td>
|
||||||
<td>{{ entry.rate|money(model.calculator.currency) }}</td>
|
<td nowrap class="text-nowrap text-right">{{ entry.rate|money(model.calculator.currency) }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
|
<tfoot>
|
||||||
|
<tr>
|
||||||
|
<td colspan="4" class="text-right">
|
||||||
|
{{ 'invoice.subtotal'|trans }}
|
||||||
|
</td>
|
||||||
|
<td class="text-right">{{ model.calculator.subtotal|money(model.calculator.currency) }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td colspan="4" class="text-right">
|
||||||
|
{{ 'invoice.tax'|trans }} ({{ model.calculator.vat }}%)
|
||||||
|
</td>
|
||||||
|
<td class="text-right">{{ model.calculator.tax|money(model.calculator.currency) }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td colspan="4" class="text-right">
|
||||||
|
<strong>{{ 'invoice.total'|trans }}</strong>
|
||||||
|
</td>
|
||||||
|
<td class="text-right">
|
||||||
|
<strong>{{ model.calculator.total|money(model.calculator.currency) }}</strong>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tfoot>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-xs-6">
|
<div class="col-xs-12">
|
||||||
{% if model.template.paymentTerms is not empty %}
|
{% if model.template.paymentTerms is not empty %}
|
||||||
<p class="lead">{{ 'label.payment_terms'|trans }}</p>
|
<p contenteditable="true" class="paymentTerms">
|
||||||
|
{{ model.template.paymentTerms|nl2br|md2html }}
|
||||||
<p class="text-muted well well-sm no-shadow" contenteditable="true">
|
|
||||||
{{ model.template.paymentTerms|trim|nl2br }}
|
|
||||||
</p>
|
</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-xs-6">
|
|
||||||
<p class="lead" contenteditable="true">{{ 'invoice.due_days'|trans }} {{ model.dueDate|date_short }}</p>
|
|
||||||
|
|
||||||
<div class="table-responsive">
|
|
||||||
<table class="table">
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<th style="width:50%">{{ 'invoice.subtotal'|trans }}</th>
|
|
||||||
<td>{{ model.calculator.subtotal|money(model.calculator.currency) }}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th>{{ 'invoice.tax'|trans }} ({{ model.calculator.vat }}%)</th>
|
|
||||||
<td>{{ model.calculator.tax|money(model.calculator.currency) }}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th>{{ 'invoice.total'|trans }}</th>
|
|
||||||
<td>{{ model.calculator.total|money(model.calculator.currency) }}</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<footer class="footer">
|
||||||
|
<p>
|
||||||
|
<strong>{{ 'label.address'|trans }}</strong>: {{ model.template.address|replace({"\n": ' – ', "\r\n": ' – ', "\r": ' – '})|raw }}
|
||||||
|
<br>
|
||||||
|
<strong>{{ 'label.invoice_bank_account'|trans }}</strong>: {{ model.template.paymentDetails|replace({"\n": ' – ', "\r\n": ' – ', "\r": ' – '})|raw }}
|
||||||
|
<br>
|
||||||
|
<strong>{{ 'label.contact'|trans }}</strong>: {{ model.template.contact|replace({"\n": ' – ', "\r\n": ' – ', "\r": ' – '})|raw }}
|
||||||
|
</p>
|
||||||
|
</footer>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@@ -4,30 +4,18 @@
|
|||||||
|
|
||||||
{% block invoice %}
|
{% block invoice %}
|
||||||
|
|
||||||
{# You can overwrite these settings in your config/packages/local.yaml under the key "twig.globals.company" #}
|
{% set signature = null %}
|
||||||
{% set company = company|default({
|
{# You can overwrite these settings in your config/packages/local.yaml under the key "twig.globals.company" - previously '/build/images/signature.png' #}
|
||||||
name: 'Kimai Inc.',
|
{% set company = company|default({signature: null}) %}
|
||||||
homepage: 'www.kimai.org',
|
{% if company.signature is defined and company.signature is not empty %}
|
||||||
email: 'kimai@example.com',
|
{% set signature = company.signature %}
|
||||||
phone: '0123-4567890',
|
{% endif %}
|
||||||
tax_number: 'YourTaxNumber',
|
|
||||||
signature: '/build/images/signature.png',
|
|
||||||
address: 'Kimai Inc.
|
|
||||||
Example road 42
|
|
||||||
D – 12345 City
|
|
||||||
',
|
|
||||||
bank_account: '
|
|
||||||
Kimai Inc.
|
|
||||||
IBAN: DE00 0000 0000 0000 0000 00
|
|
||||||
BIC: XXXXXXXX (a bank name)
|
|
||||||
'
|
|
||||||
}) %}
|
|
||||||
|
|
||||||
<div class="row" id="freelancer-invoice" style="page:invoiceFreelancer;">
|
<div class="row" id="freelancer-invoice">
|
||||||
<div class="col-xs-12">
|
<div class="col-xs-12">
|
||||||
<header>
|
<header>
|
||||||
<address>
|
<address>
|
||||||
<p contenteditable="true">{{ model.template.address }}</p>
|
<p contenteditable="true">{{ model.template.address|replace({"\n": ' – ', "\r\n": ' – ', "\r": ' – '})|raw }}</p>
|
||||||
</address>
|
</address>
|
||||||
</header>
|
</header>
|
||||||
<article class="address">
|
<article class="address">
|
||||||
@@ -36,6 +24,10 @@
|
|||||||
<p>
|
<p>
|
||||||
<strong>{{ model.customer.company|default(model.customer.name) }}</strong><br>
|
<strong>{{ model.customer.company|default(model.customer.name) }}</strong><br>
|
||||||
{{ model.customer.address|nl2br }}
|
{{ model.customer.address|nl2br }}
|
||||||
|
{% if model.customer.vatId is not empty %}
|
||||||
|
<br>
|
||||||
|
{{ 'label.vat_id'|trans }}: {{ model.customer.vatId }}
|
||||||
|
{% endif %}
|
||||||
</p>
|
</p>
|
||||||
</address>
|
</address>
|
||||||
<table class="meta">
|
<table class="meta">
|
||||||
@@ -59,6 +51,14 @@
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% if model.template.vatId is not empty %}
|
||||||
|
<tr>
|
||||||
|
<th>{{ 'label.vat_id'|trans }}:</th>
|
||||||
|
<td>
|
||||||
|
{{ model.template.vatId }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endif %}
|
||||||
</table>
|
</table>
|
||||||
</article>
|
</article>
|
||||||
<article>
|
<article>
|
||||||
@@ -118,44 +118,44 @@
|
|||||||
</table>
|
</table>
|
||||||
</article>
|
</article>
|
||||||
{% if model.template.paymentTerms is not empty %}
|
{% if model.template.paymentTerms is not empty %}
|
||||||
<article class="footer">
|
<article class="paymentTerms">
|
||||||
<p contenteditable="true">
|
<p contenteditable="true">
|
||||||
{{ model.template.paymentTerms|trim|nl2br }}
|
{{ model.template.paymentTerms|nl2br|md2html }}
|
||||||
</p>
|
</p>
|
||||||
{% if company.signature is defined %}
|
{% if signature is not empty %}
|
||||||
<img src="{{ company.signature }}">
|
<img src="{{ signature }}">
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</article>
|
</article>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<footer>
|
<footer class="footer">
|
||||||
<table>
|
<div class="row">
|
||||||
<thead>
|
<div class="col-sm-4" contenteditable="true">
|
||||||
<tr>
|
<p>
|
||||||
<th>{{ 'label.address'|trans }}</th>
|
<strong>{{ 'label.address'|trans }}</strong>
|
||||||
<th>{{ 'label.invoice_bank_account'|trans }}</th>
|
</p>
|
||||||
<th>{{ 'label.contact'|trans }}</th>
|
<p>
|
||||||
</tr>
|
{{ model.template.address|nl2br }}
|
||||||
</thead>
|
</p>
|
||||||
<tbody>
|
</div>
|
||||||
<tr>
|
<div class="col-sm-4 text-center" contenteditable="true">
|
||||||
<td contenteditable="true">
|
<p>
|
||||||
{{ company.address|nl2br }}
|
<strong>{{ 'label.invoice_bank_account'|trans }}</strong>
|
||||||
{{ 'label.invoice_tax_number'|trans }} {{ company.tax_number }}
|
</p>
|
||||||
</td>
|
<p>
|
||||||
<td contenteditable="true">
|
{{ model.template.paymentDetails|nl2br }}
|
||||||
{{ company.bank_account|nl2br }}
|
</p>
|
||||||
</td>
|
</div>
|
||||||
<td contenteditable="true">
|
<div class="col-sm-4 text-right" contenteditable="true">
|
||||||
{{ company.phone }}
|
<p>
|
||||||
<br/>
|
<strong>{{ 'label.contact'|trans }}</strong>
|
||||||
{{ company.email }}
|
</p>
|
||||||
<br/>
|
<p>
|
||||||
{{ company.homepage }}
|
{{ model.template.contact|nl2br }}
|
||||||
</td>
|
</p>
|
||||||
</tr>
|
</div>
|
||||||
</tbody>
|
</div>
|
||||||
</table>
|
|
||||||
</footer>
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@@ -52,7 +52,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row">
|
<div class="row invoice-items">
|
||||||
<div class="col-xs-12 table-responsive">
|
<div class="col-xs-12 table-responsive">
|
||||||
<table class="table table-striped">
|
<table class="table table-striped">
|
||||||
<thead>
|
<thead>
|
||||||
@@ -97,22 +97,21 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row" style="padding: 60px 0">
|
<div class="row">
|
||||||
<div class="col-xs-12">
|
<div class="col-xs-12">
|
||||||
{% if model.template.paymentTerms is not empty %}
|
{% if model.template.paymentTerms is not empty %}
|
||||||
<p class="lead">{{ 'label.payment_terms'|trans }}</p>
|
<p class="lead">{{ 'label.payment_terms'|trans }}</p>
|
||||||
|
|
||||||
<p class="text-muted well well-sm no-shadow" contenteditable="true">
|
<p class="text-muted well well-sm no-shadow" contenteditable="true" style="margin-bottom: 100px">
|
||||||
{{ model.template.paymentTerms|trim|nl2br }}
|
{{ model.template.paymentTerms|trim|nl2br }}
|
||||||
</p>
|
</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
|
||||||
<div class="table-responsive">
|
<div class="table-responsive">
|
||||||
<table class="table">
|
<table class="table">
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<th style="width:50%;padding-bottom: 60px">{{ 'invoice.signature_user'|trans }}</th>
|
<th style="padding-bottom: 60px">{{ 'invoice.signature_user'|trans }}</th>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>{{ 'invoice.signature_customer'|trans }}</th>
|
<th>{{ 'invoice.signature_customer'|trans }}</th>
|
||||||
|
|||||||
@@ -3,9 +3,54 @@
|
|||||||
{% block page_title %}{{ 'admin_invoice_template.title'|trans }}{% endblock %}
|
{% block page_title %}{{ 'admin_invoice_template.title'|trans }}{% endblock %}
|
||||||
|
|
||||||
{% block main %}
|
{% block main %}
|
||||||
{{ include(app.request.xmlHttpRequest ? 'default/_form_modal.html.twig' : 'default/_form.html.twig', {
|
{% set formEditTemplate = app.request.xmlHttpRequest ? 'default/_form_modal.html.twig' : 'default/_form.html.twig' %}
|
||||||
|
{% set formOptions = {
|
||||||
'title': template.name|default('create'|trans),
|
'title': template.name|default('create'|trans),
|
||||||
'form': form,
|
'form': form,
|
||||||
'back': path('admin_invoice_template')
|
'back': path('admin_invoice_template')
|
||||||
}) }}
|
} %}
|
||||||
|
|
||||||
|
{% embed formEditTemplate with formOptions %}
|
||||||
|
{% block form_body %}
|
||||||
|
{{ form_row(form.name) }}
|
||||||
|
{{ form_row(form.title) }}
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6">
|
||||||
|
{{ form_row(form.company) }}
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
{{ form_row(form.vatId) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6">
|
||||||
|
{{ form_row(form.address) }}
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
{{ form_row(form.contact) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6">
|
||||||
|
{{ form_row(form.paymentTerms) }}
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
{{ form_row(form.paymentDetails) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6">
|
||||||
|
{{ form_row(form.dueDays) }}
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
{{ form_row(form.vat) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{ form_row(form.renderer) }}
|
||||||
|
{{ form_row(form.calculator) }}
|
||||||
|
{{ form_row(form.numberGenerator) }}
|
||||||
|
{{ form_widget(form) }}
|
||||||
|
{% endblock %}
|
||||||
|
{% endembed %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
|
|
||||||
{% set tableName = 'invoice_template' %}
|
{% set tableName = 'invoice_template' %}
|
||||||
|
|
||||||
{{ tables.data_table_header(tableName, columns) }}
|
{{ tables.datatable_header(tableName, columns, null, {'reload': 'kimai.invoiceTemplateUpdate'}) }}
|
||||||
|
|
||||||
{% for entry in entries %}
|
{% for entry in entries %}
|
||||||
<tr class="modal-ajax-form open-edit" data-href="{{ path('admin_invoice_template_edit', {'id' : entry.id}) }}">
|
<tr class="modal-ajax-form open-edit" data-href="{{ path('admin_invoice_template_edit', {'id' : entry.id}) }}">
|
||||||
|
|||||||
@@ -23,85 +23,89 @@ class CustomerTest extends TestCase
|
|||||||
public function testDefaultValues()
|
public function testDefaultValues()
|
||||||
{
|
{
|
||||||
$sut = new Customer();
|
$sut = new Customer();
|
||||||
$this->assertNull($sut->getId());
|
self::assertNull($sut->getId());
|
||||||
$this->assertNull($sut->getName());
|
self::assertNull($sut->getName());
|
||||||
$this->assertNull($sut->getNumber());
|
self::assertNull($sut->getNumber());
|
||||||
$this->assertNull($sut->getComment());
|
self::assertNull($sut->getComment());
|
||||||
$this->assertTrue($sut->getVisible());
|
self::assertTrue($sut->getVisible());
|
||||||
|
|
||||||
$this->assertNull($sut->getCompany());
|
self::assertNull($sut->getCompany());
|
||||||
$this->assertNull($sut->getContact());
|
self::assertNull($sut->getVatId());
|
||||||
$this->assertNull($sut->getAddress());
|
self::assertNull($sut->getContact());
|
||||||
$this->assertNull($sut->getCountry());
|
self::assertNull($sut->getAddress());
|
||||||
$this->assertEquals('EUR', $sut->getCurrency());
|
self::assertNull($sut->getCountry());
|
||||||
$this->assertEquals('EUR', Customer::DEFAULT_CURRENCY);
|
self::assertEquals('EUR', $sut->getCurrency());
|
||||||
$this->assertNull($sut->getPhone());
|
self::assertEquals('EUR', Customer::DEFAULT_CURRENCY);
|
||||||
$this->assertNull($sut->getFax());
|
self::assertNull($sut->getPhone());
|
||||||
$this->assertNull($sut->getMobile());
|
self::assertNull($sut->getFax());
|
||||||
$this->assertNull($sut->getEmail());
|
self::assertNull($sut->getMobile());
|
||||||
$this->assertNull($sut->getHomepage());
|
self::assertNull($sut->getEmail());
|
||||||
$this->assertNull($sut->getTimezone());
|
self::assertNull($sut->getHomepage());
|
||||||
|
self::assertNull($sut->getTimezone());
|
||||||
|
|
||||||
$this->assertNull($sut->getFixedRate());
|
self::assertNull($sut->getFixedRate());
|
||||||
$this->assertNull($sut->getHourlyRate());
|
self::assertNull($sut->getHourlyRate());
|
||||||
$this->assertNull($sut->getColor());
|
self::assertNull($sut->getColor());
|
||||||
$this->assertEquals(0.0, $sut->getBudget());
|
self::assertEquals(0.0, $sut->getBudget());
|
||||||
$this->assertEquals(0, $sut->getTimeBudget());
|
self::assertEquals(0, $sut->getTimeBudget());
|
||||||
$this->assertInstanceOf(Collection::class, $sut->getMetaFields());
|
self::assertInstanceOf(Collection::class, $sut->getMetaFields());
|
||||||
$this->assertEquals(0, $sut->getMetaFields()->count());
|
self::assertEquals(0, $sut->getMetaFields()->count());
|
||||||
$this->assertNull($sut->getMetaField('foo'));
|
self::assertNull($sut->getMetaField('foo'));
|
||||||
$this->assertInstanceOf(Collection::class, $sut->getTeams());
|
self::assertInstanceOf(Collection::class, $sut->getTeams());
|
||||||
$this->assertEquals(0, $sut->getTeams()->count());
|
self::assertEquals(0, $sut->getTeams()->count());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testSetterAndGetter()
|
public function testSetterAndGetter()
|
||||||
{
|
{
|
||||||
$sut = new Customer();
|
$sut = new Customer();
|
||||||
$this->assertInstanceOf(Customer::class, $sut->setName('foo-bar'));
|
self::assertInstanceOf(Customer::class, $sut->setName('foo-bar'));
|
||||||
$this->assertEquals('foo-bar', $sut->getName());
|
self::assertEquals('foo-bar', $sut->getName());
|
||||||
$this->assertEquals('foo-bar', (string) $sut);
|
self::assertEquals('foo-bar', (string) $sut);
|
||||||
|
|
||||||
$this->assertInstanceOf(Customer::class, $sut->setVisible(false));
|
self::assertInstanceOf(Customer::class, $sut->setVisible(false));
|
||||||
$this->assertFalse($sut->getVisible());
|
self::assertFalse($sut->getVisible());
|
||||||
|
|
||||||
$this->assertInstanceOf(Customer::class, $sut->setComment('hello world'));
|
self::assertInstanceOf(Customer::class, $sut->setComment('hello world'));
|
||||||
$this->assertEquals('hello world', $sut->getComment());
|
self::assertEquals('hello world', $sut->getComment());
|
||||||
|
|
||||||
$this->assertInstanceOf(Customer::class, $sut->setColor('#fffccc'));
|
self::assertInstanceOf(Customer::class, $sut->setColor('#fffccc'));
|
||||||
$this->assertEquals('#fffccc', $sut->getColor());
|
self::assertEquals('#fffccc', $sut->getColor());
|
||||||
|
|
||||||
$this->assertInstanceOf(Customer::class, $sut->setCompany('test company'));
|
self::assertInstanceOf(Customer::class, $sut->setCompany('test company'));
|
||||||
$this->assertEquals('test company', $sut->getCompany());
|
self::assertEquals('test company', $sut->getCompany());
|
||||||
|
|
||||||
$this->assertInstanceOf(Customer::class, $sut->setContact('test contact'));
|
self::assertInstanceOf(Customer::class, $sut->setContact('test contact'));
|
||||||
$this->assertEquals('test contact', $sut->getContact());
|
self::assertEquals('test contact', $sut->getContact());
|
||||||
|
|
||||||
$this->assertInstanceOf(Customer::class, $sut->setPhone('0123456789'));
|
self::assertInstanceOf(Customer::class, $sut->setPhone('0123456789'));
|
||||||
$this->assertEquals('0123456789', $sut->getPhone());
|
self::assertEquals('0123456789', $sut->getPhone());
|
||||||
|
|
||||||
$this->assertInstanceOf(Customer::class, $sut->setFax('asdfghjkl'));
|
self::assertInstanceOf(Customer::class, $sut->setFax('asdfghjkl'));
|
||||||
$this->assertEquals('asdfghjkl', $sut->getFax());
|
self::assertEquals('asdfghjkl', $sut->getFax());
|
||||||
|
|
||||||
$this->assertInstanceOf(Customer::class, $sut->setMobile('76576534'));
|
self::assertInstanceOf(Customer::class, $sut->setMobile('76576534'));
|
||||||
$this->assertEquals('76576534', $sut->getMobile());
|
self::assertEquals('76576534', $sut->getMobile());
|
||||||
|
|
||||||
$this->assertInstanceOf(Customer::class, $sut->setEmail('test@example.com'));
|
self::assertInstanceOf(Customer::class, $sut->setEmail('test@example.com'));
|
||||||
$this->assertEquals('test@example.com', $sut->getEmail());
|
self::assertEquals('test@example.com', $sut->getEmail());
|
||||||
|
|
||||||
$this->assertInstanceOf(Customer::class, $sut->setHomepage('https://www.example.com'));
|
self::assertInstanceOf(Customer::class, $sut->setHomepage('https://www.example.com'));
|
||||||
$this->assertEquals('https://www.example.com', $sut->getHomepage());
|
self::assertEquals('https://www.example.com', $sut->getHomepage());
|
||||||
|
|
||||||
$this->assertInstanceOf(Customer::class, $sut->setFixedRate(13.47));
|
self::assertInstanceOf(Customer::class, $sut->setFixedRate(13.47));
|
||||||
$this->assertEquals(13.47, $sut->getFixedRate());
|
self::assertEquals(13.47, $sut->getFixedRate());
|
||||||
|
|
||||||
$this->assertInstanceOf(Customer::class, $sut->setHourlyRate(99));
|
self::assertInstanceOf(Customer::class, $sut->setHourlyRate(99));
|
||||||
$this->assertEquals(99, $sut->getHourlyRate());
|
self::assertEquals(99, $sut->getHourlyRate());
|
||||||
|
|
||||||
$this->assertInstanceOf(Customer::class, $sut->setBudget(12345.67));
|
self::assertInstanceOf(Customer::class, $sut->setBudget(12345.67));
|
||||||
$this->assertEquals(12345.67, $sut->getBudget());
|
self::assertEquals(12345.67, $sut->getBudget());
|
||||||
|
|
||||||
$this->assertInstanceOf(Customer::class, $sut->setTimeBudget(937321));
|
self::assertInstanceOf(Customer::class, $sut->setTimeBudget(937321));
|
||||||
$this->assertEquals(937321, $sut->getTimeBudget());
|
self::assertEquals(937321, $sut->getTimeBudget());
|
||||||
|
|
||||||
|
self::assertInstanceOf(Customer::class, $sut->setVatId('ID 1234567890'));
|
||||||
|
self::assertEquals('ID 1234567890', $sut->getVatId());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testMetaFields()
|
public function testMetaFields()
|
||||||
@@ -109,7 +113,7 @@ class CustomerTest extends TestCase
|
|||||||
$sut = new Customer();
|
$sut = new Customer();
|
||||||
$meta = new CustomerMeta();
|
$meta = new CustomerMeta();
|
||||||
$meta->setName('foo')->setValue('bar')->setType('test');
|
$meta->setName('foo')->setValue('bar')->setType('test');
|
||||||
$this->assertInstanceOf(Customer::class, $sut->setMetaField($meta));
|
self::assertInstanceOf(Customer::class, $sut->setMetaField($meta));
|
||||||
self::assertEquals(1, $sut->getMetaFields()->count());
|
self::assertEquals(1, $sut->getMetaFields()->count());
|
||||||
$result = $sut->getMetaField('foo');
|
$result = $sut->getMetaField('foo');
|
||||||
self::assertSame($result, $meta);
|
self::assertSame($result, $meta);
|
||||||
@@ -117,7 +121,7 @@ class CustomerTest extends TestCase
|
|||||||
|
|
||||||
$meta2 = new CustomerMeta();
|
$meta2 = new CustomerMeta();
|
||||||
$meta2->setName('foo')->setValue('bar')->setType('test2');
|
$meta2->setName('foo')->setValue('bar')->setType('test2');
|
||||||
$this->assertInstanceOf(Customer::class, $sut->setMetaField($meta2));
|
self::assertInstanceOf(Customer::class, $sut->setMetaField($meta2));
|
||||||
self::assertEquals(1, $sut->getMetaFields()->count());
|
self::assertEquals(1, $sut->getMetaFields()->count());
|
||||||
self::assertCount(0, $sut->getVisibleMetaFields());
|
self::assertCount(0, $sut->getVisibleMetaFields());
|
||||||
|
|
||||||
|
|||||||
@@ -17,36 +17,63 @@ use PHPUnit\Framework\TestCase;
|
|||||||
*/
|
*/
|
||||||
class InvoiceTemplateTest extends TestCase
|
class InvoiceTemplateTest extends TestCase
|
||||||
{
|
{
|
||||||
protected function assertIsFluent($actual)
|
public function testDefaultValues()
|
||||||
{
|
{
|
||||||
$this->assertInstanceOf(InvoiceTemplate::class, $actual);
|
$sut = new InvoiceTemplate();
|
||||||
|
|
||||||
|
self::assertNull($sut->getVatId());
|
||||||
|
self::assertNull($sut->getPaymentDetails());
|
||||||
|
self::assertNull($sut->getPaymentTerms());
|
||||||
|
self::assertNull($sut->getContact());
|
||||||
|
self::assertEquals(0.00, $sut->getVat());
|
||||||
|
self::assertNull($sut->getId());
|
||||||
|
self::assertNull($sut->getName());
|
||||||
|
self::assertNull($sut->getAddress());
|
||||||
|
self::assertNull($sut->getTitle());
|
||||||
|
self::assertNull($sut->getCompany());
|
||||||
|
self::assertEquals('default', $sut->getCalculator());
|
||||||
|
self::assertEquals('default', $sut->getNumberGenerator());
|
||||||
|
self::assertEquals('default', $sut->getRenderer());
|
||||||
|
self::assertEquals(30, $sut->getDueDays());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function getEntity()
|
public function testSetNullForOptionalValues()
|
||||||
{
|
{
|
||||||
$entity = new InvoiceTemplate();
|
$sut = new InvoiceTemplate();
|
||||||
|
|
||||||
return $entity;
|
self::assertInstanceOf(InvoiceTemplate::class, $sut->setPaymentDetails(null));
|
||||||
|
self::assertInstanceOf(InvoiceTemplate::class, $sut->setVatId(null));
|
||||||
|
self::assertInstanceOf(InvoiceTemplate::class, $sut->setContact(null));
|
||||||
|
self::assertInstanceOf(InvoiceTemplate::class, $sut->setAddress(null));
|
||||||
|
self::assertInstanceOf(InvoiceTemplate::class, $sut->setPaymentTerms(null));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testSetGetPaymentTerms()
|
public function testSetterAndGetter()
|
||||||
{
|
{
|
||||||
$sut = $this->getEntity();
|
$sut = new InvoiceTemplate();
|
||||||
|
|
||||||
$this->assertNull($sut->getPaymentTerms());
|
self::assertInstanceOf(InvoiceTemplate::class, $sut->setPaymentTerms('foo bar'));
|
||||||
$this->assertIsFluent($sut->setPaymentTerms(null));
|
self::assertEquals('foo bar', $sut->getPaymentTerms());
|
||||||
$this->assertIsFluent($sut->setPaymentTerms(''));
|
|
||||||
$this->assertIsFluent($sut->setPaymentTerms('foo bar'));
|
self::assertInstanceOf(InvoiceTemplate::class, $sut->setPaymentDetails('iuasdzgf isdfhvlksdjfbnvl ksdfbglisbdf'));
|
||||||
$this->assertEquals('foo bar', $sut->getPaymentTerms());
|
self::assertEquals('iuasdzgf isdfhvlksdjfbnvl ksdfbglisbdf', $sut->getPaymentDetails());
|
||||||
|
|
||||||
|
self::assertInstanceOf(InvoiceTemplate::class, $sut->setContact('hello world'));
|
||||||
|
self::assertEquals('hello world', $sut->getContact());
|
||||||
|
|
||||||
|
self::assertInstanceOf(InvoiceTemplate::class, $sut->setVat(7.31));
|
||||||
|
self::assertEquals(7.31, $sut->getVat());
|
||||||
|
|
||||||
|
self::assertInstanceOf(InvoiceTemplate::class, $sut->setVatId('1234567890'));
|
||||||
|
self::assertEquals('1234567890', $sut->getVatId());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testToString()
|
public function testToString()
|
||||||
{
|
{
|
||||||
$sut = $this->getEntity();
|
$sut = new InvoiceTemplate();
|
||||||
|
|
||||||
$this->assertNull($sut->__toString());
|
self::assertInstanceOf(InvoiceTemplate::class, $sut->setName('a template name'));
|
||||||
$this->assertIsFluent($sut->setName('a template name'));
|
self::assertEquals('a template name', $sut->__toString());
|
||||||
$this->assertEquals('a template name', $sut->__toString());
|
self::assertEquals('a template name', (string) $sut);
|
||||||
$this->assertEquals('a template name', (string) $sut);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ class CsvRendererTest extends TestCase
|
|||||||
$this->assertFalse($sut->supports($this->getInvoiceDocument('timesheet.html.twig')));
|
$this->assertFalse($sut->supports($this->getInvoiceDocument('timesheet.html.twig')));
|
||||||
$this->assertFalse($sut->supports($this->getInvoiceDocument('foo.html.twig')));
|
$this->assertFalse($sut->supports($this->getInvoiceDocument('foo.html.twig')));
|
||||||
$this->assertFalse($sut->supports($this->getInvoiceDocument('company.docx')));
|
$this->assertFalse($sut->supports($this->getInvoiceDocument('company.docx')));
|
||||||
$this->assertTrue($sut->supports($this->getInvoiceDocument('export.csv')));
|
$this->assertTrue($sut->supports($this->getInvoiceDocument('export.csv', true)));
|
||||||
$this->assertFalse($sut->supports($this->getInvoiceDocument('spreadsheet.xlsx')));
|
$this->assertFalse($sut->supports($this->getInvoiceDocument('spreadsheet.xlsx')));
|
||||||
$this->assertFalse($sut->supports($this->getInvoiceDocument('open-spreadsheet.ods')));
|
$this->assertFalse($sut->supports($this->getInvoiceDocument('open-spreadsheet.ods')));
|
||||||
}
|
}
|
||||||
@@ -51,7 +51,7 @@ class CsvRendererTest extends TestCase
|
|||||||
{
|
{
|
||||||
/** @var CsvRenderer $sut */
|
/** @var CsvRenderer $sut */
|
||||||
$sut = $this->getAbstractRenderer(CsvRenderer::class);
|
$sut = $this->getAbstractRenderer(CsvRenderer::class);
|
||||||
$document = $this->getInvoiceDocument('export.csv');
|
$document = $this->getInvoiceDocument('export.csv', true);
|
||||||
/** @var BinaryFileResponse $response */
|
/** @var BinaryFileResponse $response */
|
||||||
$response = $sut->render($document, $model);
|
$response = $sut->render($document, $model);
|
||||||
|
|
||||||
|
|||||||
@@ -73,6 +73,9 @@ class DebugRendererTest extends TestCase
|
|||||||
'template.title',
|
'template.title',
|
||||||
'template.payment_terms',
|
'template.payment_terms',
|
||||||
'template.due_days',
|
'template.due_days',
|
||||||
|
'template.vat_id',
|
||||||
|
'template.contact',
|
||||||
|
'template.payment_details',
|
||||||
'query.begin',
|
'query.begin',
|
||||||
'query.day',
|
'query.day',
|
||||||
'query.end',
|
'query.end',
|
||||||
@@ -84,6 +87,7 @@ class DebugRendererTest extends TestCase
|
|||||||
'customer.name',
|
'customer.name',
|
||||||
'customer.contact',
|
'customer.contact',
|
||||||
'customer.company',
|
'customer.company',
|
||||||
|
'customer.vat',
|
||||||
'customer.country',
|
'customer.country',
|
||||||
'customer.number',
|
'customer.number',
|
||||||
'customer.homepage',
|
'customer.homepage',
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ class OdsRendererTest extends TestCase
|
|||||||
$this->assertFalse($sut->supports($this->getInvoiceDocument('company.docx')));
|
$this->assertFalse($sut->supports($this->getInvoiceDocument('company.docx')));
|
||||||
$this->assertFalse($sut->supports($this->getInvoiceDocument('export.csv')));
|
$this->assertFalse($sut->supports($this->getInvoiceDocument('export.csv')));
|
||||||
$this->assertFalse($sut->supports($this->getInvoiceDocument('spreadsheet.xlsx')));
|
$this->assertFalse($sut->supports($this->getInvoiceDocument('spreadsheet.xlsx')));
|
||||||
$this->assertTrue($sut->supports($this->getInvoiceDocument('open-spreadsheet.ods')));
|
$this->assertTrue($sut->supports($this->getInvoiceDocument('open-spreadsheet.ods', true)));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getTestModel()
|
public function getTestModel()
|
||||||
@@ -52,7 +52,7 @@ class OdsRendererTest extends TestCase
|
|||||||
/** @var OdsRenderer $sut */
|
/** @var OdsRenderer $sut */
|
||||||
$sut = $this->getAbstractRenderer(OdsRenderer::class);
|
$sut = $this->getAbstractRenderer(OdsRenderer::class);
|
||||||
$model = $this->getInvoiceModel();
|
$model = $this->getInvoiceModel();
|
||||||
$document = $this->getInvoiceDocument('open-spreadsheet.ods');
|
$document = $this->getInvoiceDocument('open-spreadsheet.ods', true);
|
||||||
/** @var BinaryFileResponse $response */
|
/** @var BinaryFileResponse $response */
|
||||||
$response = $sut->render($document, $model);
|
$response = $sut->render($document, $model);
|
||||||
|
|
||||||
|
|||||||
@@ -47,10 +47,16 @@ trait RendererTestTrait
|
|||||||
* @param string $filename
|
* @param string $filename
|
||||||
* @return InvoiceDocument
|
* @return InvoiceDocument
|
||||||
*/
|
*/
|
||||||
protected function getInvoiceDocument(string $filename)
|
protected function getInvoiceDocument(string $filename, bool $testOnly = false)
|
||||||
{
|
{
|
||||||
|
if (!$testOnly) {
|
||||||
|
return new InvoiceDocument(
|
||||||
|
new \SplFileInfo($this->getInvoiceTemplatePath() . $filename)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return new InvoiceDocument(
|
return new InvoiceDocument(
|
||||||
new \SplFileInfo($this->getInvoiceTemplatePath() . $filename)
|
new \SplFileInfo(__DIR__ . '/../templates/' . $filename)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ class XlsxRendererTest extends TestCase
|
|||||||
$this->assertFalse($sut->supports($this->getInvoiceDocument('foo.html.twig')));
|
$this->assertFalse($sut->supports($this->getInvoiceDocument('foo.html.twig')));
|
||||||
$this->assertFalse($sut->supports($this->getInvoiceDocument('company.docx')));
|
$this->assertFalse($sut->supports($this->getInvoiceDocument('company.docx')));
|
||||||
$this->assertFalse($sut->supports($this->getInvoiceDocument('export.csv')));
|
$this->assertFalse($sut->supports($this->getInvoiceDocument('export.csv')));
|
||||||
$this->assertTrue($sut->supports($this->getInvoiceDocument('spreadsheet.xlsx')));
|
$this->assertTrue($sut->supports($this->getInvoiceDocument('spreadsheet.xlsx', true)));
|
||||||
$this->assertFalse($sut->supports($this->getInvoiceDocument('open-spreadsheet.ods')));
|
$this->assertFalse($sut->supports($this->getInvoiceDocument('open-spreadsheet.ods')));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,7 +52,7 @@ class XlsxRendererTest extends TestCase
|
|||||||
/** @var XlsxRenderer $sut */
|
/** @var XlsxRenderer $sut */
|
||||||
$sut = $this->getAbstractRenderer(XlsxRenderer::class);
|
$sut = $this->getAbstractRenderer(XlsxRenderer::class);
|
||||||
$model = $this->getInvoiceModel();
|
$model = $this->getInvoiceModel();
|
||||||
$document = $this->getInvoiceDocument('spreadsheet.xlsx');
|
$document = $this->getInvoiceDocument('spreadsheet.xlsx', true);
|
||||||
/** @var BinaryFileResponse $response */
|
/** @var BinaryFileResponse $response */
|
||||||
$response = $sut->render($document, $model);
|
$response = $sut->render($document, $model);
|
||||||
|
|
||||||
|
|||||||
@@ -25,10 +25,7 @@ class InvoiceDocumentRepositoryTest extends TestCase
|
|||||||
protected static $defaultDocuments = [
|
protected static $defaultDocuments = [
|
||||||
'company.docx',
|
'company.docx',
|
||||||
'default.html.twig',
|
'default.html.twig',
|
||||||
'export.csv',
|
|
||||||
'freelancer.html.twig',
|
'freelancer.html.twig',
|
||||||
'open-spreadsheet.ods',
|
|
||||||
'spreadsheet.xlsx',
|
|
||||||
'timesheet.html.twig',
|
'timesheet.html.twig',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -551,6 +551,10 @@
|
|||||||
<source>label.vat</source>
|
<source>label.vat</source>
|
||||||
<target>Umsatzsteuer</target>
|
<target>Umsatzsteuer</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
|
<trans-unit id="label.vat_id">
|
||||||
|
<source>label.vat_id</source>
|
||||||
|
<target>Umsatzsteuer-ID</target>
|
||||||
|
</trans-unit>
|
||||||
<trans-unit id="label.contact">
|
<trans-unit id="label.contact">
|
||||||
<source>label.contact</source>
|
<source>label.contact</source>
|
||||||
<target>Kontakt</target>
|
<target>Kontakt</target>
|
||||||
|
|||||||
@@ -551,6 +551,10 @@
|
|||||||
<source>label.vat</source>
|
<source>label.vat</source>
|
||||||
<target>Tax</target>
|
<target>Tax</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
|
<trans-unit id="label.vat_id">
|
||||||
|
<source>label.vat_id</source>
|
||||||
|
<target>VAT-ID</target>
|
||||||
|
</trans-unit>
|
||||||
<trans-unit id="label.contact">
|
<trans-unit id="label.contact">
|
||||||
<source>label.contact</source>
|
<source>label.contact</source>
|
||||||
<target>Contact</target>
|
<target>Contact</target>
|
||||||
|
|||||||
Binary file not shown.
1
var/packages/.gitkeep
Normal file
1
var/packages/.gitkeep
Normal file
@@ -0,0 +1 @@
|
|||||||
|
This directory can store as Kimai plugin/bundle folder.
|
||||||
Reference in New Issue
Block a user