From af73fc2e51f44be37bb1d035830adce587d1113e Mon Sep 17 00:00:00 2001 From: David Bomba Date: Mon, 19 Aug 2024 07:42:49 +1000 Subject: [PATCH] Improve expense XML parsing --- app/Filters/ProjectFilters.php | 2 +- app/Http/Controllers/ExpenseController.php | 13 ++--- .../Requests/Expense/EDocumentRequest.php | 4 +- app/Jobs/EDocument/ImportEDocument.php | 50 +++++++++++-------- .../EDocument/Imports/ZugferdEDocument.php | 34 +++++++------ 5 files changed, 57 insertions(+), 46 deletions(-) diff --git a/app/Filters/ProjectFilters.php b/app/Filters/ProjectFilters.php index 311e3465ca..8b848472bd 100644 --- a/app/Filters/ProjectFilters.php +++ b/app/Filters/ProjectFilters.php @@ -60,7 +60,7 @@ class ProjectFilters extends QueryFilters { $sort_col = explode('|', $sort); - if (!is_array($sort_col) || count($sort_col) != 2) { + if (!is_array($sort_col) || count($sort_col) != 2 || !in_array($sort_col[0], \Illuminate\Support\Facades\Schema::getColumnListing('projects'))) { return $this->builder; } diff --git a/app/Http/Controllers/ExpenseController.php b/app/Http/Controllers/ExpenseController.php index 0553b5dd3a..3c2028c6f8 100644 --- a/app/Http/Controllers/ExpenseController.php +++ b/app/Http/Controllers/ExpenseController.php @@ -584,14 +584,15 @@ class ExpenseController extends BaseController return $this->itemResponse($expense->fresh()); } - public function edocument(EDocumentRequest $request): string + public function edocument(EDocumentRequest $request) { - if ($request->hasFile("documents")) { - return (new ImportEDocument($request->file("documents")[0]->get(), $request->file("documents")[0]->getClientOriginalName()))->handle(); - } - else { - return "No file found"; + $user = auth()->user(); + + foreach($request->file("documents") as $file) { + ImportEDocument::dispatch($file->get(), $file->getClientOriginalName(), $user->company()); } + return response()->json(['message' => 'Processing....'], 200); + } } diff --git a/app/Http/Requests/Expense/EDocumentRequest.php b/app/Http/Requests/Expense/EDocumentRequest.php index 5428eda982..643c3cd6fc 100644 --- a/app/Http/Requests/Expense/EDocumentRequest.php +++ b/app/Http/Requests/Expense/EDocumentRequest.php @@ -25,9 +25,9 @@ class EDocumentRequest extends Request $rules = []; if ($this->file('documents') && is_array($this->file('documents'))) { - $rules['documents.*'] = $this->fileValidation(); + $rules['documents.*'] = 'required|file|max:1000000|mimes:xml'; } elseif ($this->file('documents')) { - $rules['documents'] = $this->fileValidation(); + $rules['documents'] = 'required|file|max:1000000|mimes:xml'; } return $rules; } diff --git a/app/Jobs/EDocument/ImportEDocument.php b/app/Jobs/EDocument/ImportEDocument.php index 13fb22de94..e92860faf8 100644 --- a/app/Jobs/EDocument/ImportEDocument.php +++ b/app/Jobs/EDocument/ImportEDocument.php @@ -11,14 +11,16 @@ namespace App\Jobs\EDocument; -use App\Models\Expense; -use App\Services\EDocument\Imports\ZugferdEDocument; use Exception; +use App\Models\Company; +use App\Models\Expense; use Illuminate\Bus\Queueable; use Illuminate\Queue\SerializesModels; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; +use Illuminate\Queue\Middleware\WithoutOverlapping; +use App\Services\EDocument\Imports\ZugferdEDocument; class ImportEDocument implements ShouldQueue { @@ -27,14 +29,10 @@ class ImportEDocument implements ShouldQueue use Queueable; use SerializesModels; - public $deleteWhenMissingModels = true; - private string $file_name; - private readonly string $file_content; + public $tries = 1; - public function __construct(string $file_content, string $file_name) + public function __construct(private readonly string $file_content, private string $file_name, private Company $company) { - $this->file_content = $file_content; - $this->file_name = $file_name; } /** @@ -45,20 +43,30 @@ class ImportEDocument implements ShouldQueue */ 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"); + + 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, $this->company))->run(); + default: + throw new Exception("E-Invoice standard not supported"); } } + + public function middleware() + { + return [new WithoutOverlapping($this->company->company_key)]; + } + + public function failed($exception = null) + { + if ($exception) { + nlog("EXCEPTION:: ImportEDocument:: ".$exception->getMessage()); + } + + config(['queue.failed.driver' => null]); + } } diff --git a/app/Services/EDocument/Imports/ZugferdEDocument.php b/app/Services/EDocument/Imports/ZugferdEDocument.php index 2f23849982..f03d298a66 100644 --- a/app/Services/EDocument/Imports/ZugferdEDocument.php +++ b/app/Services/EDocument/Imports/ZugferdEDocument.php @@ -11,19 +11,20 @@ namespace App\Services\EDocument\Imports; -use App\Factory\ExpenseFactory; -use App\Factory\VendorFactory; -use App\Jobs\Util\UploadFile; -use App\Models\Country; -use App\Models\Currency; -use App\Models\Expense; -use App\Models\Vendor; -use App\Services\AbstractService; -use App\Utils\TempFile; use Exception; +use App\Models\Vendor; +use App\Models\Company; +use App\Models\Country; +use App\Models\Expense; +use App\Utils\TempFile; +use App\Models\Currency; +use App\Jobs\Util\UploadFile; +use App\Factory\VendorFactory; +use App\Factory\ExpenseFactory; +use App\Services\AbstractService; use horstoeko\zugferd\ZugferdDocumentReader; -use horstoeko\zugferdvisualizer\renderer\ZugferdVisualizerLaravelRenderer; use horstoeko\zugferdvisualizer\ZugferdVisualizer; +use horstoeko\zugferdvisualizer\renderer\ZugferdVisualizerLaravelRenderer; class ZugferdEDocument extends AbstractService { public ZugferdDocumentReader|string $document; @@ -31,7 +32,7 @@ class ZugferdEDocument extends AbstractService { /** * @throws Exception */ - public function __construct(public string $tempdocument, public string $documentname) + public function __construct(public string $tempdocument, public string $documentname, public Company $company) { # curl -X POST http://localhost:8000/api/v1/edocument/upload -H "Content-Type: multipart/form-data" -H "X-API-TOKEN: 7tdDdkz987H3AYIWhNGXy8jTjJIoDhkAclCDLE26cTCj1KYX7EBHC66VEitJwWhn" -H "X-Requested-With: XMLHttpRequest" -F _method=PUT -F documents[]=@einvoice.xml } @@ -42,13 +43,14 @@ class ZugferdEDocument extends AbstractService { public function run(): Expense { /** @var \App\Models\User $user */ - $user = auth()->user(); + $user = $this->company->owner(); + $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); /** @var \App\Models\Expense $expense */ - $expense = Expense::where("company_id", $user->company()->id)->where('amount', $grandTotalAmount)->where("transaction_reference", $documentno)->whereDate("date", $documentdate)->first(); + $expense = Expense::where("company_id", $this->company->id)->where('amount', $grandTotalAmount)->where("transaction_reference", $documentno)->whereDate("date", $documentdate)->first(); if (!$expense) { // The document does not exist as an expense // Handle accordingly @@ -59,10 +61,10 @@ class ZugferdEDocument extends AbstractService { $visualizer->setPdfPaperSize('A4-P'); $visualizer->setTemplate('edocument.xinvoice'); - $expense = ExpenseFactory::create($user->company()->id, $user->id); + $expense = ExpenseFactory::create($this->company->id, $user->id); $expense->date = $documentdate; $expense->public_notes = $documentno; - $expense->currency_id = Currency::whereCode($invoiceCurrency)->first()->id ?? $user->company()->settings->currency_id; + $expense->currency_id = Currency::whereCode($invoiceCurrency)->first()->id ?? $this->company->settings->currency_id; $expense->save(); $origin_file = TempFile::UploadedFileFromRaw($this->tempdocument, $this->documentname, "application/xml"); @@ -98,7 +100,7 @@ class ZugferdEDocument extends AbstractService { // Vendor found $expense->vendor_id = $vendor->id; } else { - $vendor = VendorFactory::create($user->company()->id, $user->id); + $vendor = VendorFactory::create($this->company->id, $user->id); $vendor->name = $name; if ($taxid != null) { $vendor->vat_number = $taxid;