diff --git a/app/Http/Controllers/Api/ExcelController.php b/app/Http/Controllers/Api/ExcelController.php index 6a38e08..5668a7f 100644 --- a/app/Http/Controllers/Api/ExcelController.php +++ b/app/Http/Controllers/Api/ExcelController.php @@ -3,8 +3,10 @@ namespace App\Http\Controllers\Api; use App\Http\Controllers\Controller; +use App\Models\Incoming; use App\Models\Invoice; use App\Models\Payment; +use Illuminate\Database\Eloquent\Collection; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; use PhpOffice\PhpSpreadsheet\Shared\Date; @@ -31,7 +33,7 @@ class ExcelController extends Controller $requestData = $request->validate([ 'from' => 'required|date', 'end' => 'required|date', - 'report' => 'required|in:invoice,payment', + 'report' => 'required|in:invoice,payment,incoming,outgoing', ]); $this->from = $requestData['from']; @@ -51,7 +53,14 @@ class ExcelController extends Controller case 'payment': $this->payment(); break; + case 'incoming': + $this->incoming(); + break; + case 'outgoing': + $this->outgoing(); + break; } + return response()->json($this->writer->save('php://output')); } @@ -159,6 +168,7 @@ class ExcelController extends Controller $row = 2; foreach ($payments as $payment) { $invoice = $payment->invoice; + foreach ($invoice->items as $item) { if (!isset($net[$item->tax])) { $net[$item->tax] = 0; @@ -169,6 +179,7 @@ class ExcelController extends Controller $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); @@ -204,6 +215,97 @@ class ExcelController extends Controller } $this->writer = new Xlsx($spreadsheet); + } + protected function incoming(): void + { + $incoming = Incoming::whereBetween('issue_date', [$this->from, $this->end])->with(['supplier', 'taxes'])->orderBy('issue_date', 'desc')->get(); + $this->incomingToSheet($incoming); + } + + protected function outgoing(): void + { + $incoming = Incoming::whereBetween('pay_date', [$this->from, $this->end])->with(['supplier', 'taxes'])->orderBy('pay_date', 'desc')->get(); + $this->incomingToSheet($incoming); + } + + protected function incomingToSheet(Collection $incoming): void + { + $net = []; + $tax = []; + $gross = []; + + $spreadsheet = new Spreadsheet(); + $worksheet = $spreadsheet->getActiveSheet(); + $worksheet->setTitle(__('incoming.Incoming')); + $worksheet->getPageSetup()->setOrientation(PageSetup::ORIENTATION_LANDSCAPE); + $worksheet->getHeaderFooter()->setEvenHeader(__('incoming.Invoices from to', ['from' => $this->from, 'end' => $this->end])); + $worksheet->getHeaderFooter()->setOddHeader(__('incoming.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->setCellValue('H1', __('common.Paid at')); + $worksheet->getStyle('A1:H1')->getBorders()->getBottom()->applyFromArray(['borderStyle' => Border::BORDER_DOUBLE]); + $worksheet->getStyle('A1:H1')->getFont()->setBold(true); + $worksheet->getStyle('D1:H1')->getAlignment()->applyFromArray(['horizontal' => Alignment::HORIZONTAL_RIGHT]); + + $row = 2; + foreach ($incoming as $invoice) { + foreach ($invoice->taxes as $item) { + if (!isset($net[$item->percentage])) { + $net[$item->percentage] = 0; + $tax[$item->percentage] = 0; + $gross[$item->percentage] = 0; + } + $net[$item->percentage] += $item->taxable_amount; + $tax[$item->percentage] += $item->amount; + $gross[$item->percentage] += $item->taxable_amount + $item->amount; + } + $worksheet->setCellValue('A' . $row, $invoice->invoice_number); + $worksheet->setCellValue('B' . $row, $invoice->supplier->name); + $worksheet->setCellValue('C' . $row, $invoice->supplier->email); + $worksheet->getCell('D' . $row)->getStyle()->getNumberFormat()->setFormatCode($this->currencyMask); + $worksheet->setCellValue('D' . $row, $invoice->net); + $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->gross); + $worksheet->setCellValue('G' . $row, Date::PHPToExcel(\DateTime::createFromFormat('!Y-m-d', $invoice->issue_date))); + $worksheet->getCell('G' . $row)->getStyle()->getNumberFormat()->setFormatCode('dd.mm.yyyy'); + if (is_null($invoice->pay_date)) { + $worksheet->setCellValue('H' . $row, ''); + } else { + $worksheet->setCellValue('H' . $row, Date::PHPToExcel(\DateTime::createFromFormat('!Y-m-d', $invoice->pay_date))); + } + $worksheet->getCell('H' . $row)->getStyle()->getNumberFormat()->setFormatCode('dd.mm.yyyy'); + $row++; + } + + $worksheet->getStyle('A' . $row - 1 . ':H' . $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); } } diff --git a/app/Http/Controllers/Api/IncomingController.php b/app/Http/Controllers/Api/IncomingController.php new file mode 100644 index 0000000..53f8eac --- /dev/null +++ b/app/Http/Controllers/Api/IncomingController.php @@ -0,0 +1,180 @@ +json(Incoming::whereBetween('issue_date', [$from, $end])->with(['supplier'])->orderBy('issue_date', 'desc')->get()); + } + + /** + * Process an upload file for the resource. + */ + public function upload(Request $request): JsonResponse + { + $xmlString = base64_decode(preg_replace('#^data:text/\w+;base64,#i', '', $request->uploadFile)); + $reader = new \App\XmlReader($xmlString); + $supplierData = $reader->supplier(); + $incomingData = $reader->incoming(); + $itemsData = $reader->items(); + $taxesData = $reader->taxes(); + + $supplier = Supplier::where('email', '=', $supplierData['email'])->firstOrNew($supplierData); + $supplier->save(); + + $incomingData['supplier_id'] = $supplier->id; + + $incoming = new Incoming($incomingData); + $incoming->save(); + + foreach ($itemsData as $item) { + $item['incoming_id'] = $incoming->id; + $incomingItem = new Incomingitem($item); + $incomingItem->save(); + } + + foreach ($taxesData as $tax) { + $tax['incoming_id'] = $incoming->id; + $incomingTax = new Incomingtax($tax); + $incomingTax->save(); + } + + return response()->json($incoming); + } + + /** + * Store a newly created resource in storage. + */ + public function store(Request $request): JsonResponse + { + $supplierData = $request->validate([ + 'supplier.name' => 'required|string', + 'supplier.registration_name' => 'nullable|string', + 'supplier.email' => 'nullable|email', + 'supplier.address' => 'nullable|string', + 'supplier.city' => 'nullable|string', + 'supplier.zip' => 'nullable|string', + 'supplier.country_code' => 'nullable|string', + 'supplier.tax_fc' => 'nullable|string', + 'supplier.tax_vat' => 'nullable|string', + 'supplier.contact_name' => 'nullable|string', + 'supplier.contact_phone' => 'nullable|string', + 'supplier.contact_email' => 'nullable|string', + ]); + + $supplier = Supplier::where('email', '=', $supplierData['supplier']['email'])->firstOrNew($supplierData['supplier']); + $supplier->save(); + + $incomingData = $request->validate([ + 'incoming.invoice_number' => 'required|string', + 'incoming.issue_date' => 'required|date', + 'incoming.due_date' => 'nullable|date', + 'incoming.invoice_type_code' => 'required|string', + 'incoming.currency_code' => 'required|string', + 'incoming.net' => 'required|numeric', + 'incoming.gross' => 'required|numeric', + 'incoming.tax' => 'required|numeric', + 'incoming.pay_date' => 'nullable|date', + 'incoming.pay_name' => 'nullable|string', + 'incoming.pay_bic' => 'nullable|string', + 'incoming.pay_iban' => 'nullable|string', + ]); + + $incomingData['incoming']['supplier_id'] = $supplier->id; + $incoming = new Incoming($incomingData['incoming']); + $incoming->save(); + + $taxesData = $request->validate([ + 'taxes.*.taxable_amount' => 'required|numeric', + 'taxes.*.amount' => 'required|numeric', + 'taxes.*.percentage' => 'required|numeric', + 'taxes.*.currency' => 'required|string', + ]); + + $incoming->taxes()->createMany($taxesData['taxes']); + + return response()->json($supplierData['supplier']); + } + + /** + * Update the specified resource in storage. + */ + public function update(Request $request, Incoming $incoming): JsonResponse + { + $incomingData = $request->validate([ + 'incoming.invoice_number' => 'required|string', + 'incoming.issue_date' => 'required|date', + 'incoming.due_date' => 'nullable|date', + 'incoming.invoice_type_code' => 'required|string', + 'incoming.currency_code' => 'required|string', + 'incoming.net' => 'required|numeric', + 'incoming.gross' => 'required|numeric', + 'incoming.tax' => 'required|numeric', + 'incoming.pay_date' => 'nullable|date', + 'incoming.pay_name' => 'nullable|string', + 'incoming.pay_bic' => 'nullable|string', + 'incoming.pay_iban' => 'nullable|string', + ]); + + $incoming->update($incomingData['incoming']); + + $supplierData = $request->validate([ + 'supplier.name' => 'required|string', + 'supplier.registration_name' => 'nullable|string', + 'supplier.email' => 'nullable|email', + 'supplier.address' => 'nullable|string', + 'supplier.city' => 'nullable|string', + 'supplier.zip' => 'nullable|string', + 'supplier.country_code' => 'nullable|string', + 'supplier.tax_fc' => 'nullable|string', + 'supplier.tax_vat' => 'nullable|string', + 'supplier.contact_name' => 'nullable|string', + 'supplier.contact_phone' => 'nullable|string', + 'supplier.contact_email' => 'nullable|string', + ]); + + $incoming->supplier()->update($supplierData['supplier']); + + $itemsData = $request->validate([ + 'items.*.name' => 'required|string', + 'items.*.article_number' => 'nullable|string', + 'items.*.description' => 'nullable|string', + 'items.*.amount' => 'required|numeric', + 'items.*.discount' => 'nullable|numeric', + 'items.*.tax' => 'required|numeric', + 'items.*.price' => 'required|numeric', + 'items.*.total' => 'required|numeric', + ]); + + $incoming->items()->delete(); + + if (!empty($itemsData)) { + $incoming->items()->createMany($itemsData['items']); + } + + $taxesData = $request->validate([ + 'taxes.*.taxable_amount' => 'required|numeric', + 'taxes.*.amount' => 'required|numeric', + 'taxes.*.percentage' => 'required|numeric', + 'taxes.*.currency' => 'required|string', + ]); + + $incoming->taxes()->delete(); + $incoming->taxes()->createMany($taxesData['taxes']); + + return response()->json($incoming); + } +} diff --git a/app/Http/Controllers/Api/InvoiceController.php b/app/Http/Controllers/Api/InvoiceController.php index bb5893d..ef3992f 100644 --- a/app/Http/Controllers/Api/InvoiceController.php +++ b/app/Http/Controllers/Api/InvoiceController.php @@ -19,6 +19,9 @@ class InvoiceController extends Controller return response()->json(Invoice::whereBetween('created_at', [$from, $end])->with(['address', 'customer'])->orderBy('created_at', 'desc')->get()); } + /** + * Display a listing of the resource that have a status "sent". + */ public function open(): JsonResponse { return response()->json(Invoice::where('status', '=', 'sent')->orderBy('created_at', 'desc')->with(['customer', 'address', 'payments'])->get()); @@ -49,14 +52,6 @@ class InvoiceController extends Controller return response()->json($invoice); } - /** - * Display the specified resource. - */ - public function show(Invoice $invoice) - { - // - } - /** * Update the specified resource in storage. */ @@ -76,6 +71,9 @@ class InvoiceController extends Controller return response()->json($invoice); } + /** + * Update the specified resource's status in storage. + */ public function state(Request $request, Invoice $invoice): JsonResponse { $invoiceData = $request->validate([ diff --git a/app/Http/Controllers/IncomingController.php b/app/Http/Controllers/IncomingController.php new file mode 100644 index 0000000..966ea82 --- /dev/null +++ b/app/Http/Controllers/IncomingController.php @@ -0,0 +1,41 @@ + $incoming, 'supplier' => $incoming->supplier, 'items' => $incoming->items, 'taxes' => $incoming->taxes]); + } +} diff --git a/app/Models/Incoming.php b/app/Models/Incoming.php index b3537bc..70610d9 100644 --- a/app/Models/Incoming.php +++ b/app/Models/Incoming.php @@ -2,6 +2,7 @@ namespace App\Models; +use Carbon\Carbon; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\HasMany; @@ -31,6 +32,41 @@ class Incoming extends Model 'pay_iban', ]; + /** + * The attributes that are appended with attribute getters. + * + * @var string[] + */ + protected $appends = [ + 'created', + 'paid', + 'due', + ]; + + /** + * Get the pay_date attribute in local time format. + */ + public function getDueAttribute(): string + { + return (is_null($this->due_date)) ? '' : Carbon::createFromFormat('Y-m-d', $this->due_date)->format('d.m.Y'); + } + + /** + * Get the pay_date attribute in local time format. + */ + public function getPaidAttribute(): string + { + return (is_null($this->pay_date)) ? '' : Carbon::createFromFormat('Y-m-d', $this->pay_date)->format('d.m.Y'); + } + + /** + * Get the issue_date attribute in local time format. + */ + public function getCreatedAttribute(): string + { + return Carbon::createFromFormat('Y-m-d', $this->issue_date)->format('d.m.Y'); + } + /** * Get the supplier of the incoming invoice. */ diff --git a/app/XmlReader.php b/app/XmlReader.php new file mode 100644 index 0000000..d03638c --- /dev/null +++ b/app/XmlReader.php @@ -0,0 +1,99 @@ +xml = new DOMDocument(); + $this->xml->loadXML($xml); + $this->xpath = new DOMXPath($this->xml); + } + + public function incoming(): array + { + $incoming = []; + + $incoming['invoice_number'] = $this->xpath->query('cbc:ID', $this->xml->documentElement)->item(0)->nodeValue; + $incoming['issue_date'] = $this->xpath->query('cbc:IssueDate', $this->xml->documentElement)->item(0)->nodeValue; + $incoming['due_date'] = $this->xpath->query('cbc:DueDate', $this->xml->documentElement)->item(0)->nodeValue; + $incoming['invoice_type_code'] = $this->xpath->query('cbc:InvoiceTypeCode', $this->xml->documentElement)->item(0)->nodeValue; + $incoming['currency_code'] = $this->xpath->query('cbc:DocumentCurrencyCode', $this->xml->documentElement)->item(0)->nodeValue; + $incoming['net'] = $this->xpath->query('cac:LegalMonetaryTotal/cbc:TaxExclusiveAmount', $this->xml->documentElement)->item(0)->nodeValue; + $incoming['gross'] = $this->xpath->query('cac:LegalMonetaryTotal/cbc:TaxInclusiveAmount', $this->xml->documentElement)->item(0)->nodeValue; + $incoming['tax'] = $this->xpath->query('cac:TaxTotal/cbc:TaxAmount', $this->xml->documentElement)->item(0)->nodeValue; + $incoming['pay_name'] = $this->xpath->query('cac:PaymentMeans/cac:PayeeFinancialAccount/cbc:Name', $this->xml->documentElement)->item(0)->nodeValue; + $incoming['pay_bic'] = ($this->xpath->query('cac:PaymentMeans/cac:PayeeFinancialAccount/cac:FinancialInstitutionBranch/cbc:ID', $this->xml->documentElement)->length > 0) ? $this->xpath->query('cac:PaymentMeans/cac:PayeeFinancialAccount/cac:FinancialInstitutionBranch/cbc:ID', $this->xml->documentElement)->item(0)->nodeValue : null; + $incoming['pay_iban'] = $this->xpath->query('cac:PaymentMeans/cac:PayeeFinancialAccount/cbc:ID', $this->xml->documentElement)->item(0)->nodeValue; + + return $incoming; + } + + public function supplier(): array + { + $supplier = []; + $supplierNode = $this->xpath->query('cac:AccountingSupplierParty', $this->xml->documentElement)->item(0); + + $supplier['name'] = $this->xpath->query("cac:Party/cac:PartyName/cbc:Name", $supplierNode)->item(0)->nodeValue; + $supplier['registration_name'] = $this->xpath->query("cac:Party/cac:PartyLegalEntity/cbc:RegistrationName", $supplierNode)->item(0)->nodeValue; + $supplier['email'] = $this->xpath->query("cac:Party/cbc:EndpointID", $supplierNode)->item(0)->nodeValue; + $supplier['address'] = $this->xpath->query("cac:Party/cac:PostalAddress/cbc:StreetName", $supplierNode)->item(0)->nodeValue; + $supplier['city'] = $this->xpath->query("cac:Party/cac:PostalAddress/cbc:CityName", $supplierNode)->item(0)->nodeValue; + $supplier['zip'] = $this->xpath->query("cac:Party/cac:PostalAddress/cbc:PostalZone", $supplierNode)->item(0)->nodeValue; + $supplier['country_code'] = $this->xpath->query("cac:Party/cac:PostalAddress/cac:Country/cbc:IdentificationCode", $supplierNode)->item(0)->nodeValue; + $supplier['tax_fc'] = ($this->xpath->query("cac:Party/cac:PartyTaxScheme[cac:TaxScheme/cbc:ID/text() = 'FC']", $supplierNode)->length === 0) ? null : $this->xpath->query("cac:Party/cac:PartyTaxScheme[cac:TaxScheme/cbc:ID/text() = 'FC']/cbc:CompanyID", $supplierNode)->item(0)->nodeValue; + $supplier['tax_vat'] = ($this->xpath->query("cac:Party/cac:PartyTaxScheme[cac:TaxScheme/cbc:ID/text() = 'VAT']", $supplierNode)->length === 0) ? null : $this->xpath->query("cac:Party/cac:PartyTaxScheme[cac:TaxScheme/cbc:ID/text() = 'VAT']/cbc:CompanyID", $supplierNode)->item(0)->nodeValue; + $supplier['contact_name'] = $this->xpath->query("cac:Party/cac:Contact/cbc:Name", $supplierNode)->item(0)->nodeValue; + $supplier['contact_phone'] = $this->xpath->query("cac:Party/cac:Contact/cbc:Telephone", $supplierNode)->item(0)->nodeValue; + $supplier['contact_email'] = $this->xpath->query("cac:Party/cac:Contact/cbc:ElectronicMail", $supplierNode)->item(0)->nodeValue; + + return $supplier; + } + + public function items(): array + { + $items = []; + $itemList = $this->xpath->query('cac:InvoiceLine', $this->xml->documentElement); + + foreach ($itemList as $itemNode) { + $item = []; + $item['name'] = $this->xpath->query("cac:Item/cbc:Name", $itemNode)->item(0)->nodeValue; + $item['article_number'] = null; + $item['description'] = ($this->xpath->query("cac:Item/cbc:Description", $itemNode)->length === 0) ? null : $this->xpath->query("cac:Item/cbc:Description", $itemNode)->item(0)->nodeValue; + $item['amount'] = $this->xpath->query("cbc:InvoicedQuantity", $itemNode)->item(0)->nodeValue; + $item['discount'] = null; + $item['tax'] = $this->xpath->query("cac:Item/cac:ClassifiedTaxCategory/cbc:Percent", $itemNode)->item(0)->nodeValue; + $item['price'] = $this->xpath->query("cac:Price/cbc:PriceAmount", $itemNode)->item(0)->nodeValue; + $item['total'] = $this->xpath->query("cbc:LineExtensionAmount", $itemNode)->item(0)->nodeValue; + + $items[] = $item; + } + + return $items; + } + + public function taxes(): array + { + $taxes = []; + $taxList = $this->xpath->query('cac:TaxTotal/cac:TaxSubtotal', $this->xml->documentElement); + + foreach ($taxList as $taxNode) { + $tax = []; + $tax['taxable_amount'] = $this->xpath->query("cbc:TaxableAmount", $taxNode)->item(0)->nodeValue; + $tax['amount'] = $this->xpath->query("cbc:TaxAmount", $taxNode)->item(0)->nodeValue; + $tax['percentage'] = $this->xpath->query("cac:TaxCategory/cbc:Percent", $taxNode)->item(0)->nodeValue; + $tax['currency'] = $this->xpath->query("cbc:TaxableAmount", $taxNode)->item(0)->getAttribute("currencyID"); + + $taxes[] = $tax; + } + + return $taxes; + } +} diff --git a/composer.json b/composer.json index 424bdc9..9bdaf5c 100644 --- a/composer.json +++ b/composer.json @@ -11,7 +11,8 @@ "laravel/framework": "^11.31", "laravel/sanctum": "^4.0", "laravel/tinker": "^2.9", - "phpoffice/phpspreadsheet": "^3.8" + "phpoffice/phpspreadsheet": "^3.8", + "ext-dom": "*" }, "require-dev": { "fakerphp/faker": "^1.23", diff --git a/lang/de/common.php b/lang/de/common.php index 2c56e71..aabb1db 100644 --- a/lang/de/common.php +++ b/lang/de/common.php @@ -26,6 +26,7 @@ return [ 'Zip Code' => 'Postleitzahl', 'City' => 'Stadt', 'Created at' => 'Erstellt am', + 'Paid at' => 'Bezahlt am', 'Search' => 'Suchen', 'You\'re logged in!' => 'Du bist angemeldet!', 'Confirm' => 'Bestätigen', @@ -38,5 +39,13 @@ return [ 'Email-Subject' => 'Betreff', 'Email-Body' => 'Nachricht', 'Tooltip multiple email' => 'Mehrere E-Mail-Adressen werden durch "," (Komma) von einander getrennt.', + 'Registration name' => 'Registrierungsname', + 'Country code' => 'Ländercode', + 'Tax FC' => 'Steuernummer', + 'Tax VAT' => 'USt-Id', + 'Contact name' => 'Ansprechpartner', + 'Contact email' => 'E-Mail', + 'Contact phone' => 'Telefon', + 'Currency code' => 'Währungscode', ]; diff --git a/lang/de/customer.php b/lang/de/customer.php index 9489027..993b274 100644 --- a/lang/de/customer.php +++ b/lang/de/customer.php @@ -33,6 +33,7 @@ return [ 'Are you sure you want to delete the address?' => 'Sicher, dass die Adresse gelöscht werden soll?', 'Once the address is deleted, all the ressources and data will be permanently deleted.' => 'Sobald die Adresse gelöscht wird, werden alle Ressourcen und Daten dauerhaft gelöscht.', 'Enter your customer\'s address.' => 'Gib die Adresse des Kunden ein.', - 'Hint edit address.' => 'Bei bestehenden Adressen kann nur gewählt werden, ob es sich um eine Rechnungs- oder Versandadresse handelt. Die Bearbeitung der anderen Felder ist unterbunden, da diese Daten Einfluss auf bereits bestehende Rechnungen haben.' + 'Hint edit address.' => 'Bei bestehenden Adressen kann nur gewählt werden, ob es sich um eine Rechnungs- oder Versandadresse handelt. Die Bearbeitung der anderen Felder ist unterbunden, da diese Daten Einfluss auf bereits bestehende Rechnungen haben.', + 'Edit address' => 'Adresse bearbeiten', ]; diff --git a/lang/de/dashboard.php b/lang/de/dashboard.php index 4432df6..e242777 100644 --- a/lang/de/dashboard.php +++ b/lang/de/dashboard.php @@ -16,5 +16,7 @@ return [ 'Customers without address' => 'Kunden ohne Rechnungsadresse', 'Invoices not sent' => 'Nicht versendete Rechnungen', 'Invoices not paid' => 'Nicht bezahlte Rechnungen', + 'Incoming not paid' => 'Nicht bezahlte Eingangsrechnungen', + 'Incoming paid' => 'Bezahlte Eingangsrechnungen', ]; diff --git a/lang/de/excel.php b/lang/de/excel.php index 18f7a29..4fe41df 100644 --- a/lang/de/excel.php +++ b/lang/de/excel.php @@ -19,6 +19,8 @@ return [ 'Generate' => 'Generieren', 'Outgoing invoices' => 'Ausgangsrechnungen', 'Incoming payments' => 'Zahlungseingang', + 'Incoming by issue date' => 'Eingang nach Rechnungsdatum', + 'Outgoing by pay date' => 'Eingang nach Zahlungsdatum', ]; diff --git a/lang/de/form.php b/lang/de/form.php index 29fb22f..edeb6f0 100644 --- a/lang/de/form.php +++ b/lang/de/form.php @@ -20,5 +20,6 @@ return [ 'Saved' => 'Gespeichert', 'Cancel' => 'Abbrechen', 'Send' => 'Senden', + 'Upload' => 'Hochladen', ]; diff --git a/lang/de/incoming.php b/lang/de/incoming.php new file mode 100644 index 0000000..c579f95 --- /dev/null +++ b/lang/de/incoming.php @@ -0,0 +1,27 @@ + 'Eingang', + 'Add new invoice' => 'Neue Eingangsrechnung manuell anlegen', + 'Add new invoice by clicking add' => 'Neue Eingangsrechnung durch Klick auf "Anlegen" erstellen. Die Daten zur Rechnung müssen händisch eingegeben werden.', + 'Upload new invoice' => 'Neue Eingangsrechnung hochladen', + 'Upload new xml invoice by clicking upload' => 'Neue Eingangsrechnung durch Klick auf "Hochladen" erstellen. Es können nur XML Rechnungen im e-Rechnungsformat verarbeitet werden.', + 'Invoice file' => 'Rechnungsdatei', + 'Invoices from to' => 'Eingangsrechnungen vom :from bis :end', + 'Payments from to' => 'Zahlungsausgänge vom :from bis :end', + 'Outgoing' => 'Bezahlte Eingangsrechnungen', + 'Existing invoices' => 'Bestehende Eingangsrechnungen', + 'Edit incoming' => 'Bestehende Eingangsrechnungen bearbeiten', + 'Incoming data' => 'Daten der Eingangsrechnung', + +]; diff --git a/lang/de/invoice.php b/lang/de/invoice.php index 1d353c0..36f1c11 100644 --- a/lang/de/invoice.php +++ b/lang/de/invoice.php @@ -35,6 +35,8 @@ return [ 'State' => 'Status', 'Invoice Number' => 'Rechnungsnummer', 'Invoice Number short' => 'Rechnung-Nr.', + 'Invoice Date' => 'Rechnungsdatum', + 'Invoice Due date' => 'Fälligkeitsdatum', 'Sum' => 'Summe', 'Net' => 'Nettobetrag', 'Gross' => 'Gesamtbetrag', @@ -81,5 +83,10 @@ return [ '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', + 'Account holder' => 'Kontoinhaber', + 'Tax items' => 'Steueranteile', + 'Article number' => 'Artikelnummer', + 'Discount' => 'Rabatt', + 'Type code' => 'Rechnungstyp', ]; diff --git a/lang/de/supplier.php b/lang/de/supplier.php new file mode 100644 index 0000000..9e63f40 --- /dev/null +++ b/lang/de/supplier.php @@ -0,0 +1,16 @@ + 'Lieferant', + +]; diff --git a/resources/views/dashboard.blade.php b/resources/views/dashboard.blade.php index 67a6288..dc0d785 100644 --- a/resources/views/dashboard.blade.php +++ b/resources/views/dashboard.blade.php @@ -2,7 +2,7 @@ $customers = \App\Models\Customer::doesntHave('address')->get(); $created_invoices = \App\Models\Invoice::where('status', '=', 'created')->orderBy('created_at')->get(); $sent_invoices = \App\Models\Invoice::where('status', '=', 'sent')->orderBy('created_at')->get(); - + $unpaid_incoming = \App\Models\Incoming::where('pay_date', '=', null)->orderBy('due_date')->get(); @endphp @@ -24,7 +24,7 @@ class="flex max-w even:bg-gray-100 odd:bg-white">
{{ $invoice->number }}
{{ $invoice->address->name }}
-
{{ $invoice->sum }}
+
{{ \Illuminate\Support\Number::currency($invoice->sum) }}
{{ $invoice->created }}
@endforeach @@ -59,6 +59,22 @@ + +
+ +
+
diff --git a/resources/views/excel.blade.php b/resources/views/excel.blade.php index fc7f0f0..ca76cad 100644 --- a/resources/views/excel.blade.php +++ b/resources/views/excel.blade.php @@ -29,6 +29,8 @@ @@ -60,7 +62,6 @@ let vm = this; axios.post('/excel', this.data, {responseType: "blob"}) .then(function(response) { - console.log(response); let url = window.URL.createObjectURL(response.data); let a = document.createElement('a'); a.href = url; @@ -71,6 +72,12 @@ if (vm.data.report === 'payment') { a.download = '{{ __('invoice.Payments') }}' + '.xlsx'; } + if (vm.data.report === 'incoming') { + a.download = '{{ __('incoming.Incoming') }}' + '.xlsx'; + } + if (vm.data.report === 'outgoing') { + a.download = '{{ __('incoming.Outgoing') }}' + '.xlsx'; + } let download = document.querySelector('#download'); download.appendChild(a); a.click(); diff --git a/resources/views/incoming/create.blade.php b/resources/views/incoming/create.blade.php new file mode 100644 index 0000000..4631498 --- /dev/null +++ b/resources/views/incoming/create.blade.php @@ -0,0 +1,310 @@ + + +

+ {{ __('incoming.Edit incoming') }} +

+
+ +
+
+ + +
+
+
+
+

+ {{ __('incoming.Incoming data') }} +

+
+ +
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + + + + + + + + +
+ + {{ __('form.Save') }} + +
+
+
+
+
+ + +
+
+
+
+

+ {{ __('supplier.Supplier') }} +

+
+ +
+ +
+
+ + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + + + + + + + + + + +
+
+ +
+ + +
+
+
+ + +
+
+
+
+

+ {{ __('invoice.Tax items') }} + + +

+
+ +
+ + + + +
+
+ +
+ +
+ +
+
+
+ +
+
+
+ + diff --git a/resources/views/incoming/edit.blade.php b/resources/views/incoming/edit.blade.php new file mode 100644 index 0000000..1b704d7 --- /dev/null +++ b/resources/views/incoming/edit.blade.php @@ -0,0 +1,394 @@ + + +

+ {{ __('incoming.Edit incoming') }} +

+
+ +
+
+ + +
+
+
+
+

+ {{ __('incoming.Incoming data') }} +

+
+ +
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + + + + + + + + +
+ + {{ __('form.Save') }} + +
+
+
+
+
+ + +
+
+
+
+

+ {{ __('supplier.Supplier') }} +

+
+ +
+ +
+
+ + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + + + + + + + + + + +
+
+ +
+ + +
+
+
+ + +
+
+
+
+

+ {{ __('invoice.Invoice items') }} + + +

+
+ +
+ + + + + + +
+
+ +
+ +
+ + +
+
+
+ + +
+
+
+
+

+ {{ __('invoice.Tax items') }} + + +

+
+ +
+ + + + +
+
+ +
+ +
+ +
+
+
+ +
+
+
+ + diff --git a/resources/views/incoming/index.blade.php b/resources/views/incoming/index.blade.php new file mode 100644 index 0000000..76b7bd9 --- /dev/null +++ b/resources/views/incoming/index.blade.php @@ -0,0 +1,143 @@ + + +
+

+ {{ __('incoming.Incoming') }} +

+ + + +
+
+ +
+
+
+
+
+
+

+ {{ __('incoming.Add new invoice') }} +

+ +

+ {{ __("incoming.Add new invoice by clicking add") }} +

+
+ {{ __('form.Add') }} +
+
+
+

+ {{ __('incoming.Upload new invoice') }} +

+ +

+ {{ __("incoming.Upload new xml invoice by clicking upload") }} +

+
+ {{ __('form.Upload') }} +
+
+
+ +
+
+
+
+

+ {{ __('incoming.Existing invoices') }} +

+
+ + + + +
+
+ + +
{{ __('invoice.Invoice Number') }}
+
{{ __('supplier.Supplier') }}
+
{{ __('common.Name') }}
+
{{ __('invoice.Sum') }}
+
{{ __('common.Created at') }}
+
{{ __('common.Paid at') }}
+
+ + + +
+ +
+
{{ __('invoice.Net') }}
+
+
{{ __('invoice.Tax') }}
+
+
{{ __('invoice.Sum') }}
+
+
+ +
+
+
+ + +
+
+
+ + diff --git a/resources/views/incoming/upload.blade.php b/resources/views/incoming/upload.blade.php new file mode 100644 index 0000000..868fc21 --- /dev/null +++ b/resources/views/incoming/upload.blade.php @@ -0,0 +1,77 @@ + + +
+

+ {{ __('incoming.Upload new invoice') }} +

+
+
+ +
+
+
+
+
+
+

+ {{ __('incoming.Upload new invoice') }} +

+ +

+ {{ __("incoming.Upload new xml invoice by clicking upload") }} +

+
+
+
+

+
+ + +
+ {{ __('form.Upload') }} +
+ +
+
+ +
+
+
+ + diff --git a/resources/views/layouts/navigation.blade.php b/resources/views/layouts/navigation.blade.php index d67feaf..e880d91 100644 --- a/resources/views/layouts/navigation.blade.php +++ b/resources/views/layouts/navigation.blade.php @@ -31,6 +31,10 @@ :active="\Illuminate\Support\Str::startsWith(request()->route()->getName(), 'taxrate.')"> {{ __('configuration.Taxrates') }} + + {{ __('incoming.Incoming') }} + {{ __('configuration.Options') }} diff --git a/routes/api.php b/routes/api.php index b75c874..6d3f8c8 100644 --- a/routes/api.php +++ b/routes/api.php @@ -4,6 +4,7 @@ use App\Http\Controllers\Api\AddressController; use App\Http\Controllers\Api\AuthController; use App\Http\Controllers\Api\CustomerController; use App\Http\Controllers\Api\ExcelController; +use App\Http\Controllers\Api\IncomingController; use App\Http\Controllers\Api\InvoiceController; use App\Http\Controllers\Api\InvoiceitemController; use App\Http\Controllers\Api\MailController; @@ -40,6 +41,10 @@ Route::group(['as' => 'api.'], function () { Route::get('/payment-filter/{start}/{end}', [PaymentController::class, 'indexFilter'])->name('payment.index'); Route::apiResource('/invoice.payment', PaymentController::class)->shallow(); Route::post('/excel', [ExcelController::class, 'export'])->name('excel.export'); + Route::post('/incoming-upload', [IncomingController::class, 'upload'])->name('incoming.upload'); + Route::get('/incoming-filter/{start}/{end}', [IncomingController::class, 'index'])->name('incoming.index'); + Route::put('/incoming/{incoming}', [IncomingController::class, 'update'])->name('incoming.update'); + Route::post('/incoming', [IncomingController::class, 'store'])->name('incoming.store'); }); diff --git a/routes/web.php b/routes/web.php index acc5f74..053ece6 100644 --- a/routes/web.php +++ b/routes/web.php @@ -3,6 +3,7 @@ use App\Http\Controllers\AddressController; use App\Http\Controllers\CustomerController; use App\Http\Controllers\EController; +use App\Http\Controllers\IncomingController; use App\Http\Controllers\InvoiceController; use App\Http\Controllers\OptionController; use App\Http\Controllers\PaymentController; @@ -34,6 +35,8 @@ Route::middleware('auth')->group(function () { Route::get('/invoice/{id}/mail', [InvoiceController::class, 'mail'])->name('invoice.mail'); Route::resource('/payment', PaymentController::class)->only(['index', 'create', 'edit']); Route::get('/excel', function() { return view('excel'); })->name('excel'); + Route::resource('/incoming', IncomingController::class)->only(['index', 'create', 'edit']); + Route::get('/incoming-upload', [IncomingController::class, 'upload'])->name('incoming.upload'); }); require __DIR__.'/auth.php';