Generate Excel reports for outgoing invoices and incoming payments.

This commit is contained in:
2025-01-18 08:53:48 +01:00
parent 5d4766a103
commit f246f80b6b
3 changed files with 224 additions and 5 deletions

View File

@@ -3,14 +3,207 @@
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Models\Invoice;
use App\Models\Payment;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use PhpOffice\PhpSpreadsheet\Shared\Date;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Style\Alignment;
use PhpOffice\PhpSpreadsheet\Style\Border;
use PhpOffice\PhpSpreadsheet\Style\NumberFormat\Wizard\Currency;
use PhpOffice\PhpSpreadsheet\Style\NumberFormat\Wizard\CurrencyBase;
use PhpOffice\PhpSpreadsheet\Style\NumberFormat\Wizard\Number;
use PhpOffice\PhpSpreadsheet\Worksheet\PageSetup;
use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
class ExcelController extends Controller
{
protected string $from;
protected string $end;
protected Xlsx $writer;
protected CurrencyBase $currencyMask;
public function export(Request $request): JsonResponse
{
return response()->json($request->all());
$requestData = $request->validate([
'from' => 'required|date',
'end' => 'required|date',
'report' => 'required|in:invoice,payment',
]);
$this->from = $requestData['from'];
$this->end = $requestData['end'];
$this->currencyMask = new Currency(
'€',
2,
Number::WITH_THOUSANDS_SEPARATOR,
Currency::TRAILING_SYMBOL,
Currency::SYMBOL_WITH_SPACING
);
switch ($requestData['report']) {
case 'invoice':
$this->invoice();
break;
case 'payment':
$this->payment();
break;
}
return response()->json($this->writer->save('php://output'));
}
protected function invoice(): void
{
$from = $this->from . ' 00:00:00';
$end = $this->end . ' 23:59:59';
$net = [];
$tax = [];
$gross = [];
$invoices = Invoice::whereBetween('created_at', [$from, $end])->where('status', '!=', 'created')->with(['address', 'customer'])->orderBy('created_at', 'desc')->get();
$spreadsheet = new Spreadsheet();
$worksheet = $spreadsheet->getActiveSheet();
$worksheet->setTitle(__('invoice.Invoices'));
$worksheet->getPageSetup()->setOrientation(PageSetup::ORIENTATION_LANDSCAPE);
$worksheet->getHeaderFooter()->setEvenHeader(__('invoice.Invoices from to', ['from' => $this->from, 'end' => $this->end]));
$worksheet->getHeaderFooter()->setOddHeader(__('invoice.Invoices from to', ['from' => $this->from, 'end' => $this->end]));
$worksheet->setCellValue('A1', __('invoice.Invoice Number'));
$worksheet->setCellValue('B1', __('customer.Customer'));
$worksheet->setCellValue('C1', __('common.Name'));
$worksheet->setCellValue('D1', __('invoice.Net'));
$worksheet->setCellValue('E1', __('invoice.Tax'));
$worksheet->setCellValue('F1', __('invoice.Gross'));
$worksheet->setCellValue('G1', __('common.Created at'));
$worksheet->getStyle('A1:G1')->getBorders()->getBottom()->applyFromArray(['borderStyle' => Border::BORDER_DOUBLE]);
$worksheet->getStyle('A1:G1')->getFont()->setBold(true);
$worksheet->getStyle('D1:G1')->getAlignment()->applyFromArray(['horizontal' => Alignment::HORIZONTAL_RIGHT]);
$row = 2;
foreach ($invoices as $invoice) {
foreach ($invoice->items as $item) {
if (!isset($net[$item->tax])) {
$net[$item->tax] = 0;
$tax[$item->tax] = 0;
$gross[$item->tax] = 0;
}
$net[$item->tax] += $item->amount * $item->price;
$tax[$item->tax] += $item->amount * $item->price * $item->tax / 100;
$gross[$item->tax] += $item->total;
}
$worksheet->setCellValue('A' . $row, $invoice->number);
$worksheet->setCellValue('B' . $row, $invoice->customer->name);
$worksheet->setCellValue('C' . $row, $invoice->address->name);
$worksheet->getCell('D' . $row)->getStyle()->getNumberFormat()->setFormatCode($this->currencyMask);
$worksheet->setCellValue('D' . $row, $invoice->sum - $invoice->tax);
$worksheet->getCell('E' . $row)->getStyle()->getNumberFormat()->setFormatCode($this->currencyMask);
$worksheet->setCellValue('E' . $row, $invoice->tax);
$worksheet->getCell('F' . $row)->getStyle()->getNumberFormat()->setFormatCode($this->currencyMask);
$worksheet->setCellValue('F' . $row, $invoice->sum);
$worksheet->setCellValue('G' . $row, Date::PHPToExcel(\DateTime::createFromFormat('!d.m.Y', $invoice->created)));
$worksheet->getCell('G' . $row)->getStyle()->getNumberFormat()->setFormatCode('dd.mm.yyyy');
$row++;
}
$worksheet->getStyle('A' . $row - 1 . ':G' . $row - 1)->getBorders()->getBottom()->applyFromArray(['borderStyle' => Border::BORDER_DOUBLE]);
foreach ($net as $tax_value => $amount) {
$worksheet->getStyle('A' . $row . ':G' . $row)->getFont()->setBold(true);
$worksheet->getStyle('D' . $row . ':G' . $row)->getAlignment()->applyFromArray(['horizontal' => Alignment::HORIZONTAL_RIGHT]);
$worksheet->setCellValue('B' . $row, __('invoice.Sails from vat', ['tax' => \Illuminate\Support\Number::percentage($tax_value)]));
$worksheet->getCell('D' . $row)->getStyle()->getNumberFormat()->setFormatCode($this->currencyMask);
$worksheet->setCellValue('D' . $row, $amount);
$worksheet->getCell('E' . $row)->getStyle()->getNumberFormat()->setFormatCode($this->currencyMask);
$worksheet->setCellValue('E' . $row, $tax[$tax_value]);
$worksheet->getCell('F' . $row)->getStyle()->getNumberFormat()->setFormatCode($this->currencyMask);
$worksheet->setCellValue('F' . $row, $gross[$tax_value]);
$row++;
}
foreach ($worksheet->getColumnIterator() as $column) {
$worksheet->getColumnDimension($column->getColumnIndex())->setAutoSize(true);
}
$this->writer = new Xlsx($spreadsheet);
}
protected function payment(): void
{
$net = [];
$tax = [];
$gross = [];
$payments = Payment::whereBetween('payment_date', [$this->from, $this->end])->with(['invoice'])->orderBy('payment_date', 'desc')->get();
$spreadsheet = new Spreadsheet();
$worksheet = $spreadsheet->getActiveSheet();
$worksheet->setTitle(__('invoice.Payments'));
$worksheet->getPageSetup()->setOrientation(PageSetup::ORIENTATION_LANDSCAPE);
$worksheet->getHeaderFooter()->setEvenHeader(__('invoice.Payments from to', ['from' => $this->from, 'end' => $this->end]));
$worksheet->getHeaderFooter()->setOddHeader(__('invoice.Payments from to', ['from' => $this->from, 'end' => $this->end]));
$worksheet->setCellValue('A1', __('invoice.Invoice Number'));
$worksheet->setCellValue('B1', __('customer.Customer'));
$worksheet->setCellValue('C1', __('common.Name'));
$worksheet->setCellValue('D1', __('invoice.Net'));
$worksheet->setCellValue('E1', __('invoice.Tax'));
$worksheet->setCellValue('F1', __('invoice.Gross'));
$worksheet->setCellValue('G1', __('invoice.Paid at'));
$worksheet->getStyle('A1:G1')->getBorders()->getBottom()->applyFromArray(['borderStyle' => Border::BORDER_DOUBLE]);
$worksheet->getStyle('A1:G1')->getFont()->setBold(true);
$worksheet->getStyle('D1:G1')->getAlignment()->applyFromArray(['horizontal' => Alignment::HORIZONTAL_RIGHT]);
$row = 2;
foreach ($payments as $payment) {
$invoice = $payment->invoice;
foreach ($invoice->items as $item) {
if (!isset($net[$item->tax])) {
$net[$item->tax] = 0;
$tax[$item->tax] = 0;
$gross[$item->tax] = 0;
}
$net[$item->tax] += $item->amount * $item->price * $payment->paid_amount / $invoice->sum;
$tax[$item->tax] += $item->amount * $item->price * $item->tax * $payment->paid_amount / ($invoice->sum * 100);
$gross[$item->tax] += $item->total * $payment->paid_amount / ($invoice->sum);
}
$worksheet->setCellValue('A' . $row, $invoice->number);
$worksheet->setCellValue('B' . $row, $invoice->customer->name);
$worksheet->setCellValue('C' . $row, $invoice->address->name);
$worksheet->getCell('D' . $row)->getStyle()->getNumberFormat()->setFormatCode($this->currencyMask);
$worksheet->setCellValue('D' . $row, ($invoice->sum - $invoice->tax) / ($invoice->sum / $payment->paid_amount));
$worksheet->getCell('E' . $row)->getStyle()->getNumberFormat()->setFormatCode($this->currencyMask);
$worksheet->setCellValue('E' . $row, $invoice->tax / ($invoice->sum / $payment->paid_amount));
$worksheet->getCell('F' . $row)->getStyle()->getNumberFormat()->setFormatCode($this->currencyMask);
$worksheet->setCellValue('F' . $row, $payment->paid_amount);
$worksheet->setCellValue('G' . $row, Date::PHPToExcel(\DateTime::createFromFormat('!Y-m-d', $payment->payment_date)));
$worksheet->getCell('G' . $row)->getStyle()->getNumberFormat()->setFormatCode('dd.mm.yyyy');
$row++;
}
$worksheet->getStyle('A' . $row - 1 . ':G' . $row - 1)->getBorders()->getBottom()->applyFromArray(['borderStyle' => Border::BORDER_DOUBLE]);
foreach ($net as $tax_value => $amount) {
$worksheet->getStyle('A' . $row . ':G' . $row)->getFont()->setBold(true);
$worksheet->getStyle('D' . $row . ':G' . $row)->getAlignment()->applyFromArray(['horizontal' => Alignment::HORIZONTAL_RIGHT]);
$worksheet->setCellValue('B' . $row, __('invoice.Sails from vat', ['tax' => \Illuminate\Support\Number::percentage($tax_value)]));
$worksheet->getCell('D' . $row)->getStyle()->getNumberFormat()->setFormatCode($this->currencyMask);
$worksheet->setCellValue('D' . $row, $amount);
$worksheet->getCell('E' . $row)->getStyle()->getNumberFormat()->setFormatCode($this->currencyMask);
$worksheet->setCellValue('E' . $row, $tax[$tax_value]);
$worksheet->getCell('F' . $row)->getStyle()->getNumberFormat()->setFormatCode($this->currencyMask);
$worksheet->setCellValue('F' . $row, $gross[$tax_value]);
$row++;
}
foreach ($worksheet->getColumnIterator() as $column) {
$worksheet->getColumnDimension($column->getColumnIndex())->setAutoSize(true);
}
$this->writer = new Xlsx($spreadsheet);
}
}

View File

@@ -37,6 +37,7 @@ return [
'Invoice Number short' => 'Rechnung-Nr.',
'Sum' => 'Summe',
'Net' => 'Nettobetrag',
'Gross' => 'Gesamtbetrag',
'From' => 'Von',
'End' => 'Bis',
'Enter your invoice items. Click add for an additional invoice item.' => 'Gib Deine Rechnungspositionen ein. Klicke auf "Hinzufügen" für weitere Rechnungspositionen.',
@@ -77,5 +78,8 @@ return [
'Payment amount' => 'Zahlungsbetrag',
'Existing payments for invoice' => 'Bestehende Teilzahlungen für Rechnung',
'Paid at' => 'Bezahlt am',
'Sails from vat' => 'Umsätze aus :tax Mehrwertsteuer',
'Invoices from to' => 'Rechnungen vom :from bis :end',
'Payments from to' => 'Zahlungseingänge vom :from bis :end',
];

View File

@@ -18,6 +18,8 @@
<p class="mt-1 text-sm text-gray-600 dark:text-gray-400">
{{ __("excel.Choose data hint") }}
</p>
<p class="text-red-600 font-bold" x-text="message" x-show="error"></p>
<div class="flex flex-row space-x-4 items-center mt-8">
<x-input-label for="from" :value="__('invoice.From')"/>
<x-text-input type="date" id="from" name="from" x-model="data.from"/>
@@ -32,6 +34,9 @@
</header>
<x-primary-button @click="submit" class="mt-8" x-show="data.report !== ''">{{ __('excel.Generate') }}</x-primary-button>
<div id="download"></div>
</section>
</div>
</div>
@@ -43,6 +48,8 @@
<script>
function excelForm() {
return {
error: false,
message: '',
data: {
from: "{{ \Illuminate\Support\Facades\Date::now()->firstOfMonth()->format('Y-m-d') }}",
end: "{{ \Illuminate\Support\Facades\Date::now()->format('Y-m-d') }}",
@@ -50,14 +57,29 @@
},
submit() {
axios.post('/excel', this.data)
let vm = this;
axios.post('/excel', this.data, {responseType: "blob"})
.then(function(response) {
console.log(response.data);
console.log(response);
let url = window.URL.createObjectURL(response.data);
let a = document.createElement('a');
a.href = url;
a.download = 'Excel.xlsx';
if (vm.data.report === 'invoice') {
a.download = '{{ __('invoice.Invoices') }}' + '.xlsx';
}
if (vm.data.report === 'payment') {
a.download = '{{ __('invoice.Payments') }}' + '.xlsx';
}
let download = document.querySelector('#download');
download.appendChild(a);
a.click();
window.URL.revokeObjectURL(url);
}).catch(function(error) {
console.log(error);
vm.error = true;
vm.message = error.response.data.message;
})
}
}
}