1
0
mirror of https://github.com/invoiceninja/invoiceninja.git synced 2024-11-12 14:12:44 +01:00

handle missing required keys for Invoice2Go Imports

This commit is contained in:
David Bomba 2023-01-12 11:08:32 +11:00
parent 077986d59c
commit 4979109d97
11 changed files with 416 additions and 8 deletions

View File

@ -20,7 +20,7 @@ use App\Http\Requests\Quote\StoreQuoteRequest;
use App\Import\ImportException;
use App\Jobs\Mail\NinjaMailerJob;
use App\Jobs\Mail\NinjaMailerObject;
use App\Mail\Import\ImportCompleted;
use App\Mail\Import\CsvImportCompleted;
use App\Models\Company;
use App\Models\Invoice;
use App\Models\Quote;
@ -187,6 +187,10 @@ class BaseImport
try {
$entity = $this->transformer->transform($record);
if(!$entity)
continue;
$validator = $this->runValidation($entity);
if ($validator->fails()) {
@ -282,6 +286,8 @@ class BaseImport
public function ingestInvoices($invoices, $invoice_number_key)
{
$count = 0;
$invoice_transformer = $this->transformer;
/** @var PaymentRepository $payment_repository */
@ -343,6 +349,7 @@ class BaseImport
}
$invoice_repository->save($invoice_data, $invoice);
$count++;
// If we're doing a generic CSV import, only import payment data if we're not importing a payment CSV.
// If we're doing a platform-specific import, trust the platform to only return payment info if there's not a separate payment CSV.
if (
@ -404,6 +411,9 @@ class BaseImport
];
}
}
return $count;
}
private function actionInvoiceStatus(
@ -475,6 +485,8 @@ class BaseImport
public function ingestQuotes($quotes, $quote_number_key)
{
$count = 0;
$quote_transformer = $this->transformer;
/** @var ClientRepository $client_repository */
@ -532,6 +544,8 @@ class BaseImport
$quote->status_id = $quote_data['status_id'];
}
$quote_repository->save($quote_data, $quote);
$count++;
$this->actionQuoteStatus(
$quote,
@ -552,7 +566,11 @@ class BaseImport
'error' => $message,
];
}
}
return $count;
}
protected function getUserIDForRecord($record)
@ -586,10 +604,11 @@ class BaseImport
$data = [
'errors' => $this->error_array,
'company' => $this->company,
'entity_count' => $this->entity_count
];
$nmo = new NinjaMailerObject;
$nmo->mailable = new ImportCompleted($this->company, $data);
$nmo->mailable = new CsvImportCompleted($this->company, $data);
$nmo->company = $this->company;
$nmo->settings = $this->company->settings;
$nmo->to_user = $this->company->owner();

View File

@ -37,7 +37,7 @@ use App\Import\Transformer\Csv\PaymentTransformer;
use App\Import\Transformer\Csv\ProductTransformer;
use App\Import\Transformer\Csv\QuoteTransformer;
use App\Import\Transformer\Csv\VendorTransformer;
use App\Import\Transformers\Bank\BankTransformer;
use App\Import\Transformer\Bank\BankTransformer;
use App\Repositories\BankTransactionRepository;
use App\Repositories\ClientRepository;
use App\Repositories\ExpenseRepository;

View File

@ -66,7 +66,6 @@ class Wave extends BaseImport implements ImportInterface
if (empty($data)) {
$this->entity_count['clients'] = 0;
return;
}
@ -170,11 +169,16 @@ class Wave extends BaseImport implements ImportInterface
$entity_type = 'expense';
$data = $this->getCsvData($entity_type);
if(!$data){
$this->entity_count['expense'] = 0;
return;
}
$data = $this->preTransform($data, $entity_type);
if (empty($data)) {
$this->entity_count['expense'] = 0;
return;
}
@ -212,6 +216,8 @@ class Wave extends BaseImport implements ImportInterface
public function ingestExpenses($data)
{
$count = 0;
$key = 'Transaction ID';
$expense_transformer = $this->transformer;
@ -255,6 +261,7 @@ class Wave extends BaseImport implements ImportInterface
);
$expense_repository->save($expense_data, $expense);
$count++;
}
} catch (\Exception $ex) {
if ($ex instanceof ImportException) {
@ -270,5 +277,8 @@ class Wave extends BaseImport implements ImportInterface
];
}
}
return $count;
}
}

View File

@ -0,0 +1,81 @@
<?php
/**
* client Ninja (https://clientninja.com).
*
* @link https://github.com/clientninja/clientninja source repository
*
* @copyright Copyright (c) 2022. client Ninja LLC (https://clientninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Import\Transformer\Bank;
use App\Import\ImportException;
use App\Import\Transformer\BaseTransformer;
use App\Models\BankTransaction;
use App\Utils\Number;
/**
* Class BankTransformer.
*/
class BankTransformer extends BaseTransformer
{
/**
* @param $line_items_data
*
* @return bool|array
*/
public function transform($transaction)
{
$now = now();
$transformed = [
'bank_integration_id' => $transaction['transaction.bank_integration_id'],
'transaction_id' => $this->getNumber($transaction,'transaction.transaction_id'),
'amount' => abs($this->getFloat($transaction, 'transaction.amount')),
'currency_id' => $this->getCurrencyByCode($transaction, 'transaction.currency'),
'account_type' => strlen($this->getString($transaction, 'transaction.account_type')) > 1 ? $this->getString($transaction, 'transaction.account_type') : 'bank',
'category_id' => $this->getNumber($transaction, 'transaction.category_id') > 0 ? $this->getNumber($transaction, 'transaction.category_id') : null,
'category_type' => $this->getString($transaction, 'transaction.category_type'),
'date' => array_key_exists('transaction.date', $transaction) ? $this->parseDate($transaction['transaction.date'])
: now()->format('Y-m-d'),
'bank_account_id' => array_key_exists('transaction.bank_account_id', $transaction) ? $transaction['transaction.bank_account_id'] : 0,
'description' => array_key_exists('transaction.description', $transaction) ? $transaction['transaction.description'] : '',
'base_type' => $this->calculateType($transaction),
'created_at' => $now,
'updated_at' => $now,
'company_id' => $this->company->id,
'user_id' => $this->company->owner()->id,
];
return $transformed;
}
private function calculateType($transaction)
{
if(array_key_exists('transaction.base_type', $transaction) && (($transaction['transaction.base_type'] == 'CREDIT') || strtolower($transaction['transaction.base_type']) == 'deposit'))
return 'CREDIT';
if(array_key_exists('transaction.base_type', $transaction) && (($transaction['transaction.base_type'] == 'DEBIT') || strtolower($transaction['transaction.base_type']) == 'withdrawal'))
return 'DEBIT';
if(array_key_exists('transaction.category_id', $transaction))
return 'DEBIT';
if(array_key_exists('transaction.category_type', $transaction) && $transaction['transaction.category_type'] == 'Income')
return 'CREDIT';
if(array_key_exists('transaction.category_type', $transaction))
return 'DEBIT';
if(array_key_exists('transaction.amount', $transaction) && is_numeric($transaction['transaction.amount']) && $transaction['transaction.amount'] > 0)
return 'CREDIT';
return 'DEBIT';
}
}

View File

@ -28,6 +28,11 @@ class InvoiceTransformer extends BaseTransformer
*/
public function transform($invoice_data)
{
if (!isset($invoice_data['DocumentNumber'])) {
throw new ImportException('DocumentNumber key not found in this import file.');
}
if ($this->hasInvoice($invoice_data['DocumentNumber'])) {
throw new ImportException('Invoice number already exists');
}

View File

@ -28,7 +28,8 @@ class ClientTransformer extends BaseTransformer
public function transform($data)
{
if (isset($data['customer_name']) && $this->hasClient($data['customer_name'])) {
throw new ImportException('Client already exists');
return false;
// throw new ImportException('Client already exists');
}
$settings = new \stdClass;

View File

@ -23,7 +23,6 @@ use App\Libraries\MultiDB;
use App\Models\Client;
use App\Models\Company;
use App\Models\Vendor;
use App\Utils\Ninja;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;

View File

@ -0,0 +1,93 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Mail\Import;
use App\Models\Company;
use App\Utils\Ninja;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\App;
class CsvImportCompleted extends Mailable
{
// use Queueable, SerializesModels;
/** @var Company */
public $company;
/**
* @var array $data Array containing the necessary params.
*
* $data = [
* 'errors' => (array) $errors,
* 'company' => Company $company,
* 'entity_count' => (array) $entity_count
* ];
*/
public $data;
/**
* Create a new message instance.
*
* @return void
*/
public function __construct(Company $company, $data)
{
$this->company = $company;
$this->data = $data;
}
/**
* Build the message.
*
* @return $this
*/
public function build()
{
App::forgetInstance('translator');
App::setLocale($this->company->getLocale());
$t = app('translator');
$t->replace(Ninja::transformTranslations($this->company->settings));
$data = array_merge($this->data, [
'logo' => $this->company->present()->logo(),
'settings' => $this->company->settings,
'company' => $this->company,
'client_count' => isset($this->data['entity_count']['clients']) ? $this->data['entity_count']['clients'] : false,
'product_count' => isset($this->data['entity_count']['products']) ? $this->data['entity_count']['products'] : false,
'invoice_count' => isset($this->data['entity_count']['invoices']) ? $this->data['entity_count']['invoices'] : false,
'quote_count' => isset($this->data['entity_count']['quotes']) ? $this->data['entity_count']['quotes'] : false,
'credit_count' => isset($this->data['entity_count']['credits']) ? $this->data['entity_count']['credits'] : false,
'project_count' => isset($this->data['entity_count']['projects']) ? $this->data['entity_count']['projects'] : false,
'task_count' => isset($this->data['entity_count']['tasks']) ? $this->data['entity_count']['tasks'] : false,
'vendor_count' => isset($this->data['entity_count']['vendors']) ? $this->data['entity_count']['vendors'] : false,
'payment_count' => isset($this->data['entity_count']['payments']) ? $this->data['entity_count']['payments'] : false,
'recurring_invoice_count' => isset($this->data['entity_count']['recurring_invoices']) ? $this->data['entity_count']['recurring_invoices'] : false,
'expense_count' => isset($this->data['entity_count']['expenses']) ? $this->data['entity_count']['expenses'] : false,
'company_gateway_count' => isset($this->data['entity_count']['company_gateways']) ? $this->data['entity_count']['company_gateways'] : false,
'client_gateway_token_count' => isset($this->data['entity_count']['client_gateway_tokens']) ? $this->data['entity_count']['client_gateway_tokens'] : false,
'tax_rate_count' => isset($this->data['entity_count']['tax_rates']) ? $this->data['entity_count']['tax_rates'] : false,
'document_count' => isset($this->data['entity_count']['documents']) ? $this->data['entity_count']['documents'] : false,
'transaction_count' => isset($this->data['entity_count']['transactions']) ? $this->data['entity_count']['transactions'] : false,
]);
return $this
->subject(ctrans('texts.import_completed'))
->from(config('mail.from.address'), config('mail.from.name'))
->text('email.import.csv_completed_text')
->view('email.import.csv_completed', $data);
}
}

View File

@ -4923,7 +4923,7 @@ $LANG = array(
'matomo_id' => 'Matomo Id',
'action_add_to_invoice' => 'Add To Invoice',
'danger_zone' => 'Danger Zone',
'import_completed' => 'Import completed',
);

View File

@ -0,0 +1,102 @@
@component('email.template.admin', ['logo' => $logo, 'settings' => $settings, 'company' => $company ?? ''])
<div class="center">
<h1>{{ ctrans('texts.import_complete') }}</h1>
<p><img src="{{ $logo }}"></p>
@if($client_count)
<p><b>{{ ctrans('texts.clients') }}:</b> {{ $client_count }} </p>
@endif
@if($product_count)
<p><b>{{ ctrans('texts.products') }}:</b> {{ $product_count }} </p>
@endif
@if($invoice_count)
<p><b>{{ ctrans('texts.invoices') }}:</b> {{ $invoice_count }} </p>
@endif
@if($payment_count)
<p><b>{{ ctrans('texts.payments') }}:</b> {{ $payment_count }} </p>
@endif
@if($recurring_invoice_count)
<p><b>{{ ctrans('texts.recurring_invoices') }}:</b> {{ $recurring_invoice_count }} </p>
@endif
@if($quote_count)
<p><b>{{ ctrans('texts.quotes') }}:</b> {{ $quote_count }} </p>
@endif
@if($credit_count)
<p><b>{{ ctrans('texts.credits') }}:</b> {{ $credit_count }} </p>
@endif
@if($project_count)
<p><b>{{ ctrans('texts.projects') }}:</b> {{ $project_count }} </p>
@endif
@if($task_count)
<p><b>{{ ctrans('texts.tasks') }}:</b> {{ $task_count }} </p>
@endif
@if($vendor_count)
<p><b>{{ ctrans('texts.vendors') }}:</b> {{ $vendor_count }} </p>
@endif
@if($expense_count)
<p><b>{{ ctrans('texts.expenses') }}:</b> {{ $expense_count }} </p>
@endif
@if($company_gateway_count)
<p><b>{{ ctrans('texts.gateways') }}:</b> {{ $company_gateway_count }} </p>
@endif
@if($client_gateway_token_count)
<p><b>{{ ctrans('texts.tokens') }}:</b> {{ $client_gateway_token_count }} </p>
@endif
@if($tax_rate_count)
<p><b>{{ ctrans('texts.tax_rates') }}:</b> {{ $tax_rate_count }} </p>
@endif
@if($document_count)
<p><b>{{ ctrans('texts.documents') }}:</b> {{ $document_count }} </p>
@endif
@if($transaction_count)
<p><b>{{ ctrans('texts.documents') }}:</b> {{ $transaction_count }} </p>
@endif
@if(!empty($errors) )
<p>{{ ctrans('texts.failed_to_import') }}</p>
<p>{{ ctrans('texts.error') }}:</p>
<table>
<thead>
<tr>
<th>Type</th>
<th>Data</th>
<th>Error</th>
</tr>
</thead>
<tbody>
@foreach($errors as $entityType=>$entityErrors)
@foreach($entityErrors as $error)
<tr>
<td>{{$entityType}}</td>
<td>{{json_encode($error[$entityType]??null)}}</td>
<td>{{json_encode($error['error'])}}</td>
</tr>
@endforeach
@endforeach
</tbody>
</table>
@endif
<a href="{{ url('/') }}" target="_blank" class="button">{{ ctrans('texts.account_login')}}</a>
<p>{{ ctrans('texts.email_signature')}}</p>
<p>{{ ctrans('texts.email_from') }}</p>
</div>
@endcomponent

View File

@ -0,0 +1,98 @@
{{ ctrans('texts.import_complete') }}
@if($client_count)
{{ ctrans('texts.clients') }}: {{ $client_count }}
@endif
@if($product_count)
{{ ctrans('texts.products') }}: {{ $product_count }}
@endif
@if($invoice_count)
{{ ctrans('texts.invoices') }}: {{ $invoice_count }}
@endif
@if($payment_count)
{{ ctrans('texts.payments') }}: {{ $payment_count }}
@endif
@if($recurring_invoice_count)
{{ ctrans('texts.recurring_invoices') }}: {{ $recurring_invoice_count }}
@endif
@if($quote_count)
{{ ctrans('texts.quotes') }}: {{ $quote_count }}
@endif
@if($credit_count)
{{ ctrans('texts.credits') }}: {{ $credit_count }}
@endif
@if($project_count)
{{ ctrans('texts.projects') }}: {{ $project_count }}
@endif
@if($task_count)
{{ ctrans('texts.tasks') }}: {{ $task_count }}
@endif
@if($vendor_count)
{{ ctrans('texts.vendors') }}: {{ $vendor_count }}
@endif
@if($expense_count)
{{ ctrans('texts.expenses') }}: {{ $expense_count }}
@endif
@if($company_gateway_count)
{{ ctrans('texts.gateways') }}: {{ $company_gateway_count }}
@endif
@if($client_gateway_token_count)
{{ ctrans('texts.tokens') }}: {{ $client_gateway_token_count }}
@endif
@if($tax_rate_count)
{{ ctrans('texts.tax_rates') }}: {{ $tax_rate_count }}
@endif
@if($document_count)
{{ ctrans('texts.documents') }}: {{ $document_count }}
@endif
@if($transaction_count)
{{ ctrans('texts.documents') }}: {{ $transaction_count }}
@endif
@if(!empty($errors))
<p>{{ ctrans('texts.failed_to_import') }}</p>
<p>{{ ctrans('texts.error') }}:</p>
<table>
<thead>
<tr>
<th>Type</th>
<th>Data</th>
<th>Error</th>
</tr>
</thead>
<tbody>
@foreach($errors as $entityType=>$entityErrors)
@foreach($entityErrors as $error)
<tr>
<td>{{$entityType}}</td>
<td>{{json_encode($error[$entityType]??null)}}</td>
<td>{{json_encode($error['error'])}}</td>
</tr>
@endforeach
@endforeach
</tbody>
</table>
@endif
{!! url('/') !!}
{!! ctrans('texts.email_signature') !!}
{!! ctrans('texts.email_from') !!}