diff --git a/app/Http/Controllers/ExpenseController.php b/app/Http/Controllers/ExpenseController.php index 3ab2cc90da02..0553b5dd3aec 100644 --- a/app/Http/Controllers/ExpenseController.php +++ b/app/Http/Controllers/ExpenseController.php @@ -19,10 +19,12 @@ use App\Http\Requests\Expense\BulkExpenseRequest; use App\Http\Requests\Expense\CreateExpenseRequest; use App\Http\Requests\Expense\DestroyExpenseRequest; use App\Http\Requests\Expense\EditExpenseRequest; +use App\Http\Requests\Expense\EDocumentRequest; use App\Http\Requests\Expense\ShowExpenseRequest; use App\Http\Requests\Expense\StoreExpenseRequest; use App\Http\Requests\Expense\UpdateExpenseRequest; use App\Http\Requests\Expense\UploadExpenseRequest; +use App\Jobs\EDocument\ImportEDocument; use App\Models\Account; use App\Models\Expense; use App\Repositories\ExpenseRepository; @@ -581,4 +583,15 @@ class ExpenseController extends BaseController return $this->itemResponse($expense->fresh()); } + + public function edocument(EDocumentRequest $request): string + { + if ($request->hasFile("documents")) { + return (new ImportEDocument($request->file("documents")[0]->get(), $request->file("documents")[0]->getClientOriginalName()))->handle(); + } + else { + return "No file found"; + } + + } } diff --git a/app/Http/Requests/Expense/EDocumentRequest.php b/app/Http/Requests/Expense/EDocumentRequest.php new file mode 100644 index 000000000000..5428eda9822d --- /dev/null +++ b/app/Http/Requests/Expense/EDocumentRequest.php @@ -0,0 +1,43 @@ +user(); + + return $user->isAdmin(); + } + + public function rules() + { + $rules = []; + + if ($this->file('documents') && is_array($this->file('documents'))) { + $rules['documents.*'] = $this->fileValidation(); + } elseif ($this->file('documents')) { + $rules['documents'] = $this->fileValidation(); + } + return $rules; + } + + public function prepareForValidation() + { + $input = $this->all(); + + $this->replace($input); + + } + +} diff --git a/app/Jobs/EDocument/ImportEDocument.php b/app/Jobs/EDocument/ImportEDocument.php new file mode 100644 index 000000000000..13fb22de94f8 --- /dev/null +++ b/app/Jobs/EDocument/ImportEDocument.php @@ -0,0 +1,64 @@ +file_content = $file_content; + $this->file_name = $file_name; + } + + /** + * Execute the job. + * + * @return Expense + * @throws \Exception + */ + public function handle(): Expense + { + if (str_contains($this->file_name, ".xml")){ + switch (true) { + case stristr($this->file_content, "urn:cen.eu:en16931:2017"): + case stristr($this->file_content, "urn:cen.eu:en16931:2017#compliant#urn:xeinkauf.de:kosit:xrechnung_3.0"): + case stristr($this->file_content, "urn:cen.eu:en16931:2017#compliant#urn:xeinkauf.de:kosit:xrechnung_2.1"): + case stristr($this->file_content, "urn:cen.eu:en16931:2017#compliant#urn:xeinkauf.de:kosit:xrechnung_2.0"): + return (new ZugferdEDocument($this->file_content, $this->file_name))->run(); + default: + throw new Exception("E-Invoice standard not supported"); + } + } + else { + throw new Exception("File type not supported"); + } + + } +} diff --git a/app/Services/EDocument/Imports/ZugferdEDocument.php b/app/Services/EDocument/Imports/ZugferdEDocument.php new file mode 100644 index 000000000000..443855e6b676 --- /dev/null +++ b/app/Services/EDocument/Imports/ZugferdEDocument.php @@ -0,0 +1,125 @@ +user(); + $this->document = ZugferdDocumentReader::readAndGuessFromContent($this->tempdocument); + $this->document->getDocumentInformation($documentno, $documenttypecode, $documentdate, $invoiceCurrency, $taxCurrency, $documentname, $documentlanguage, $effectiveSpecifiedPeriod); + $this->document->getDocumentSummation($grandTotalAmount, $duePayableAmount, $lineTotalAmount, $chargeTotalAmount, $allowanceTotalAmount, $taxBasisTotalAmount, $taxTotalAmount, $roundingAmount, $totalPrepaidAmount); + + $expense = Expense::where('amount', $grandTotalAmount)->where("transaction_reference", $documentno)->whereDate("date", $documentdate)->first(); + if (empty($expense)) { + // The document does not exist as an expense + // Handle accordingly + $visualizer = new ZugferdVisualizer($this->document); + $visualizer->setDefaultTemplate(); + $visualizer->setPdfFontDefault("arial"); + $visualizer->setPdfPaperSize('A4-P'); + + $expense = ExpenseFactory::create($user->company()->id, $user->id); + $expense->date = $documentdate; + $expense->user_id = $user->id; + $expense->company_id = $user->company->id; + $expense->public_notes = $documentno; + $expense->currency_id = Currency::whereCode($invoiceCurrency)->first()->id; + $expense->save(); + + $origin_file = TempFile::UploadedFileFromRaw($this->tempdocument, $this->documentname, "application/xml"); + (new UploadFile($origin_file, UploadFile::DOCUMENT, $user, $expense->company, $expense, null, false))->handle(); + $uploaded_file = TempFile::UploadedFileFromRaw($visualizer->renderPdf(), $documentno."_visualiser.pdf", "application/pdf"); + (new UploadFile($uploaded_file, UploadFile::DOCUMENT, $user, $expense->company, $expense, null, false))->handle(); + $expense->save(); + if ($taxCurrency && $taxCurrency != $invoiceCurrency) { + $expense->private_notes = ctrans("texts.tax_currency_mismatch"); + } + $expense->uses_inclusive_taxes = false; + $expense->amount = $grandTotalAmount; + $counter = 1; + if ($this->document->firstDocumentTax()) { + do { + $this->document->getDocumentTax($categoryCode, $typeCode, $basisAmount, $calculatedAmount, $rateApplicablePercent, $exemptionReason, $exemptionReasonCode, $lineTotalBasisAmount, $allowanceChargeBasisAmount, $taxPointDate, $dueDateTypeCode); + $expense->{"tax_amount$counter"} = $calculatedAmount; + $expense->{"tax_rate$counter"} = $rateApplicablePercent; + $counter++; + } while ($this->document->nextDocumentTax()); + } + $this->document->getDocumentSeller($name, $buyer_id, $buyer_description); + $this->document->getDocumentSellerContact($person_name, $person_department, $contact_phone, $contact_fax, $contact_email); + $this->document->getDocumentSellerTaxRegistration($taxtype); + $taxid = null; + if (array_key_exists("VA", $taxtype)) { + $taxid = $taxtype["VA"]; + } + // TODO find vendor + $vendor = Vendor::whereHas('contacts', function ($q) use($contact_email, $taxid) { + $q->where('email',$contact_email)->where('vat_number', $taxid); + })->first(); + + + if ($vendor) { + // Vendor found + $expense->vendor_id = $vendor->id; + } else { + $vendor = VendorFactory::create($user->company()->id, $user->id); + $vendor->name = $name; + if ($taxid != null) { + $vendor->vat_number = $taxid; + } + #$vendor->email = $contact_email; + + $vendor->save(); + $expense->vendor_id = $vendor->id; + // Vendor not found + // Handle accordingly + } + $expense->transaction_reference = $documentno; + } + else { + // The document exists as an expense + // Handle accordingly + nlog("Document already exists"); + $expense->private_notes = $expense->private_notes . ctrans("texts.edocument_import_already_exists", ["date" => time()]); + } + $expense->save(); + return $expense; + } +} diff --git a/composer.json b/composer.json index e9839b003ec1..11aa0a0f9058 100644 --- a/composer.json +++ b/composer.json @@ -56,6 +56,7 @@ "hedii/laravel-gelf-logger": "^8", "horstoeko/orderx": "dev-master", "horstoeko/zugferd": "^1", + "horstoeko/zugferdvisualizer":"^1", "hyvor/php-json-exporter": "^0.0.3", "imdhemy/laravel-purchases": "^1.7", "intervention/image": "^2.5", diff --git a/lang/en/texts.php b/lang/en/texts.php index 758575e06cdf..43674305c77a 100644 --- a/lang/en/texts.php +++ b/lang/en/texts.php @@ -2934,6 +2934,13 @@ $lang = array( 'mime_types' => 'Mime types', 'mime_types_placeholder' => '.pdf , .docx, .jpg', 'mime_types_help' => 'Comma separated list of allowed mime types, leave blank for all', + 'ticket_number_start_help' => 'Ticket number must be greater than the current ticket number', + 'new_ticket_template_id' => 'New ticket', + 'new_ticket_autoresponder_help' => 'Selecting a template will send an auto response to a client/contact when a new ticket is created', + 'update_ticket_template_id' => 'Updated ticket', + 'update_ticket_autoresponder_help' => 'Selecting a template will send an auto response to a client/contact when a ticket is updated', + 'close_ticket_template_id' => 'Closed ticket', + 'close_ticket_autoresponder_help' => 'Selecting a template will send an auto response to a client/contact when a ticket is closed', 'default_priority' => 'Default priority', 'alert_new_comment_id' => 'New comment', 'alert_comment_ticket_help' => 'Selecting a template will send a notification (to agent) when a comment is made.', @@ -5303,6 +5310,9 @@ $lang = array( 'currency_bhutan_ngultrum' => 'Bhutan Ngultrum', 'end_of_month' => 'End Of Month', 'merge_e_invoice_to_pdf' => 'Merge E-Invoice and PDF', + 'end_of_month' => 'End Of Month', + 'tax_currency_mismatch' => 'Tax currency is different from invoice currency', + 'edocument_import_already_exists' => '\nThe invoice has already been imported on :date' ); -return $lang; \ No newline at end of file +return $lang; diff --git a/resources/views/edocument/xinvoice.php b/resources/views/edocument/xinvoice.php new file mode 100644 index 000000000000..338d552172d7 --- /dev/null +++ b/resources/views/edocument/xinvoice.php @@ -0,0 +1,406 @@ + +
+ + + +getDocumentInformation($documentno, $documenttypecode, $documentdate, $invoiceCurrency, $taxCurrency, $documentname, $documentlanguage, $effectiveSpecifiedPeriod); +$document->getDocumentBuyer($buyername, $buyerids, $buyerdescription); +$document->getDocumentBuyerAddress($buyeraddressline1, $buyeraddressline2, $buyeraddressline3, $buyerpostcode, $buyercity, $buyercounty, $buyersubdivision); +?> +
+
+
+
+
+
+
+ Invoice Date format("d.m.Y"); ?> +
++ Sehr geehrter Kunde, +
++ wir erlauben uns Ihnen folgende Position in Rechnung zu stellen. +
+Pos. | +Beschreibung | +Stk. | +Preis | +Menge | +MwSt % | +|
---|---|---|---|---|---|---|
+ | + getDocumentPositionNote($posnoteContent, $posnoteContentCode, $posnoteSubjectCode); ?> + + + | +|||||
+ | + | + | + | + firstDocumentPositionTax()) { ?> + getDocumentPositionTax($categoryCode, $typeCode, $rateApplicablePercent, $calculatedAmount, $exemptionReason, $exemptionReasonCode); ?> + | % | + ++ + |
+ | + | + | () | +|||
+ | ||||||
+ | Allowance/Charge | +|||||
+ | + | + | + | |||
+ | ||||||
+ | Summe | +|||||
+ | Nettobetrag | ++ | ||||
+ | Summe Aufschläge | ++ | ||||
+ | Summe Rabatte | ++ | ||||
+ | MwSt. | ++ | ||||
+ | Bruttosumme | ++ | ||||
+ | Bereits gezahlt | ++ | ||||
+ | Zu Zahlen | ++ | ||||
+ | ||||||
+ | VAT Breakdown | +|||||
+ | % | ++ | + | |||
+ | Summe | ++ | + | |||
+ + | +||||||