mirror of
				https://github.com/invoiceninja/invoiceninja.git
				synced 2025-11-04 03:29:03 -05:00 
			
		
		
		
	Implement E-Invoice Import as Expense
This commit is contained in:
		
							parent
							
								
									394bbb6a51
								
							
						
					
					
						commit
						61570cea93
					
				@ -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";
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										43
									
								
								app/Http/Requests/Expense/EDocumentRequest.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								app/Http/Requests/Expense/EDocumentRequest.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,43 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace App\Http\Requests\Expense;
 | 
			
		||||
 | 
			
		||||
use App\Http\Requests\Request;
 | 
			
		||||
use App\Models\User;
 | 
			
		||||
 | 
			
		||||
class EDocumentRequest extends Request
 | 
			
		||||
{
 | 
			
		||||
    /**
 | 
			
		||||
     * Determine if the user is authorized to make this request.
 | 
			
		||||
     *
 | 
			
		||||
     * @return bool
 | 
			
		||||
     */
 | 
			
		||||
    public function authorize(): bool
 | 
			
		||||
    {
 | 
			
		||||
        /** @var User $user */
 | 
			
		||||
        $user = auth()->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);
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										64
									
								
								app/Jobs/EDocument/ImportEDocument.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								app/Jobs/EDocument/ImportEDocument.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,64 @@
 | 
			
		||||
<?php
 | 
			
		||||
/**
 | 
			
		||||
 * Invoice Ninja (https://invoiceninja.com).
 | 
			
		||||
 *
 | 
			
		||||
 * @link https://github.com/invoiceninja/invoiceninja source repository
 | 
			
		||||
 *
 | 
			
		||||
 * @copyright Copyright (c) 2024. Invoice Ninja LLC (https://invoiceninja.com)
 | 
			
		||||
 *
 | 
			
		||||
 * @license https://www.elastic.co/licensing/elastic-license
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
namespace App\Jobs\EDocument;
 | 
			
		||||
 | 
			
		||||
use App\Models\Expense;
 | 
			
		||||
use App\Services\EDocument\Imports\ZugferdEDocument;
 | 
			
		||||
use Exception;
 | 
			
		||||
use Illuminate\Bus\Queueable;
 | 
			
		||||
use Illuminate\Queue\SerializesModels;
 | 
			
		||||
use Illuminate\Queue\InteractsWithQueue;
 | 
			
		||||
use Illuminate\Contracts\Queue\ShouldQueue;
 | 
			
		||||
use Illuminate\Foundation\Bus\Dispatchable;
 | 
			
		||||
 | 
			
		||||
class ImportEDocument implements ShouldQueue
 | 
			
		||||
{
 | 
			
		||||
    use Dispatchable;
 | 
			
		||||
    use InteractsWithQueue;
 | 
			
		||||
    use Queueable;
 | 
			
		||||
    use SerializesModels;
 | 
			
		||||
 | 
			
		||||
    public $deleteWhenMissingModels = true;
 | 
			
		||||
    private string $file_name;
 | 
			
		||||
    private readonly string $file_content;
 | 
			
		||||
 | 
			
		||||
    public function __construct(string $file_content, string $file_name)
 | 
			
		||||
    {
 | 
			
		||||
        $this->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");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										125
									
								
								app/Services/EDocument/Imports/ZugferdEDocument.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										125
									
								
								app/Services/EDocument/Imports/ZugferdEDocument.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,125 @@
 | 
			
		||||
<?php
 | 
			
		||||
/**
 | 
			
		||||
 * Invoice Ninja (https://invoiceninja.com).
 | 
			
		||||
 *
 | 
			
		||||
 * @link https://github.com/invoiceninja/invoiceninja source repository
 | 
			
		||||
 *
 | 
			
		||||
 * @copyright Copyright (c) 2024. Invoice Ninja LLC (https://invoiceninja.com)
 | 
			
		||||
 *
 | 
			
		||||
 * @license https://www.elastic.co/licensing/elastic-license
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
namespace App\Services\EDocument\Imports;
 | 
			
		||||
 | 
			
		||||
use App\Factory\ExpenseFactory;
 | 
			
		||||
use App\Factory\VendorFactory;
 | 
			
		||||
use App\Jobs\Util\UploadFile;
 | 
			
		||||
use App\Models\Currency;
 | 
			
		||||
use App\Models\Expense;
 | 
			
		||||
use App\Models\Vendor;
 | 
			
		||||
use App\Repositories\VendorRepository;
 | 
			
		||||
use App\Services\AbstractService;
 | 
			
		||||
use App\Utils\TempFile;
 | 
			
		||||
use Exception;
 | 
			
		||||
use horstoeko\zugferd\ZugferdDocumentReader;
 | 
			
		||||
use horstoeko\zugferdvisualizer\ZugferdVisualizer;
 | 
			
		||||
use function PHPUnit\Framework\isNull;
 | 
			
		||||
 | 
			
		||||
class ZugferdEDocument extends AbstractService {
 | 
			
		||||
    public ZugferdDocumentReader|string $document;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @throws Exception
 | 
			
		||||
     */
 | 
			
		||||
    public function __construct(public string $tempdocument, public string $documentname)
 | 
			
		||||
    {
 | 
			
		||||
        # 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
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @throws Exception
 | 
			
		||||
     */
 | 
			
		||||
    public function run(): Expense
 | 
			
		||||
    {
 | 
			
		||||
        $user = auth()->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;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -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",
 | 
			
		||||
 | 
			
		||||
@ -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;
 | 
			
		||||
							
								
								
									
										406
									
								
								resources/views/edocument/xinvoice.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										406
									
								
								resources/views/edocument/xinvoice.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,406 @@
 | 
			
		||||
<html>
 | 
			
		||||
<head>
 | 
			
		||||
    <style>
 | 
			
		||||
        @page {
 | 
			
		||||
            size: 21cm 29cm;
 | 
			
		||||
            margin-left: 2.5cm;
 | 
			
		||||
        }
 | 
			
		||||
        body {
 | 
			
		||||
            font-size: 9pt;
 | 
			
		||||
        }
 | 
			
		||||
        h1 {
 | 
			
		||||
            font-size: 19px;
 | 
			
		||||
        }
 | 
			
		||||
        table {
 | 
			
		||||
            margin: 0;
 | 
			
		||||
            padding: 0;
 | 
			
		||||
            table-layout: fixed;
 | 
			
		||||
        }
 | 
			
		||||
        tr {
 | 
			
		||||
            margin: 0;
 | 
			
		||||
            padding: 0;
 | 
			
		||||
        }
 | 
			
		||||
        th, td {
 | 
			
		||||
            vertical-align: top;
 | 
			
		||||
        }
 | 
			
		||||
        th {
 | 
			
		||||
            margin-left: 0;
 | 
			
		||||
            margin-right: 0;
 | 
			
		||||
            padding-left: 0;
 | 
			
		||||
            padding-right: 0;
 | 
			
		||||
            font-size: 8pt;
 | 
			
		||||
        }
 | 
			
		||||
        td {
 | 
			
		||||
            font-size: 8pt;
 | 
			
		||||
        }
 | 
			
		||||
        table.postable {
 | 
			
		||||
            width: 100%;
 | 
			
		||||
            min-width: 100%;
 | 
			
		||||
            max-width: 100%;
 | 
			
		||||
            margin-top: 5px;
 | 
			
		||||
        }
 | 
			
		||||
        table.postable th {
 | 
			
		||||
            padding-bottom: 10px;
 | 
			
		||||
        }
 | 
			
		||||
        table.postable td.posno,
 | 
			
		||||
        table.postable th.posno {
 | 
			
		||||
            width: 10%;
 | 
			
		||||
            min-width: 10%;
 | 
			
		||||
            max-width: 10%;
 | 
			
		||||
            text-align: left;
 | 
			
		||||
        }
 | 
			
		||||
        table.postable td.posdesc,
 | 
			
		||||
        table.postable th.posdesc {
 | 
			
		||||
            width: 25%;
 | 
			
		||||
            min-width: 25%;
 | 
			
		||||
            max-width: 25%;
 | 
			
		||||
            text-align: left;
 | 
			
		||||
        }
 | 
			
		||||
        table.postable td.posqty,
 | 
			
		||||
        table.postable th.posqty {
 | 
			
		||||
            width: 20%;
 | 
			
		||||
            min-width: 20%;
 | 
			
		||||
            max-width: 20%;
 | 
			
		||||
            text-align: right;
 | 
			
		||||
        }
 | 
			
		||||
        table.postable td.posunitprice,
 | 
			
		||||
        table.postable th.posunitprice {
 | 
			
		||||
            width: 20%;
 | 
			
		||||
            min-width: 20%;
 | 
			
		||||
            max-width: 20%;
 | 
			
		||||
            text-align: right;
 | 
			
		||||
        }
 | 
			
		||||
        table.postable td.poslineamount,
 | 
			
		||||
        table.postable th.poslineamount {
 | 
			
		||||
            width: 20%;
 | 
			
		||||
            min-width: 20%;
 | 
			
		||||
            max-width: 20%;
 | 
			
		||||
            text-align: right;
 | 
			
		||||
        }
 | 
			
		||||
        table.postable td.poslinevat,
 | 
			
		||||
        table.postable th.poslinevat {
 | 
			
		||||
            width: 5%;
 | 
			
		||||
            min-width: 5%;
 | 
			
		||||
            max-width: 5%;
 | 
			
		||||
            text-align: right;
 | 
			
		||||
        }
 | 
			
		||||
        table.postable th.posno {
 | 
			
		||||
            border-bottom: 1px solid #dcdcdc;
 | 
			
		||||
        }
 | 
			
		||||
        table.postable th.posdesc {
 | 
			
		||||
            border-bottom: 1px solid #dcdcdc;
 | 
			
		||||
        }
 | 
			
		||||
        table.postable th.posqty {
 | 
			
		||||
            border-bottom: 1px solid #dcdcdc;
 | 
			
		||||
        }
 | 
			
		||||
        table.postable th.posunitprice {
 | 
			
		||||
            border-bottom: 1px solid #dcdcdc;
 | 
			
		||||
        }
 | 
			
		||||
        table.postable th.poslineamount {
 | 
			
		||||
            border-bottom: 1px solid #dcdcdc;
 | 
			
		||||
        }
 | 
			
		||||
        table.postable th.poslinevat {
 | 
			
		||||
            border-bottom: 1px solid #dcdcdc;
 | 
			
		||||
        }
 | 
			
		||||
        table.postable td.totalname {
 | 
			
		||||
            width: 20%;
 | 
			
		||||
            min-width: 20%;
 | 
			
		||||
            max-width: 20%;
 | 
			
		||||
            text-align: left;
 | 
			
		||||
            border-bottom: 1px solid #dcdcdc;
 | 
			
		||||
        }
 | 
			
		||||
        table.postable td.totalvalue {
 | 
			
		||||
            width: 20%;
 | 
			
		||||
            min-width: 20%;
 | 
			
		||||
            max-width: 20%;
 | 
			
		||||
            text-align: right;
 | 
			
		||||
            border-bottom: 1px solid #dcdcdc;
 | 
			
		||||
        }
 | 
			
		||||
        .space {
 | 
			
		||||
            padding-top: 10px;
 | 
			
		||||
        }
 | 
			
		||||
        .space2 {
 | 
			
		||||
            padding-top: 20px;
 | 
			
		||||
        }
 | 
			
		||||
        .space3 {
 | 
			
		||||
            padding-top: 30px;
 | 
			
		||||
        }
 | 
			
		||||
        .bold {
 | 
			
		||||
            font-weight: bold;
 | 
			
		||||
        }
 | 
			
		||||
        .italic {
 | 
			
		||||
            font-style: italic;
 | 
			
		||||
        }
 | 
			
		||||
        .red {
 | 
			
		||||
            color: #ff0000;
 | 
			
		||||
        }
 | 
			
		||||
        .green {
 | 
			
		||||
            color: #00fff0
 | 
			
		||||
        }
 | 
			
		||||
        .mt-15 {
 | 
			
		||||
            margin-top: 15px;
 | 
			
		||||
        }
 | 
			
		||||
        .mt-20 {
 | 
			
		||||
            margin-top: 20px;
 | 
			
		||||
        }
 | 
			
		||||
        .mt-25 {
 | 
			
		||||
            margin-top: 25px;
 | 
			
		||||
        }
 | 
			
		||||
        .mt-30 {
 | 
			
		||||
            margin-top: 30px;
 | 
			
		||||
        }
 | 
			
		||||
        .pt-15 {
 | 
			
		||||
            padding-top: 15px;
 | 
			
		||||
        }
 | 
			
		||||
        .pt-20 {
 | 
			
		||||
            padding-top: 20px;
 | 
			
		||||
        }
 | 
			
		||||
        .pt-25 {
 | 
			
		||||
            padding-top: 25px;
 | 
			
		||||
        }
 | 
			
		||||
        .pt-30 {
 | 
			
		||||
            padding-top: 30px;
 | 
			
		||||
        }
 | 
			
		||||
        .fs-10 {
 | 
			
		||||
            font-size: 10pt;
 | 
			
		||||
        }
 | 
			
		||||
        .fs-11 {
 | 
			
		||||
            font-size: 11pt;
 | 
			
		||||
        }
 | 
			
		||||
        .fs-12 {
 | 
			
		||||
            font-size: 12pt;
 | 
			
		||||
        }
 | 
			
		||||
        .fs-13 {
 | 
			
		||||
            font-size: 13pt;
 | 
			
		||||
        }
 | 
			
		||||
        .fs-14 {
 | 
			
		||||
            font-size: 14pt;
 | 
			
		||||
        }
 | 
			
		||||
        .pb-0 {
 | 
			
		||||
            padding-bottom: 0px;
 | 
			
		||||
        }
 | 
			
		||||
    </style>
 | 
			
		||||
</head>
 | 
			
		||||
<body>
 | 
			
		||||
<?php
 | 
			
		||||
$document->getDocumentInformation($documentno, $documenttypecode, $documentdate, $invoiceCurrency, $taxCurrency, $documentname, $documentlanguage, $effectiveSpecifiedPeriod);
 | 
			
		||||
$document->getDocumentBuyer($buyername, $buyerids, $buyerdescription);
 | 
			
		||||
$document->getDocumentBuyerAddress($buyeraddressline1, $buyeraddressline2, $buyeraddressline3, $buyerpostcode, $buyercity, $buyercounty, $buyersubdivision);
 | 
			
		||||
?>
 | 
			
		||||
<p>
 | 
			
		||||
    <?php echo $buyername; ?><br>
 | 
			
		||||
    <?php if ($buyeraddressline1) { ?><?php echo $buyeraddressline1; ?><br><?php } ?>
 | 
			
		||||
    <?php if ($buyeraddressline2) { ?><?php echo $buyeraddressline2; ?><br><?php } ?>
 | 
			
		||||
    <?php if ($buyeraddressline3) { ?><?php echo $buyeraddressline3; ?><br><?php } ?>
 | 
			
		||||
    <?php echo $buyercounty . " " . $buyerpostcode . " " . $buyercity; ?><br>
 | 
			
		||||
</p>
 | 
			
		||||
<h1 style="margin: 0; padding: 0; margin-top: 50px">
 | 
			
		||||
    Invoice <?php echo $documentno; ?>
 | 
			
		||||
</h1>
 | 
			
		||||
<p style="margin: 0; padding: 0">
 | 
			
		||||
    Invoice Date <?php echo $documentdate->format("d.m.Y"); ?>
 | 
			
		||||
</p>
 | 
			
		||||
<p style="margin-top: 50px" class="bold">
 | 
			
		||||
    Sehr geehrter Kunde,
 | 
			
		||||
</p>
 | 
			
		||||
<p>
 | 
			
		||||
    wir erlauben uns Ihnen folgende Position in Rechnung zu stellen.
 | 
			
		||||
</p>
 | 
			
		||||
<table class="postable">
 | 
			
		||||
    <thead>
 | 
			
		||||
    <tr>
 | 
			
		||||
        <th class="posno">Pos.</th>
 | 
			
		||||
        <th class="posdesc">Beschreibung</th>
 | 
			
		||||
        <th class="posqty">Stk.</th>
 | 
			
		||||
        <th class="posunitprice">Preis</th>
 | 
			
		||||
        <th class="poslineamount">Menge</th>
 | 
			
		||||
        <th class="poslinevat">MwSt %</th>
 | 
			
		||||
    </tr>
 | 
			
		||||
    </thead>
 | 
			
		||||
    <tbody>
 | 
			
		||||
    <?php
 | 
			
		||||
    if ($document->firstDocumentPosition()) {
 | 
			
		||||
        $isfirstposition = true;
 | 
			
		||||
        do {
 | 
			
		||||
            $document->getDocumentPositionGenerals($lineid, $linestatuscode, $linestatusreasoncode);
 | 
			
		||||
            $document->getDocumentPositionProductDetails($prodname, $proddesc, $prodsellerid, $prodbuyerid, $prodglobalidtype, $prodglobalid);
 | 
			
		||||
            $document->getDocumentPositionGrossPrice($grosspriceamount, $grosspricebasisquantity, $grosspricebasisquantityunitcode);
 | 
			
		||||
            $document->getDocumentPositionNetPrice($netpriceamount, $netpricebasisquantity, $netpricebasisquantityunitcode);
 | 
			
		||||
            $document->getDocumentPositionLineSummation($lineTotalAmount, $totalAllowanceChargeAmount);
 | 
			
		||||
            $document->getDocumentPositionQuantity($billedquantity, $billedquantityunitcode, $chargeFreeQuantity, $chargeFreeQuantityunitcode, $packageQuantity, $packageQuantityunitcode);
 | 
			
		||||
            ?>
 | 
			
		||||
            <?php if ($document->firstDocumentPositionNote()) { ?>
 | 
			
		||||
                <tr>
 | 
			
		||||
                    <td class="<?php echo $isfirstposition ? ' space' : '' ?>"> </td>
 | 
			
		||||
                    <td colspan="5" class="<?php echo $isfirstposition ? ' space' : '' ?>">
 | 
			
		||||
                        <?php $document->getDocumentPositionNote($posnoteContent, $posnoteContentCode, $posnoteSubjectCode); ?>
 | 
			
		||||
                        <?php echo $posnoteContent; ?>
 | 
			
		||||
                        <?php $isfirstposition = false; ?>
 | 
			
		||||
                    </td>
 | 
			
		||||
                </tr>
 | 
			
		||||
            <?php } while ($document->nextDocumentPositionNote()); ?>
 | 
			
		||||
            <tr>
 | 
			
		||||
                <td class="posno<?php echo $isfirstposition ? ' space' : '' ?>"><?php echo $lineid; ?></td>
 | 
			
		||||
                <td class="posdesc<?php echo $isfirstposition ? ' space' : '' ?>"><?php echo $prodname; ?></td>
 | 
			
		||||
                <td class="posqty<?php echo $isfirstposition ? ' space' : '' ?>"><?php echo $billedquantity; ?> <?php echo $billedquantityunitcode ?></td>
 | 
			
		||||
                <td class="posunitprice<?php echo $isfirstposition ? ' space' : '' ?>"><?php echo number_format($netpriceamount, 2); ?> <?php echo $invoiceCurrency; ?></td>
 | 
			
		||||
                <td class="poslineamount<?php echo $isfirstposition ? ' space' : '' ?>"><?php echo number_format($lineTotalAmount, 2); ?> <?php echo $invoiceCurrency; ?></td>
 | 
			
		||||
                <?php if ($document->firstDocumentPositionTax()) { ?>
 | 
			
		||||
                    <?php $document->getDocumentPositionTax($categoryCode, $typeCode, $rateApplicablePercent, $calculatedAmount, $exemptionReason, $exemptionReasonCode); ?>
 | 
			
		||||
                    <td class="poslinevat<?php echo $isfirstposition ? ' space' : '' ?>"><?php echo number_format($rateApplicablePercent, 2); ?> %</td>
 | 
			
		||||
                <?php } else { ?>
 | 
			
		||||
                    <td class="poslinevat<?php echo $isfirstposition ? ' space' : '' ?>"> </td>
 | 
			
		||||
                <?php } ?>
 | 
			
		||||
            </tr>
 | 
			
		||||
            <?php if ($document->firstDocumentPositionGrossPriceAllowanceCharge()) { ?>
 | 
			
		||||
                <?php do { ?>
 | 
			
		||||
                    <?php $document->getDocumentPositionGrossPrice($grossAmount, $grossBasisQuantity, $grossBasisQuantityUnitCode); ?>
 | 
			
		||||
                    <?php $document->getDocumentPositionGrossPriceAllowanceCharge($actualAmount, $isCharge, $calculationPercent, $basisAmount, $reason, $taxTypeCode, $taxCategoryCode, $rateApplicablePercent, $sequence, $basisQuantity, $basisQuantityUnitCode, $reasonCode); ?>
 | 
			
		||||
                    <tr>
 | 
			
		||||
                        <td class="posno"> </td>
 | 
			
		||||
                        <td class="posdesc bold italic"><?php echo ($isCharge ? "Charge" : "Allowance") ?></td>
 | 
			
		||||
                        <td class="posqty"> </td>
 | 
			
		||||
                        <td class="posunitprice italic"><?php echo number_format($actualAmount, 2); ?> (<?php echo number_format($grossAmount, 2); ?>) <?php echo $invoiceCurrency; ?></td>
 | 
			
		||||
                    </tr>
 | 
			
		||||
                <?php } while ($document->nextDocumentPositionGrossPriceAllowanceCharge()); ?>
 | 
			
		||||
            <?php } ?>
 | 
			
		||||
            <?php $isfirstposition = false; ?>
 | 
			
		||||
        <?php } while ($document->nextDocumentPosition()); ?>
 | 
			
		||||
    <?php } ?>
 | 
			
		||||
 | 
			
		||||
    <!--
 | 
			
		||||
        Allowance/Charge
 | 
			
		||||
    -->
 | 
			
		||||
 | 
			
		||||
    <?php if ($document->firstDocumentAllowanceCharge()) { ?>
 | 
			
		||||
        <tr>
 | 
			
		||||
            <td colspan="6"> </td>
 | 
			
		||||
        </tr>
 | 
			
		||||
        <tr>
 | 
			
		||||
            <td colspan="3"> </td>
 | 
			
		||||
            <td colspan="3" class="bold fs-11 space">Allowance/Charge</td>
 | 
			
		||||
        </tr>
 | 
			
		||||
        <?php $isFirstDocumentAllowanceCharge = true; ?>
 | 
			
		||||
        <?php do { ?>
 | 
			
		||||
            <?php $document->getDocumentAllowanceCharge($actualAmount, $isCharge, $taxCategoryCode, $taxTypeCode, $rateApplicablePercent, $sequence, $calculationPercent, $basisAmount, $basisQuantity, $basisQuantityUnitCode, $reasonCode, $reason); ?>
 | 
			
		||||
            <tr>
 | 
			
		||||
                <td class="<?php echo $isFirstDocumentAllowanceCharge ? 'space' : ''; ?>" colspan="3"> </td>
 | 
			
		||||
                <td class="<?php echo $isFirstDocumentAllowanceCharge ? 'space' : ''; ?> totalname"><?php echo $reason ? $reason : ($isCharge ? "Charge" : "Allowance"); ?></td>
 | 
			
		||||
                <td class="<?php echo $isFirstDocumentAllowanceCharge ? 'space' : ''; ?> totalvalue"><?php echo number_format($basisAmount, 2); ?> <?php echo $invoiceCurrency; ?></td>
 | 
			
		||||
                <td class="<?php echo $isFirstDocumentAllowanceCharge ? 'space' : ''; ?> totalvalue bold"><?php echo number_format($actualAmount, 2); ?> <?php echo $invoiceCurrency; ?></td>
 | 
			
		||||
            </tr>
 | 
			
		||||
            <?php $isFirstDocumentAllowanceCharge = false; ?>
 | 
			
		||||
        <?php } while ($document->nextDocumentAllowanceCharge()); ?>
 | 
			
		||||
    <?php } ?>
 | 
			
		||||
 | 
			
		||||
    <!--
 | 
			
		||||
        Summmation
 | 
			
		||||
    -->
 | 
			
		||||
 | 
			
		||||
    <?php $document->getDocumentSummation($grandTotalAmount, $duePayableAmount, $lineTotalAmount, $chargeTotalAmount, $allowanceTotalAmount, $taxBasisTotalAmount, $taxTotalAmount, $roundingAmount, $totalPrepaidAmount); ?>
 | 
			
		||||
    <tr>
 | 
			
		||||
        <td colspan="6"> </td>
 | 
			
		||||
    </tr>
 | 
			
		||||
    <tr>
 | 
			
		||||
        <td colspan="3"> </td>
 | 
			
		||||
        <td colspan="3" class="bold fs-11 space">Summe</td>
 | 
			
		||||
    </tr>
 | 
			
		||||
    <tr>
 | 
			
		||||
        <td class="space" colspan="3"> </td>
 | 
			
		||||
        <td class="space totalname" colspan="2">Nettobetrag</td>
 | 
			
		||||
        <td class="space totalvalue"><?php echo number_format($lineTotalAmount, 2); ?> <?php echo $invoiceCurrency; ?></td>
 | 
			
		||||
    </tr>
 | 
			
		||||
    <?php if($chargeTotalAmount != 0) { ?>
 | 
			
		||||
        <tr>
 | 
			
		||||
            <td class="" colspan="3"> </td>
 | 
			
		||||
            <td class="totalname" colspan="2">Summe Aufschläge</td>
 | 
			
		||||
            <td class="totalvalue"><?php echo number_format($chargeTotalAmount, 2); ?> <?php echo $invoiceCurrency; ?></td>
 | 
			
		||||
        </tr>
 | 
			
		||||
    <?php } ?>
 | 
			
		||||
    <?php if($allowanceTotalAmount != 0) { ?>
 | 
			
		||||
        <tr>
 | 
			
		||||
            <td class="" colspan="3"> </td>
 | 
			
		||||
            <td class="totalname" colspan="2">Summe Rabatte</td>
 | 
			
		||||
            <td class="totalvalue"><?php echo number_format($allowanceTotalAmount, 2); ?> <?php echo $invoiceCurrency; ?></td>
 | 
			
		||||
        </tr>
 | 
			
		||||
    <?php } ?>
 | 
			
		||||
    <tr>
 | 
			
		||||
        <td class="" colspan="3"> </td>
 | 
			
		||||
        <td class="totalname" colspan="2">MwSt.</td>
 | 
			
		||||
        <td class="totalvalue"><?php echo number_format($taxTotalAmount, 2); ?> <?php echo $invoiceCurrency; ?></td>
 | 
			
		||||
    </tr>
 | 
			
		||||
    <tr>
 | 
			
		||||
        <td class="" colspan="3"> </td>
 | 
			
		||||
        <td class="totalname bold" colspan="2">Bruttosumme</td>
 | 
			
		||||
        <td class="totalvalue bold"><?php echo number_format($grandTotalAmount, 2); ?> <?php echo $invoiceCurrency; ?></td>
 | 
			
		||||
    </tr>
 | 
			
		||||
    <tr>
 | 
			
		||||
        <td class="" colspan="3"> </td>
 | 
			
		||||
        <td class="totalname bold" colspan="2">Bereits gezahlt</td>
 | 
			
		||||
        <td class="totalvalue bold"><?php echo number_format($totalPrepaidAmount, 2); ?> <?php echo $invoiceCurrency; ?></td>
 | 
			
		||||
    </tr>
 | 
			
		||||
    <tr>
 | 
			
		||||
        <td class="" colspan="3"> </td>
 | 
			
		||||
        <td class="totalname bold" colspan="2">Zu Zahlen</td>
 | 
			
		||||
        <td class="totalvalue bold"><?php echo number_format($duePayableAmount, 2); ?> <?php echo $invoiceCurrency; ?></td>
 | 
			
		||||
    </tr>
 | 
			
		||||
 | 
			
		||||
    <!--
 | 
			
		||||
        VAT Summation
 | 
			
		||||
    -->
 | 
			
		||||
 | 
			
		||||
    <?php if($document->firstDocumentTax()) { ?>
 | 
			
		||||
        <tr>
 | 
			
		||||
            <td colspan="6"> </td>
 | 
			
		||||
        </tr>
 | 
			
		||||
        <tr>
 | 
			
		||||
            <td colspan="3"> </td>
 | 
			
		||||
            <td colspan="3" class="bold fs-11">VAT Breakdown</td>
 | 
			
		||||
        </tr>
 | 
			
		||||
        <?php $isfirsttax = true ?>
 | 
			
		||||
        <?php $sumbasisamount = 0.0 ?>
 | 
			
		||||
        <?php do { ?>
 | 
			
		||||
            <?php $document->getDocumentTax($categoryCode, $typeCode, $basisAmount, $calculatedAmount, $rateApplicablePercent, $exemptionReason, $exemptionReasonCode, $lineTotalBasisAmount, $allowanceChargeBasisAmount, $taxPointDate, $dueDateTypeCode); ?>
 | 
			
		||||
            <tr>
 | 
			
		||||
                <td class="<?php echo $isfirsttax ? 'space' : '' ?>" colspan="3"> </td>
 | 
			
		||||
                <td class="totalname<?php echo $isfirsttax ? ' space' : '' ?>"><?php echo number_format($rateApplicablePercent, 2); ?>%</td>
 | 
			
		||||
                <td class="totalvalue<?php echo $isfirsttax ? ' space' : '' ?>"><?php echo number_format($basisAmount,2) ?> <?php echo $invoiceCurrency; ?></td>
 | 
			
		||||
                <td class="totalvalue bold<?php echo $isfirsttax ? ' space' : '' ?>"><?php echo number_format($calculatedAmount, 2); ?> <?php echo $invoiceCurrency; ?></td>
 | 
			
		||||
            </tr>
 | 
			
		||||
            <?php $sumbasisamount = $sumbasisamount + $basisAmount ?>
 | 
			
		||||
            <?php $isfirsttax = false ?>
 | 
			
		||||
        <?php } while ($document->nextDocumentTax()); ?>
 | 
			
		||||
        <tr>
 | 
			
		||||
            <td class="" colspan="3"> </td>
 | 
			
		||||
            <td class="totalname">Summe</td>
 | 
			
		||||
            <td class="totalvalue"><?php echo number_format($sumbasisamount, 2); ?> <?php echo $invoiceCurrency; ?></td>
 | 
			
		||||
            <td class="totalvalue bold"><?php echo number_format($taxTotalAmount, 2); ?> <?php echo $invoiceCurrency; ?></td>
 | 
			
		||||
        </tr>
 | 
			
		||||
    <?php } ?>
 | 
			
		||||
 | 
			
		||||
    <!--
 | 
			
		||||
        Paymentterms
 | 
			
		||||
    -->
 | 
			
		||||
 | 
			
		||||
    <?php if ($document->firstDocumentPaymentTerms()) { ?>
 | 
			
		||||
        <?php $isfirstpaymentterm = true ?>
 | 
			
		||||
        <?php do { ?>
 | 
			
		||||
            <tr>
 | 
			
		||||
                <?php $document->getDocumentPaymentTerm($description, $dueDate, $directDebitMandateID); ?>
 | 
			
		||||
                <td colspan="6" class="<?php echo $isfirstpaymentterm ? 'space3' : '' ?>">
 | 
			
		||||
                    <?php echo $description; ?>
 | 
			
		||||
                </td>
 | 
			
		||||
            </tr>
 | 
			
		||||
            <?php $isfirstpaymentterm = false ?>
 | 
			
		||||
        <?php } while ($document->nextDocumentPaymentTerms()); ?>
 | 
			
		||||
    <?php } ?>
 | 
			
		||||
    <tr><td colspan="6" class=""><bold>Hinweise:</bold></td></tr>
 | 
			
		||||
    <?php $document->getDocumentNotes($documentNotes); ?>
 | 
			
		||||
    <?php foreach ($documentNotes as $documentNote) { ?>
 | 
			
		||||
        <tr><td colspan="6" class=""><?php echo trim(nl2br($documentNote['content'])); ?></td></tr>
 | 
			
		||||
    <?php } ?>
 | 
			
		||||
    </tbody>
 | 
			
		||||
</table>
 | 
			
		||||
 | 
			
		||||
</body>
 | 
			
		||||
</html>
 | 
			
		||||
@ -184,10 +184,7 @@ Route::group(['middleware' => ['throttle:api', 'api_db', 'token_auth', 'locale']
 | 
			
		||||
 | 
			
		||||
    Route::post('client_statement', [ClientStatementController::class, 'statement'])->name('client.statement');
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Route::post('companies/purge/{company}', [MigrationController::class, 'purgeCompany'])->middleware('password_protected');
 | 
			
		||||
 | 
			
		||||
    Route::post('companies/current', [CompanyController::class, 'current'])->name('companies.current');
 | 
			
		||||
    Route::post('companies/purge/{company}', [MigrationController::class, 'purgeCompany'])->middleware('password_protected');
 | 
			
		||||
    Route::post('companies/purge_save_settings/{company}', [MigrationController::class, 'purgeCompanySaveSettings'])->middleware('password_protected');
 | 
			
		||||
    Route::resource('companies', CompanyController::class); // name = (companies. index / create / show / update / destroy / edit
 | 
			
		||||
 | 
			
		||||
@ -230,8 +227,8 @@ Route::post('companies/purge/{company}', [MigrationController::class, 'purgeComp
 | 
			
		||||
    Route::resource('expenses', ExpenseController::class); // name = (expenses. index / create / show / update / destroy / edit
 | 
			
		||||
    Route::put('expenses/{expense}/upload', [ExpenseController::class, 'upload']);
 | 
			
		||||
    Route::post('expenses/bulk', [ExpenseController::class, 'bulk'])->name('expenses.bulk');
 | 
			
		||||
 | 
			
		||||
    Route::post('export', [ExportController::class, 'index'])->name('export.index');
 | 
			
		||||
    Route::put('edocument/upload', [ExpenseController::class, "edocument"])->name("expenses.edocument");
 | 
			
		||||
 | 
			
		||||
    Route::resource('expense_categories', ExpenseCategoryController::class); // name = (expense_categories. index / create / show / update / destroy / edit
 | 
			
		||||
    Route::post('expense_categories/bulk', [ExpenseCategoryController::class, 'bulk'])->name('expense_categories.bulk');
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user