mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2024-11-08 20:22:42 +01:00
Working on hydrating XML => UBL => PDF
This commit is contained in:
parent
7402f7c110
commit
b58be6fdd6
@ -37,9 +37,9 @@ class CreateAccountRequest extends Request
|
||||
public function rules()
|
||||
{
|
||||
if (Ninja::isHosted()) {
|
||||
$email_rules = ['bail', 'required', 'email:rfc,dns', new NewUniqueUserRule(), new BlackListRule(), new EmailBlackListRule()];
|
||||
$email_rules = ['bail', 'required', 'max:255', 'email:rfc,dns', new NewUniqueUserRule(), new BlackListRule(), new EmailBlackListRule()];
|
||||
} else {
|
||||
$email_rules = ['bail', 'required', 'email:rfc,dns', new NewUniqueUserRule()];
|
||||
$email_rules = ['bail', 'required', 'max:255', 'email:rfc,dns', new NewUniqueUserRule()];
|
||||
}
|
||||
|
||||
return [
|
||||
|
@ -22,7 +22,7 @@ class SendEmailRequest extends Request
|
||||
{
|
||||
use MakesHash;
|
||||
|
||||
private string $entity_plural = '';
|
||||
private string $entity_plural = 'invoices';
|
||||
private string $error_message = '';
|
||||
|
||||
public array $templates = [
|
||||
@ -92,9 +92,8 @@ class SendEmailRequest extends Request
|
||||
$input['entity_id'] = $this->decodePrimaryKey($input['entity_id']);
|
||||
}
|
||||
|
||||
$this->entity_plural = Str::plural($input['entity']) ?? '';
|
||||
|
||||
if (isset($input['entity']) && in_array($input['entity'], ['invoice','quote','credit','recurring_invoice','purchase_order','payment'])) {
|
||||
$this->entity_plural = Str::plural($input['entity']) ?? '';
|
||||
$input['entity'] = "App\Models\\".ucfirst(Str::camel($input['entity']));
|
||||
}
|
||||
|
||||
@ -106,13 +105,15 @@ class SendEmailRequest extends Request
|
||||
})->slice(0, 4)->toArray();
|
||||
}
|
||||
|
||||
|
||||
$this->replace($input);
|
||||
}
|
||||
|
||||
public function message()
|
||||
public function messages()
|
||||
{
|
||||
return [
|
||||
'template' => 'Invalid template.',
|
||||
'template.in' => 'Template :input is anot a valid template.',
|
||||
'entity.in' => 'Entity :input is not a valid entity.'
|
||||
];
|
||||
}
|
||||
|
||||
@ -134,8 +135,7 @@ class SendEmailRequest extends Request
|
||||
}
|
||||
|
||||
/*Make sure we have all the require ingredients to send a template*/
|
||||
if (isset($input['entity']) && array_key_exists('entity_id', $input) && is_string($input['entity']) && $input['entity_id']) {
|
||||
|
||||
if (isset($input['entity']) && array_key_exists('entity_id', $input) && in_array($input['entity'], ['App\Models\Invoice','App\Models\Quote','App\Models\Credit','App\Models\RecurringInvoice','App\Models\PurchaseOrder','App\Models\Payment'])) {
|
||||
|
||||
$company = $user->company();
|
||||
|
||||
@ -148,11 +148,12 @@ class SendEmailRequest extends Request
|
||||
if ($entity_obj && ($company->id == $entity_obj->company_id) && $user->can('edit', $entity_obj)) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
$this->error_message = "Invalid entity or entity_id";
|
||||
}
|
||||
else {
|
||||
$this->error_message = "Invalid entity or entity_id";
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function failedAuthorization()
|
||||
|
@ -11,16 +11,17 @@
|
||||
|
||||
namespace App\Jobs\EDocument;
|
||||
|
||||
use App\Models\Expense;
|
||||
use App\Services\EDocument\Imports\ParseEDocument;
|
||||
use App\Utils\TempFile;
|
||||
use Exception;
|
||||
use App\Models\Company;
|
||||
use App\Models\Expense;
|
||||
use App\Utils\TempFile;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Contracts\Queue\ShouldBeUnique;
|
||||
use App\Services\EDocument\Imports\ParseEDocument;
|
||||
use Illuminate\Queue\Middleware\WithoutOverlapping;
|
||||
use App\Services\EDocument\Imports\ZugferdEDocument;
|
||||
|
||||
@ -31,8 +32,6 @@ class ImportEDocument implements ShouldQueue
|
||||
use Queueable;
|
||||
use SerializesModels;
|
||||
|
||||
public $tries = 1;
|
||||
|
||||
public function __construct(private readonly string $file_content, private string $file_name, private string $file_mime_type, private Company $company)
|
||||
{
|
||||
|
||||
@ -46,7 +45,6 @@ class ImportEDocument implements ShouldQueue
|
||||
*/
|
||||
public function handle(): Expense
|
||||
{
|
||||
|
||||
$file = TempFile::UploadedFileFromRaw($this->file_content, $this->file_name, $this->file_mime_type);
|
||||
|
||||
return (new ParseEDocument($file, $this->company))->run();
|
||||
@ -63,6 +61,8 @@ class ImportEDocument implements ShouldQueue
|
||||
if ($exception) {
|
||||
nlog("EXCEPTION:: ImportEDocument:: " . $exception->getMessage());
|
||||
}
|
||||
|
||||
$this->fail($exception); //manually fail - prevents future jobs with the same name from being discarded
|
||||
|
||||
config(['queue.failed.driver' => null]);
|
||||
}
|
||||
|
@ -118,6 +118,7 @@ class Vendor extends BaseModel
|
||||
'language_id',
|
||||
'classification',
|
||||
'is_tax_exempt',
|
||||
'routing_id',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
|
@ -41,6 +41,8 @@ class ParseEDocument extends AbstractService
|
||||
*/
|
||||
public function run(): Expense
|
||||
{
|
||||
nlog("starting");
|
||||
nlog($this->company->id);
|
||||
|
||||
/** @var \App\Models\Account $account */
|
||||
$account = $this->company->owner()->account;
|
||||
@ -51,7 +53,6 @@ class ParseEDocument extends AbstractService
|
||||
// ZUGFERD - try to parse via Zugferd lib
|
||||
switch (true) {
|
||||
case ($extension == 'pdf' || $mimetype == 'application/pdf'):
|
||||
case ($extension == 'xml' || $mimetype == 'application/xml') && stristr($this->file->get(), "urn:cen.eu:en16931:2017"):
|
||||
case ($extension == 'xml' || $mimetype == 'application/xml') && stristr($this->file->get(), "urn:cen.eu:en16931:2017#compliant#urn:xeinkauf.de:kosit:xrechnung_3.0"):
|
||||
case ($extension == 'xml' || $mimetype == 'application/xml') && stristr($this->file->get(), "urn:cen.eu:en16931:2017#compliant#urn:xeinkauf.de:kosit:xrechnung_2.1"):
|
||||
case ($extension == 'xml' || $mimetype == 'application/xml') && stristr($this->file->get(), "urn:cen.eu:en16931:2017#compliant#urn:xeinkauf.de:kosit:xrechnung_2.0"):
|
||||
@ -60,6 +61,7 @@ class ParseEDocument extends AbstractService
|
||||
} catch (\Throwable $e) {
|
||||
nlog("Zugferd Exception: " . $e->getMessage());
|
||||
}
|
||||
case ($extension == 'xml' || $mimetype == 'application/xml') && stristr($this->file->get(), "urn:cen.eu:en16931:2017"):
|
||||
case ($extension == 'xml' || $mimetype == 'application/xml') && stristr($this->file->get(), "urn:oasis:names:specification:ubl"):
|
||||
try {
|
||||
return (new UblEDocument($this->file, $this->company))->run();
|
||||
|
134
app/Services/EDocument/Imports/Ubl2Pdf.php
Normal file
134
app/Services/EDocument/Imports/Ubl2Pdf.php
Normal file
@ -0,0 +1,134 @@
|
||||
<?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\Models\Vendor;
|
||||
use App\Models\Company;
|
||||
use App\Models\Country;
|
||||
use App\Models\Expense;
|
||||
use App\Factory\VendorFactory;
|
||||
use App\Factory\ExpenseFactory;
|
||||
use App\Services\AbstractService;
|
||||
use Illuminate\Http\UploadedFile;
|
||||
use InvoiceNinja\EInvoice\EInvoice;
|
||||
use App\Utils\Traits\SavesDocuments;
|
||||
use App\Factory\VendorContactFactory;
|
||||
use App\Models\Currency;
|
||||
use App\Repositories\ExpenseRepository;
|
||||
|
||||
class Ubl2Pdf extends AbstractService
|
||||
{
|
||||
|
||||
/**
|
||||
* @throws \Throwable
|
||||
*/
|
||||
public function __construct(public \InvoiceNinja\EInvoice\Models\Peppol\Invoice $invoice, public Company $company)
|
||||
{
|
||||
}
|
||||
|
||||
public function run()
|
||||
{
|
||||
$client = $this->clientDetails();
|
||||
$supplier = $this->supplierDetails();
|
||||
$invoiceLines = $this->invoiceLines();
|
||||
$totals = $this->totals();
|
||||
}
|
||||
|
||||
private function clientDetails(): array
|
||||
{
|
||||
return [
|
||||
'name' => data_get($this->invoice, 'AccountingCustomerParty.Party.PartyName.0.Name',''),
|
||||
'address1' => data_get($this->invoice, 'AccountingCustomerParty.Party.PostalAddress.StreetName',''),
|
||||
'address2' => data_get($this->invoice, 'AccountingCustomerParty.Party.PostalAddress.AdditionalStreetName',''),
|
||||
'city' => data_get($this->invoice, 'AccountingCustomerParty.Party.PostalAddress.CityName',''),
|
||||
'state' => data_get($this->invoice, 'AccountingSupplierParty.Party.PostalAddress.CountrySubentity',''),
|
||||
'postal_code' => data_get($this->invoice, 'AccountingCustomerParty.Party.PostalAddress.PostalZone',''),
|
||||
'country_id' => data_get($this->invoice, 'AccountingCustomerParty.Party.PostalAddress.Country.IdentificationCode.value',''),
|
||||
'vat_number' => data_get($this->invoice, 'AccountingCustomerParty.Party.PartyTaxScheme.0.CompanyID.value',''),
|
||||
'contacts' => [
|
||||
'first_name' => data_get($this->invoice, 'AccountingCustomerParty.Party.Contact.Name',''),
|
||||
'phone' => data_get($this->invoice, 'AccountingCustomerParty.Party.Contact.Telephone',''),
|
||||
'email' => data_get($this->invoice, 'AccountingCustomerParty.Party.Contact.ElectronicMail',''),
|
||||
],
|
||||
'settings' => [
|
||||
'currency_id' => $this->resolveCurrencyId(data_get($this->invoice, 'DocumentCurrencyCode.value', $this->company->currency()->code))
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
private function supplierDetails(): array
|
||||
{
|
||||
return [
|
||||
'name' => data_get($this->invoice, 'AccountingSupplierParty.Party.PartyName.0.name', ''),
|
||||
'address1' => data_get($this->invoice, 'AccountingSupplierParty.Party.PostalAddress.StreetName', ''),
|
||||
'address2' => data_get($this->invoice, 'AccountingSupplierParty.Party.PostalAddress.AdditionalStreetName', ''),
|
||||
'city' => data_get($this->invoice, 'AccountingSupplierParty.Party.PostalAddress.CityName', ''),
|
||||
'state' => data_get($this->invoice, 'AccountingSupplierParty.Party.PostalAddress.CountrySubentity', ''),
|
||||
'postal_code' => data_get($this->invoice, 'AccountingSupplierParty.Party.PostalAddress.PostalZone', ''),
|
||||
'country_id' => $this->resolveCountry(data_get($this->invoice, 'AccountingSupplierParty.Party.PostalAddress.Country.IdentificationCode.value', '')),
|
||||
'vat_number' => data_get($this->invoice, 'AccountingSupplierParty.Party.PartyTaxScheme.0.CompanyID.value', ''),
|
||||
'contacts' => [
|
||||
'first_name' => data_get($this->invoice, 'AccountingCustomerParty.Party.Contact.Name', ''),
|
||||
'phone' => data_get($this->invoice, 'AccountingCustomerParty.Party.Contact.Telephone', ''),
|
||||
'email' => data_get($this->invoice, 'AccountingCustomerParty.Party.Contact.ElectronicMail', ''),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
private function invoiceLines(): array
|
||||
{
|
||||
$lines = data_get($this->invoice, 'invoiceLine', []);
|
||||
return array_map(function ($line) {
|
||||
return [
|
||||
'quantity' => data_get($line, 'InvoicedQuantity',0),
|
||||
'unit_code' => data_get($line, 'InvoicedQuantity.UnitCode',0),
|
||||
'line_extension_amount' => data_get($line, 'LineExtensionAmount',0),
|
||||
'item' => [
|
||||
'name' => data_get($line, 'Item.Name',''),
|
||||
'description' => data_get($line, 'Item.Description',''),
|
||||
],
|
||||
'price' => data_get($line, 'Price.PriceAmount',0),
|
||||
];
|
||||
}, $lines);
|
||||
}
|
||||
|
||||
private function totals(): array
|
||||
{
|
||||
return [
|
||||
'line_extension_amount' => data_get($this->invoice, 'LegalMonetaryTotal.LineExtensionAmount',0),
|
||||
'tax_exclusive_amount' => data_get($this->invoice, 'LegalMonetaryTotal.TaxExclusiveAmount',0),
|
||||
'tax_inclusive_amount' => data_get($this->invoice, 'LegalMonetaryTotal.TaxInclusiveAmount',0),
|
||||
'charge_total_amount' => data_get($this->invoice, 'LegalMonetaryTotal.ChargeTotalAmount',0),
|
||||
'payable_amount' => data_get($this->invoice, 'LegalMonetaryTotal.PayableAmount',0),
|
||||
];
|
||||
}
|
||||
|
||||
private function resolveCountry(?string $iso_country_code): int
|
||||
{
|
||||
return Country::query()
|
||||
->where('iso_3166_2', $iso_country_code)
|
||||
->orWhere('iso_3166_3', $iso_country_code)
|
||||
->first()?->id ?? (int)$this->company->settings->country_id;
|
||||
}
|
||||
|
||||
|
||||
private function resolveCurrencyId(string $currency_code): int
|
||||
{
|
||||
|
||||
/** @var \Illuminate\Support\Collection<\App\Models\Currency> */
|
||||
$currencies = app('currencies');
|
||||
|
||||
return $currencies->first(function (Currency $c) use ($currency_code) {
|
||||
return $c->code === $currency_code;
|
||||
})?->id ?? (int) $this->company->settings->currency_id;
|
||||
}
|
||||
}
|
@ -14,14 +14,17 @@ namespace App\Services\EDocument\Imports;
|
||||
use App\Models\Vendor;
|
||||
use App\Models\Company;
|
||||
use App\Models\Country;
|
||||
use App\Models\Expense;
|
||||
use App\Factory\VendorFactory;
|
||||
use App\Factory\ExpenseFactory;
|
||||
use App\Factory\VendorContactFactory;
|
||||
use App\Services\AbstractService;
|
||||
use Illuminate\Http\UploadedFile;
|
||||
|
||||
use InvoiceNinja\EInvoice\EInvoice;
|
||||
use App\Utils\Traits\SavesDocuments;
|
||||
use App\Factory\VendorContactFactory;
|
||||
use App\Models\Currency;
|
||||
use App\Repositories\ExpenseRepository;
|
||||
|
||||
class UblEDocument extends AbstractService
|
||||
{
|
||||
@ -41,18 +44,12 @@ class UblEDocument extends AbstractService
|
||||
public function run(): \App\Models\Expense
|
||||
{
|
||||
|
||||
//parse doc
|
||||
// try{
|
||||
$e = new EInvoice();
|
||||
|
||||
$invoice = $e->decode('Peppol', $this->file->get(), 'xml');
|
||||
|
||||
return $this->buildAndSaveExpense($invoice);
|
||||
|
||||
// }
|
||||
// catch(\Throwable $e){
|
||||
// return $e->getMessage();
|
||||
// }
|
||||
|
||||
}
|
||||
|
||||
private function buildAndSaveExpense(\InvoiceNinja\EInvoice\Models\Peppol\Invoice $invoice): Expense
|
||||
@ -60,23 +57,116 @@ class UblEDocument extends AbstractService
|
||||
|
||||
$vendor = $this->findOrCreateVendor($invoice);
|
||||
|
||||
$TaxExclusiveAmount = data_get($invoice, 'LegalMonetaryTotal.TaxExclusiveAmount.amount', 0);
|
||||
$TaxInclusiveAmount = data_get($invoice, 'LegalMonetaryTotal.TaxExclusiveAmount.amount', 0);
|
||||
$ChargeTotalAmount = data_get($invoice, 'LegalMonetaryTotal.ChargeTotalAmount.amount', 0);
|
||||
$PayableAmount = data_get($invoice, 'LegalMonetaryTotal.PayableAmount.amount', 0);
|
||||
$TaxAmount = data_get($invoice, 'TaxTotal.0.TaxAmount.amount', 0);
|
||||
$tax_sub_totals = data_get($invoice, 'TaxTotal.0.TaxSubtotal', []);
|
||||
$currency_code = data_get($invoice, 'DocumentCurrencyCode.value', $this->company->currency()->code);
|
||||
$date = data_get($invoice, 'IssueDate', now()->format('Y-m-d'));
|
||||
|
||||
$payment_means = [];
|
||||
$payment_means[] = data_get($invoice, 'PaymentMeans.0.PaymentMeansCode.name', false);
|
||||
$payment_means[] = data_get($invoice, 'PaymentMeans.0.PaymentID.value', false);
|
||||
$payment_means[] = data_get($invoice, 'PaymentMeans.0.PayeeFinancialAccount.ID.value', false);
|
||||
$payment_means[] = data_get($invoice, 'PaymentMeans.0.PayeeFinancialAccount.Name', false);
|
||||
$payment_means[] = data_get($invoice, 'PaymentMeans.0.PayeeFinancialAccount.FinancialInstitutionBranch.ID.value', false);
|
||||
$payment_means[] = data_get($invoice, 'PaymentTerms.0.Note', false);
|
||||
|
||||
$private_notes = collect($payment_means)
|
||||
->reject(function ($means){
|
||||
return $means === false;
|
||||
})->implode("\n");
|
||||
|
||||
$invoice_items = data_get($invoice, 'InvoiceLine', []);
|
||||
|
||||
$items = [];
|
||||
|
||||
foreach($invoice_items as $key => $item)
|
||||
{
|
||||
$items[$key]['name'] = data_get($item, 'Item.Name', false);
|
||||
$items[$key]['description'] = data_get($item, 'Item.Description', false);
|
||||
}
|
||||
|
||||
$public_notes = collect($items)->reject(function ($item){
|
||||
return $item['name'] === false && $item['description'] === false;
|
||||
})->map(function ($item){
|
||||
return $item['name'] ?? ' ' . ' ## '. $item['description'] ?? ' '; //@phpstan-ignore-line
|
||||
})->implode("\n");
|
||||
|
||||
/** @var \App\Models\Expense $expense */
|
||||
$expense = ExpenseFactory::create($this->company->id, $this->company->owner()->id);
|
||||
$expense->vendor_id = $vendor->id;
|
||||
$expense->amount = $this->company->expense_inclusive_taxes ? $TaxInclusiveAmount : $TaxExclusiveAmount;
|
||||
$expense->currency_id = $this->resolveCurrencyId($currency_code);
|
||||
$expense->date = $date;
|
||||
$expense->private_notes = $private_notes;
|
||||
$expense->public_notes = $public_notes;
|
||||
|
||||
$tax_level = 1;
|
||||
|
||||
foreach ($tax_sub_totals as $key => $sub_tax) {
|
||||
$amount = data_get($sub_tax, 'TaxAmount.amount', 0);
|
||||
$taxable_amount = data_get($sub_tax, 'TaxableAmount.amount', 0);
|
||||
$tax_rate = data_get($sub_tax, 'TaxCategory.Percent', 0);
|
||||
$id = data_get($sub_tax, 'TaxCategory.ID.value', '');
|
||||
$tax_name = data_get($sub_tax, 'TaxCategory.TaxScheme.ID.value', '');
|
||||
|
||||
if (!$this->company->calculate_expense_tax_by_amount && $tax_rate > 0) {
|
||||
|
||||
$expense->{"tax_rate{$tax_level}"} = $tax_rate;
|
||||
$expense->{"tax_name{$tax_level}"} = $tax_name;
|
||||
$expense->calculate_tax_by_amount = false;
|
||||
|
||||
}
|
||||
else {
|
||||
$expense->calculate_tax_by_amount = true;
|
||||
$expense->{"tax_amount{$tax_level}"} = $amount; //@phpstan-ignore-line
|
||||
|
||||
}
|
||||
|
||||
$tax_level++;
|
||||
|
||||
if ($tax_level == 4) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$expense->save();
|
||||
|
||||
$repo = new ExpenseRepository();
|
||||
|
||||
$data = [];
|
||||
$data['documents'][] = $this->file;
|
||||
|
||||
$expense = $repo->save($data, $expense);
|
||||
|
||||
return $expense;
|
||||
|
||||
|
||||
}
|
||||
|
||||
private function resolveCurrencyId(string $currency_code): int
|
||||
{
|
||||
|
||||
/** @var \Illuminate\Support\Collection<\App\Models\Currency> */
|
||||
$currencies = app('currencies');
|
||||
|
||||
return $currencies->first(function (Currency $c) use ($currency_code) {
|
||||
return $c->code === $currency_code;
|
||||
})?->id ?? (int) $this->company->settings->currency_id;
|
||||
}
|
||||
|
||||
private function findOrCreateVendor(\InvoiceNinja\EInvoice\Models\Peppol\Invoice $invoice): Vendor
|
||||
{
|
||||
$asp = $invoice->AccountingSupplierParty;
|
||||
|
||||
$vat_number = $asp->Party->PartyTaxScheme->CompanyID ?? false;
|
||||
$id_number = $asp->Party->PartyIdentification->ID ?? false;
|
||||
$routing_id = $asp->Party->EndpointID ?? false;
|
||||
$vendor_name = $asp->Party->PartyName->Name ?? false;
|
||||
$vat_number = $this->resolveVendorVat($invoice);
|
||||
$id_number = $this->resolveVendorIdNumber($invoice);
|
||||
$routing_id = data_get($invoice, 'AccountingSupplierParty.Party.EndpointID.value', false);
|
||||
$vendor_name = $this->resolveSupplierName($invoice);
|
||||
|
||||
$vendor = Vendor::query()
|
||||
->where('company_id', $this->company->id)
|
||||
@ -85,13 +175,13 @@ class UblEDocument extends AbstractService
|
||||
$q->when($routing_id, function ($q) use ($routing_id){
|
||||
$q->where('routing_id', $routing_id);
|
||||
})
|
||||
->when($vat_number, function ($q) use ($vat_number){
|
||||
->when(strlen($vat_number) > 1, function ($q) use ($vat_number){
|
||||
$q->orWhere('vat_number', $vat_number);
|
||||
})
|
||||
->when($id_number, function ($q) use ($id_number){
|
||||
->when(strlen($id_number) > 1, function ($q) use ($id_number){
|
||||
$q->orWhere('id_number', $id_number);
|
||||
})
|
||||
->when($vendor_name, function ($q) use ($vendor_name){
|
||||
->when(strlen($vendor_name) > 1, function ($q) use ($vendor_name){
|
||||
$q->orWhere('name', $vendor_name);
|
||||
});
|
||||
|
||||
@ -100,24 +190,62 @@ class UblEDocument extends AbstractService
|
||||
return $vendor ?? $this->newVendor($invoice);
|
||||
}
|
||||
|
||||
private function resolveSupplierName(\InvoiceNinja\EInvoice\Models\Peppol\Invoice $invoice): string
|
||||
{
|
||||
if(data_get($invoice, 'AccountingSupplierParty.Party.PartyName', false)){
|
||||
$party_name = data_get($invoice, 'AccountingSupplierParty.Party.PartyName', false);
|
||||
return data_get($party_name[0], 'Name', '');
|
||||
}
|
||||
|
||||
if (data_get($invoice, 'AccountingSupplierParty.Party.PartyLegalEntity', false)) {
|
||||
$ple = data_get($invoice, 'AccountingSupplierParty.Party.PartyName', false);
|
||||
return data_get($ple[0], 'RegistrationName', '');
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
private function resolveVendorIdNumber(\InvoiceNinja\EInvoice\Models\Peppol\Invoice $invoice):string
|
||||
{
|
||||
|
||||
$pts = data_get($invoice, 'AccountingSupplierParty.Party.PartyIdentification', false);
|
||||
|
||||
return $pts ? data_get($pts[0], 'ID.value', '') : '';
|
||||
|
||||
}
|
||||
|
||||
private function resolveVendorVat(\InvoiceNinja\EInvoice\Models\Peppol\Invoice $invoice):string
|
||||
{
|
||||
|
||||
$pts = data_get($invoice, 'AccountingSupplierParty.Party.PartyTaxScheme', false);
|
||||
|
||||
return $pts ? data_get($pts[0], 'CompanyID.value', '') : '';
|
||||
|
||||
}
|
||||
|
||||
private function newVendor(\InvoiceNinja\EInvoice\Models\Peppol\Invoice $invoice): Vendor
|
||||
{
|
||||
$vendor = VendorFactory::create($this->company->id, $this->company->owner()->id);
|
||||
|
||||
|
||||
$data = [
|
||||
'name' => data_get($invoice, 'AccountingSupplierParty.Party.PartyName.Name', ''),
|
||||
'routing_id' => data_get($invoice, 'AccountingSupplierParty.Party.EndpointID', ''),
|
||||
'vat_number' => data_get($invoice, 'AccountingSupplierParty.Party.PartyTaxScheme.CompanyID', ''),
|
||||
'name' => $this->resolveSupplierName($invoice),
|
||||
'routing_id' => data_get($invoice, 'AccountingSupplierParty.Party.EndpointID.value', ''),
|
||||
'vat_number' => $this->resolveVendorVat($invoice),
|
||||
'id_number' => $this->resolveVendorIdNumber($invoice),
|
||||
'address1' => data_get($invoice, 'AccountingSupplierParty.Party.PostalAddress.StreetName',''),
|
||||
'address2' => data_get($invoice, 'AccountingSupplierParty.Party.PostalAddress.AdditionalStreetName',''),
|
||||
'city' => data_get($invoice, 'AccountingSupplierParty.Party.PostalAddress.CityName',''),
|
||||
'state' => data_get($invoice, 'AccountingSupplierParty.Party.PostalAddress.CountrySubentity',''),
|
||||
'postal_code' => data_get($invoice, 'AccountingSupplierParty.Party.PostalAddress.PostalZone',''),
|
||||
'country_id' => $this->resovelCountry(data_get($invoice, 'AccountingSupplierParty.Party.PostalAddress.Country.IdentificationCode','')),
|
||||
'country_id' => $this->resolveCountry(data_get($invoice, 'AccountingSupplierParty.Party.PostalAddress.Country.IdentificationCode.value','')),
|
||||
'currency_id' => $this->resolveCurrencyId(data_get($invoice, 'DocumentCurrencyCode.value', $this->company->currency()->code)),
|
||||
];
|
||||
|
||||
$vendor->fill($data);
|
||||
$vendor->save();
|
||||
|
||||
$vendor->service()->applyNumber();
|
||||
|
||||
if(data_get($invoice, 'AccountingSupplierParty.Party.Contact',false))
|
||||
{
|
||||
@ -138,6 +266,6 @@ class UblEDocument extends AbstractService
|
||||
return Country::query()
|
||||
->where('iso_3166_2', $iso_country_code)
|
||||
->orWhere('iso_3166_3', $iso_country_code)
|
||||
->first() ?? (int)$this->company->settings->country_id;
|
||||
->first()?->id ?? (int)$this->company->settings->country_id;
|
||||
}
|
||||
}
|
@ -124,6 +124,8 @@ class Statics
|
||||
|
||||
$data['bulk_updates'] = [
|
||||
'client' => \App\Models\Client::$bulk_update_columns,
|
||||
'expense' => \App\Models\Expense::$bulk_update_columns,
|
||||
'recurring_invoice' => \App\Models\RecurringInvoice::$bulk_update_columns,
|
||||
];
|
||||
|
||||
return $data;
|
||||
|
@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('vendors', function (Blueprint $table) {
|
||||
if (!Schema::hasColumn('vendors', 'routing_id')) {
|
||||
$table->string('routing_id')->nullable();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
}
|
||||
};
|
@ -57,6 +57,27 @@ class InvoiceEmailTest extends TestCase
|
||||
$this->assertTrue(strpos($email, '@example.com') !== false);
|
||||
}
|
||||
|
||||
public function testEntityValidation()
|
||||
{
|
||||
$data = [
|
||||
"body" => "hey what's up",
|
||||
"entity" => 'blergen',
|
||||
"entity_id" => $this->invoice->hashed_id,
|
||||
"subject" => 'Reminder $number',
|
||||
"template" => "email_template_invoice"
|
||||
];
|
||||
|
||||
$response = false;
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->postJson('/api/v1/emails', $data);
|
||||
|
||||
$response->assertStatus(422);
|
||||
|
||||
}
|
||||
|
||||
|
||||
public function testClientEmailHistory()
|
||||
{
|
||||
|
@ -12,7 +12,10 @@
|
||||
namespace Tests\Integration\Einvoice;
|
||||
|
||||
use Tests\TestCase;
|
||||
use App\Utils\TempFile;
|
||||
use Tests\MockAccountData;
|
||||
use InvoiceNinja\EInvoice\EInvoice;
|
||||
use App\Services\EDocument\Imports\UblEDocument;
|
||||
|
||||
|
||||
/**
|
||||
@ -20,9 +23,27 @@ use InvoiceNinja\EInvoice\EInvoice;
|
||||
*/
|
||||
class ImportEInvoiceTest extends TestCase
|
||||
{
|
||||
|
||||
use MockAccountData;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->makeTestData();
|
||||
|
||||
}
|
||||
|
||||
public function testImportExpenseEinvoice()
|
||||
{
|
||||
$file = file_get_contents(base_path('tests/Integration/Einvoice/samples/peppol.xml'));
|
||||
|
||||
$file = TempFile::UploadedFileFromRaw($file, 'peppol.xml', 'xml');
|
||||
|
||||
$expense = (new UblEDocument($file, $this->company))->run();
|
||||
|
||||
$this->assertNotNull($expense);
|
||||
|
||||
}
|
||||
|
||||
public function testParsingDocument()
|
||||
@ -45,57 +66,76 @@ class ImportEInvoiceTest extends TestCase
|
||||
$this->assertInstanceOf(\InvoiceNinja\EInvoice\Models\Peppol\Invoice::class, $invoice);
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
// public function testHtmlConversion()
|
||||
// {
|
||||
|
||||
public function testHtmlConversion()
|
||||
{
|
||||
|
||||
// // Load the XML source
|
||||
// $xml = new \DOMDocument();
|
||||
// $xml->load(base_path('tests/Integration/Einvoice/samples/peppol.xml'));
|
||||
// Load the XML source
|
||||
$xml = new \DOMDocument();
|
||||
$xml->load(base_path('tests/Integration/Einvoice/samples/peppol.xml'));
|
||||
|
||||
// // Load XSLT stylesheet
|
||||
// $xsl = new \DOMDocument();
|
||||
// $xsl->load(base_path('tests/Integration/Einvoice/samples/peppol.xslt'));
|
||||
// Load XSLT stylesheet
|
||||
$xsl = new \DOMDocument();
|
||||
$xsl->load(base_path('tests/Integration/Einvoice/samples/peppol.xslt'));
|
||||
|
||||
// // Configure the transformer
|
||||
// $proc = new \XSLTProcessor();
|
||||
// $proc->importStyleSheet($xsl); // attach the xsl rules
|
||||
// Configure the transformer
|
||||
$proc = new \XSLTProcessor();
|
||||
$proc->importStyleSheet($xsl); // attach the xsl rules
|
||||
|
||||
// $transformed = $proc->transformToXML($xml);
|
||||
|
||||
// // determining if output is html document
|
||||
// $html = $transformed;
|
||||
|
||||
// // splitting up html document at doctype and doc
|
||||
// $html_array = explode("\n", $html, 15);
|
||||
|
||||
// $html_doc = array_pop($html_array);
|
||||
|
||||
// $html_doctype = implode("\n", $html_array);
|
||||
|
||||
// // convert XHTML syntax to HTML5
|
||||
// // <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||
// // <!DOCTYPE html>
|
||||
// $proc->setParameter('', 'mode', 'document');
|
||||
|
||||
|
||||
// $html_doctype = preg_replace("/<!DOCTYPE [^>]+>/", "<!DOCTYPE html>", $html_doctype);
|
||||
|
||||
// // <html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
|
||||
// // <html lang="en">
|
||||
// libxml_use_internal_errors(true);
|
||||
// // ... your existing code ...
|
||||
|
||||
// $transformed = $proc->transformToXML($xml);
|
||||
// if ($transformed === false) {
|
||||
// foreach (libxml_get_errors() as $error) {
|
||||
// nlog($error->message);
|
||||
// }
|
||||
// } else {
|
||||
// nlog($transformed);
|
||||
// }
|
||||
// libxml_clear_errors();
|
||||
|
||||
|
||||
// $html_doctype = preg_replace('/ xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\"| xml:lang="[^\"]*\"/', '', $html_doctype);
|
||||
|
||||
// nlog($transformed);
|
||||
|
||||
// // determining if output is html document
|
||||
// $html = $transformed;
|
||||
|
||||
// // splitting up html document at doctype and doc
|
||||
// $html_array = explode("\n", $html, 15);
|
||||
|
||||
// $html_doc = array_pop($html_array);
|
||||
|
||||
// $html_doctype = implode("\n", $html_array);
|
||||
|
||||
// // convert XHTML syntax to HTML5
|
||||
// // <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||
// // <!DOCTYPE html>
|
||||
|
||||
|
||||
// // <meta http-equiv="content-type" content="text/html; charset=utf-8" />
|
||||
// // to this --> <meta charset="utf-8" />
|
||||
// $html_doctype = preg_replace("/<!DOCTYPE [^>]+>/", "<!DOCTYPE html>", $html_doctype);
|
||||
|
||||
// $html_doctype = preg_replace('/<meta http-equiv=\"Content-Type\" content=\"text\/html; charset=(.*[a-z0-9-])\" \/>/i', '<meta charset="\1" />', $html_doctype);
|
||||
// // <html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
|
||||
// // <html lang="en">
|
||||
|
||||
// $html = $html_doctype . "\n" . $html_doc;
|
||||
// nlog($html);
|
||||
|
||||
// }
|
||||
// $html_doctype = preg_replace('/ xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\"| xml:lang="[^\"]*\"/', '', $html_doctype);
|
||||
|
||||
|
||||
// // <meta http-equiv="content-type" content="text/html; charset=utf-8" />
|
||||
// // to this --> <meta charset="utf-8" />
|
||||
|
||||
// $html_doctype = preg_replace('/<meta http-equiv=\"Content-Type\" content=\"text\/html; charset=(.*[a-z0-9-])\" \/>/i', '<meta charset="\1" />', $html_doctype);
|
||||
|
||||
// $html = $html_doctype . "\n" . $html_doc;
|
||||
// nlog($html);
|
||||
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user