1
0
mirror of https://github.com/invoiceninja/invoiceninja.git synced 2024-09-20 08:21:34 +02:00

Improve expense XML parsing

This commit is contained in:
David Bomba 2024-08-19 07:42:49 +10:00
parent 57866333a8
commit af73fc2e51
5 changed files with 57 additions and 46 deletions

View File

@ -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;
}

View File

@ -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);
}
}

View File

@ -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;
}

View File

@ -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]);
}
}

View File

@ -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;