diff --git a/src/.env.example b/src/.env.example
--- a/src/.env.example
+++ b/src/.env.example
@@ -105,5 +105,8 @@
COMPANY_EMAIL=
COMPANY_LOGO=
+VAT_COUNTRIES=CH,LI
+VAT_RATE=7.7
+
KB_ACCOUNT_DELETE=
KB_ACCOUNT_SUSPENDED=
diff --git a/src/app/Documents/Receipt.php b/src/app/Documents/Receipt.php
--- a/src/app/Documents/Receipt.php
+++ b/src/app/Documents/Receipt.php
@@ -127,6 +127,7 @@
$company = $this->companyData();
if (self::$fakeMode) {
+ $country = 'CH';
$customer = [
'id' => $this->wallet->owner->id,
'wallet_id' => $this->wallet->id,
@@ -156,6 +157,7 @@
]);
} else {
$customer = $this->customerData();
+ $country = $this->wallet->owner->getSetting('country');
$items = $this->wallet->payments()
->where('status', PaymentProvider::STATUS_PAID)
@@ -166,18 +168,33 @@
->get();
}
+ $vatRate = \config('app.vat.rate');
+ $vatCountries = explode(',', \config('app.vat.countries'));
+ $vatCountries = array_map('strtoupper', array_map('trim', $vatCountries));
+
+ if (!$country || !in_array(strtoupper($country), $vatCountries)) {
+ $vatRate = 0;
+ }
+
+ $totalVat = 0;
$total = 0;
- $items = $items->map(function ($item) use (&$total, $appName) {
- $total += $item->amount;
+ $items = $items->map(function ($item) use (&$total, &$totalVat, $appName, $vatRate) {
+ $amount = $item->amount;
+
+ if ($vatRate > 0) {
+ $amount = round($amount * ((100 - $vatRate) / 100));
+ $totalVat += $item->amount - $amount;
+ }
+
+ $total += $amount;
+
return [
- 'amount' => sprintf('%.2f %s', $item->amount / 100, $this->wallet->currency),
+ 'amount' => $this->wallet->money($amount),
'description' => \trans('documents.receipt-item-desc', ['site' => $appName]),
'date' => $item->updated_at->toDateString(),
];
});
- $total = sprintf('%.2f %s', $total / 100, $this->wallet->currency);
-
// Load the template
$view = view('documents.receipt')
->with([
@@ -186,7 +203,11 @@
'company' => $company,
'customer' => $customer,
'items' => $items,
- 'total' => $total,
+ 'subTotal' => $this->wallet->money($total),
+ 'total' => $this->wallet->money($total + $totalVat),
+ 'totalVat' => $this->wallet->money($totalVat),
+ 'vatRate' => preg_replace('/([.,]00|0|[.,])$/', '', sprintf('%.2f', $vatRate)),
+ 'vat' => $vatRate > 0,
]);
return $view;
diff --git a/src/config/app.php b/src/config/app.php
--- a/src/config/app.php
+++ b/src/config/app.php
@@ -253,4 +253,9 @@
'email' => env('COMPANY_EMAIL'),
'logo' => env('COMPANY_LOGO'),
],
+
+ 'vat' => [
+ 'countries' => env('VAT_COUNTRIES'),
+ 'rate' => (float) env('VAT_RATE'),
+ ],
];
diff --git a/src/resources/lang/en/documents.php b/src/resources/lang/en/documents.php
--- a/src/resources/lang/en/documents.php
+++ b/src/resources/lang/en/documents.php
@@ -33,4 +33,6 @@
'receipt-title' => "Receipt for :month :year",
'receipt-item-desc' => ":site Services",
+ 'subtotal' => "Subtotal",
+ 'vat' => "VAT (:rate%)",
];
diff --git a/src/resources/sass/document.scss b/src/resources/sass/document.scss
--- a/src/resources/sass/document.scss
+++ b/src/resources/sass/document.scss
@@ -62,6 +62,12 @@
font-size: 10pt;
}
+ .price {
+ width: 150px;
+ text-align: right;
+ white-space: nowrap;
+ }
+
td.logo {
width: 1%;
}
@@ -73,6 +79,11 @@
tr.total {
background-color: #f4f4f4;
}
+
+ tr.vat td,
+ tr.subtotal td {
+ border-bottom: 0;
+ }
}
#footer {
@@ -106,7 +117,3 @@
.align-left {
text-align: left;
}
-
-.amount {
- white-space: nowrap;
-}
diff --git a/src/resources/views/documents/receipt.blade.php b/src/resources/views/documents/receipt.blade.php
--- a/src/resources/views/documents/receipt.blade.php
+++ b/src/resources/views/documents/receipt.blade.php
@@ -36,18 +36,28 @@
{{ __('documents.date') }} |
{{ __('documents.description') }} |
- {{ __('documents.amount') }} |
+ {{ __('documents.amount') }} |
@foreach ($items as $item)
{{ $item['date'] }} |
{{ $item['description'] }} |
- {{ $item['amount'] }} |
+ {{ $item['amount'] }} |
@endforeach
+@if ($vat)
+
+ {{ __('documents.subtotal') }} |
+ {{ $subTotal }} |
+
+
+ {{ __('documents.vat', ['rate' => $vatRate]) }} |
+ {{ $totalVat }} |
+
+@endif
- {{ __('documents.total') }} |
- {{ $total }} |
+ {{ __('documents.total') }} |
+ {{ $total }} |
diff --git a/src/tests/Feature/Documents/ReceiptTest.php b/src/tests/Feature/Documents/ReceiptTest.php
--- a/src/tests/Feature/Documents/ReceiptTest.php
+++ b/src/tests/Feature/Documents/ReceiptTest.php
@@ -24,11 +24,9 @@
}
/**
- * Test receipt HTML output
- *
- * @return void
+ * Test receipt HTML output (without VAT)
*/
- public function testHtmlOutput()
+ public function testHtmlOutput(): void
{
$appName = \config('app.name');
$wallet = $this->getTestData();
@@ -64,21 +62,21 @@
$this->assertCount(3, $cells);
$this->assertSame('2020-05-01', $this->getNodeContent($cells[0]));
$this->assertSame("$appName Services", $this->getNodeContent($cells[1]));
- $this->assertSame('12.34 CHF', $this->getNodeContent($cells[2]));
+ $this->assertSame('12,34 CHF', $this->getNodeContent($cells[2]));
$cells = $records[2]->getElementsByTagName('td');
$this->assertCount(3, $cells);
$this->assertSame('2020-05-10', $this->getNodeContent($cells[0]));
$this->assertSame("$appName Services", $this->getNodeContent($cells[1]));
- $this->assertSame('0.01 CHF', $this->getNodeContent($cells[2]));
+ $this->assertSame('0,01 CHF', $this->getNodeContent($cells[2]));
$cells = $records[3]->getElementsByTagName('td');
$this->assertCount(3, $cells);
$this->assertSame('2020-05-31', $this->getNodeContent($cells[0]));
$this->assertSame("$appName Services", $this->getNodeContent($cells[1]));
- $this->assertSame('1.00 CHF', $this->getNodeContent($cells[2]));
+ $this->assertSame('1,00 CHF', $this->getNodeContent($cells[2]));
$summaryCells = $records[4]->getElementsByTagName('td');
$this->assertCount(2, $summaryCells);
$this->assertSame('Total', $this->getNodeContent($summaryCells[0]));
- $this->assertSame('13.35 CHF', $this->getNodeContent($summaryCells[1]));
+ $this->assertSame('13,35 CHF', $this->getNodeContent($summaryCells[1]));
// Customer data
$customer = $dom->getElementById('customer');
@@ -90,7 +88,7 @@
$this->assertTrue(strpos($customerIdents, "Account ID {$wallet->id}") !== false);
$this->assertTrue(strpos($customerIdents, "Customer No. {$wallet->owner->id}") !== false);
- // TODO: Company details in the footer
+ // Company details in the footer
$footer = $dom->getElementById('footer');
$footerOutput = $footer->textContent;
$this->assertStringStartsWith(\config('app.company.details'), $footerOutput);
@@ -98,11 +96,61 @@
}
/**
+ * Test receipt HTML output (with VAT)
+ */
+ public function testHtmlOutputVat(): void
+ {
+ \config(['app.vat.rate' => 7.7]);
+ \config(['app.vat.countries' => 'ch']);
+
+ $appName = \config('app.name');
+ $wallet = $this->getTestData('CH');
+ $receipt = new Receipt($wallet, 2020, 5);
+ $html = $receipt->htmlOutput();
+
+ $this->assertStringStartsWith('', $html);
+
+ $dom = new \DOMDocument('1.0', 'UTF-8');
+ $dom->loadHTML($html);
+
+ // The main table content
+ $content = $dom->getElementById('content');
+ $records = $content->getElementsByTagName('tr');
+ $this->assertCount(7, $records);
+
+ $cells = $records[1]->getElementsByTagName('td');
+ $this->assertCount(3, $cells);
+ $this->assertSame('2020-05-01', $this->getNodeContent($cells[0]));
+ $this->assertSame("$appName Services", $this->getNodeContent($cells[1]));
+ $this->assertSame('11,39 CHF', $this->getNodeContent($cells[2]));
+ $cells = $records[2]->getElementsByTagName('td');
+ $this->assertCount(3, $cells);
+ $this->assertSame('2020-05-10', $this->getNodeContent($cells[0]));
+ $this->assertSame("$appName Services", $this->getNodeContent($cells[1]));
+ $this->assertSame('0,01 CHF', $this->getNodeContent($cells[2]));
+ $cells = $records[3]->getElementsByTagName('td');
+ $this->assertCount(3, $cells);
+ $this->assertSame('2020-05-31', $this->getNodeContent($cells[0]));
+ $this->assertSame("$appName Services", $this->getNodeContent($cells[1]));
+ $this->assertSame('0,92 CHF', $this->getNodeContent($cells[2]));
+ $subtotalCells = $records[4]->getElementsByTagName('td');
+ $this->assertCount(2, $subtotalCells);
+ $this->assertSame('Subtotal', $this->getNodeContent($subtotalCells[0]));
+ $this->assertSame('12,32 CHF', $this->getNodeContent($subtotalCells[1]));
+ $vatCells = $records[5]->getElementsByTagName('td');
+ $this->assertCount(2, $vatCells);
+ $this->assertSame('VAT (7.7%)', $this->getNodeContent($vatCells[0]));
+ $this->assertSame('1,03 CHF', $this->getNodeContent($vatCells[1]));
+ $totalCells = $records[6]->getElementsByTagName('td');
+ $this->assertCount(2, $totalCells);
+ $this->assertSame('Total', $this->getNodeContent($totalCells[0]));
+ $this->assertSame('13,35 CHF', $this->getNodeContent($totalCells[1]));
+ }
+
+ /**
* Test receipt PDF output
- *
- * @return void
*/
- public function testPdfOutput()
+ public function testPdfOutput(): void
{
$wallet = $this->getTestData();
$receipt = new Receipt($wallet, 2020, 5);
@@ -117,9 +165,11 @@
/**
* Prepare data for a test
*
+ * @param string $country User country code
+ *
* @return \App\Wallet
*/
- protected function getTestData()
+ protected function getTestData(string $country = null): Wallet
{
Bus::fake();
@@ -128,6 +178,7 @@
'first_name' => 'Firstname',
'last_name' => 'Lastname',
'billing_address' => "Test Unicode Straße 150\n10115 Berlin",
+ 'country' => $country
]);
$wallet = $user->wallets()->first();