mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2025-05-24 02:14:21 -04:00
Import functionality updates
This commit is contained in:
parent
dd20860cce
commit
ac99b0039d
@ -14,99 +14,119 @@ namespace App\Http\Controllers;
|
||||
use App\Http\Requests\Import\ImportRequest;
|
||||
use App\Http\Requests\Import\PreImportRequest;
|
||||
use App\Jobs\Import\CSVImport;
|
||||
use Illuminate\Http\UploadedFile;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Str;
|
||||
use League\Csv\Reader;
|
||||
use League\Csv\Statement;
|
||||
|
||||
class ImportController extends Controller
|
||||
{
|
||||
class ImportController extends Controller {
|
||||
|
||||
/**
|
||||
* Store a newly created resource in storage.
|
||||
*
|
||||
* @param StoreImportRequest $request
|
||||
* @return Response
|
||||
*
|
||||
* @OA\Post(
|
||||
* path="/api/v1/preimport",
|
||||
* operationId="preimport",
|
||||
* tags={"imports"},
|
||||
* summary="Pre Import checks - returns a reference to the job and the headers of the CSV",
|
||||
* description="Pre Import checks - returns a reference to the job and the headers of the CSV",
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Api-Secret"),
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Api-Token"),
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
|
||||
* @OA\Parameter(ref="#/components/parameters/include"),
|
||||
* @OA\RequestBody(
|
||||
* description="The CSV file",
|
||||
* required=true,
|
||||
* @OA\MediaType(
|
||||
* mediaType="multipart/form-data",
|
||||
* @OA\Schema(
|
||||
* type="string",
|
||||
* format="binary"
|
||||
* )
|
||||
* )
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=200,
|
||||
* description="Returns a reference to the file",
|
||||
* @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"),
|
||||
* @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"),
|
||||
* @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"),
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=422,
|
||||
* description="Validation error",
|
||||
* @OA\JsonContent(ref="#/components/schemas/ValidationError"),
|
||||
*
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response="default",
|
||||
* description="Unexpected Error",
|
||||
* @OA\JsonContent(ref="#/components/schemas/Error"),
|
||||
* ),
|
||||
* )
|
||||
*/
|
||||
public function preimport(PreImportRequest $request)
|
||||
{
|
||||
//create a reference
|
||||
$hash = Str::random(32);
|
||||
/**
|
||||
* Store a newly created resource in storage.
|
||||
*
|
||||
* @param PreImportRequest $request
|
||||
*
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*
|
||||
* @OA\Post(
|
||||
* path="/api/v1/preimport",
|
||||
* operationId="preimport",
|
||||
* tags={"imports"},
|
||||
* summary="Pre Import checks - returns a reference to the job and the headers of the CSV",
|
||||
* description="Pre Import checks - returns a reference to the job and the headers of the CSV",
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Api-Secret"),
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Api-Token"),
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
|
||||
* @OA\Parameter(ref="#/components/parameters/include"),
|
||||
* @OA\RequestBody(
|
||||
* description="The CSV file",
|
||||
* required=true,
|
||||
* @OA\MediaType(
|
||||
* mediaType="multipart/form-data",
|
||||
* @OA\Schema(
|
||||
* type="string",
|
||||
* format="binary"
|
||||
* )
|
||||
* )
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=200,
|
||||
* description="Returns a reference to the file",
|
||||
* @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"),
|
||||
* @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"),
|
||||
* @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"),
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=422,
|
||||
* description="Validation error",
|
||||
* @OA\JsonContent(ref="#/components/schemas/ValidationError"),
|
||||
*
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response="default",
|
||||
* description="Unexpected Error",
|
||||
* @OA\JsonContent(ref="#/components/schemas/Error"),
|
||||
* ),
|
||||
* )
|
||||
*/
|
||||
public function preimport( PreImportRequest $request ) {
|
||||
// Create a reference
|
||||
$hash = Str::random( 32 );
|
||||
|
||||
//store the csv in cache with an expiry of 10 minutes
|
||||
Cache::put($hash, base64_encode(file_get_contents($request->file('file')->getPathname())), 3600);
|
||||
$data = [
|
||||
'hash' => $hash,
|
||||
'mappings' => [],
|
||||
];
|
||||
/** @var UploadedFile $file */
|
||||
foreach ( $request->files->get( 'files' ) as $entityType => $file ) {
|
||||
$contents = file_get_contents( $file->getPathname() );
|
||||
|
||||
//parse CSV
|
||||
$csv_array = $this->getCsvData(file_get_contents($request->file('file')->getPathname()));
|
||||
// Store the csv in cache with an expiry of 10 minutes
|
||||
Cache::put( $hash . '-' . $entityType, base64_encode( $contents ), 3600 );
|
||||
|
||||
$class_map = $this->getEntityMap($request->input('entity_type'));
|
||||
// Parse CSV
|
||||
$csv_array = $this->getCsvData( $contents );
|
||||
|
||||
$data = [
|
||||
'hash' => $hash,
|
||||
'available' => $class_map::importable(),
|
||||
'headers' => array_slice($csv_array, 0, 2)
|
||||
];
|
||||
$class_map = $this->getEntityMap( $entityType );
|
||||
|
||||
return response()->json($data);
|
||||
}
|
||||
$data['mappings'][ $entityType ] = [
|
||||
'available' => $class_map::importable(),
|
||||
'headers' => array_slice( $csv_array, 0, 2 ),
|
||||
];
|
||||
}
|
||||
|
||||
public function import(ImportRequest $request)
|
||||
{
|
||||
CSVImport::dispatch($request->all(), auth()->user()->company());
|
||||
|
||||
return response()->json(['message' => ctrans('texts.import_started')], 200);
|
||||
}
|
||||
return response()->json( $data );
|
||||
}
|
||||
|
||||
private function getEntityMap($entity_type)
|
||||
{
|
||||
return sprintf('App\\Import\\Definitions\%sMap', ucfirst($entity_type));
|
||||
}
|
||||
public function import( ImportRequest $request ) {
|
||||
$data = $request->all();
|
||||
|
||||
private function getCsvData($csvfile)
|
||||
{
|
||||
if (! ini_get('auto_detect_line_endings')) {
|
||||
ini_set('auto_detect_line_endings', '1');
|
||||
if ( empty( $data['hash'] ) ) {
|
||||
// Create a reference
|
||||
$data['hash'] = $hash = Str::random( 32 );
|
||||
|
||||
/** @var UploadedFile $file */
|
||||
foreach ( $request->files->get( 'files' ) as $entityType => $file ) {
|
||||
$contents = file_get_contents( $file->getPathname() );
|
||||
|
||||
// Store the csv in cache with an expiry of 10 minutes
|
||||
Cache::put( $hash . '-' . $entityType, base64_encode( $contents ), 3600 );
|
||||
}
|
||||
}
|
||||
|
||||
CSVImport::dispatchNow( $data, auth()->user()->company() );
|
||||
|
||||
return response()->json( [ 'message' => ctrans( 'texts.import_started' ) ], 200 );
|
||||
}
|
||||
|
||||
private function getEntityMap( $entity_type ) {
|
||||
return sprintf( 'App\\Import\\Definitions\%sMap', ucfirst( $entity_type ) );
|
||||
}
|
||||
|
||||
private function getCsvData( $csvfile ) {
|
||||
if ( ! ini_get( 'auto_detect_line_endings' ) ) {
|
||||
ini_set( 'auto_detect_line_endings', '1' );
|
||||
}
|
||||
|
||||
$csv = Reader::createFromString($csvfile);
|
||||
@ -121,10 +141,10 @@ class ImportController extends Controller
|
||||
$firstCell = $headers[0];
|
||||
|
||||
if (strstr($firstCell, (string)config('ninja.app_name'))) {
|
||||
array_shift($data); // Invoice Ninja...
|
||||
array_shift($data); // <blank line>
|
||||
array_shift($data); // Enitty Type Header
|
||||
}
|
||||
array_shift( $data ); // Invoice Ninja...
|
||||
array_shift( $data ); // <blank line>
|
||||
array_shift( $data ); // Entity Type Header
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -28,10 +28,12 @@ class ImportRequest extends Request
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
'import_type' => 'required',
|
||||
'files' => 'required_without:hash|array|min:1|max:6',
|
||||
'hash' => 'required|string',
|
||||
'entity_type' => 'required|string',
|
||||
'column_map' => 'required|array',
|
||||
'skip_header' => 'required|boolean'
|
||||
'column_map' => 'required_with:hash|array',
|
||||
'skip_header' => 'required_with:hash|boolean',
|
||||
'files.*' => 'file|mimes:csv,txt',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -28,8 +28,9 @@ class PreImportRequest extends Request
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
'file' => 'required|file|mimes:csv,txt',
|
||||
'entity_type' => 'required',
|
||||
'files.*' => 'file|mimes:csv,txt',
|
||||
'files' => 'required|array|min:1|max:6',
|
||||
'import_type' => 'required',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -26,50 +26,51 @@ class InvoiceMap
|
||||
7 => 'invoice.date',
|
||||
8 => 'invoice.due_date',
|
||||
9 => 'invoice.terms',
|
||||
10 => 'invoice.public_notes',
|
||||
11 => 'invoice.is_sent',
|
||||
12 => 'invoice.private_notes',
|
||||
13 => 'invoice.uses_inclusive_taxes',
|
||||
14 => 'invoice.tax_name1',
|
||||
15 => 'invoice.tax_rate1',
|
||||
16 => 'invoice.tax_name2',
|
||||
17 => 'invoice.tax_rate2',
|
||||
18 => 'invoice.tax_name3',
|
||||
19 => 'invoice.tax_rate3',
|
||||
20 => 'invoice.is_amount_discount',
|
||||
21 => 'invoice.footer',
|
||||
22 => 'invoice.partial',
|
||||
23 => 'invoice.partial_due_date',
|
||||
24 => 'invoice.custom_value1',
|
||||
25 => 'invoice.custom_value2',
|
||||
26 => 'invoice.custom_value3',
|
||||
27 => 'invoice.custom_value4',
|
||||
28 => 'invoice.custom_surcharge1',
|
||||
29 => 'invoice.custom_surcharge2',
|
||||
30 => 'invoice.custom_surcharge3',
|
||||
31 => 'invoice.custom_surcharge4',
|
||||
32 => 'invoice.exchange_rate',
|
||||
33 => 'payment.date',
|
||||
34 => 'payment.amount',
|
||||
35 => 'payment.transaction_reference',
|
||||
36 => 'item.quantity',
|
||||
37 => 'item.cost',
|
||||
38 => 'item.product_key',
|
||||
39 => 'item.notes',
|
||||
40 => 'item.discount',
|
||||
41 => 'item.is_amount_discount',
|
||||
42 => 'item.tax_name1',
|
||||
43 => 'item.tax_rate1',
|
||||
44 => 'item.tax_name2',
|
||||
45 => 'item.tax_rate2',
|
||||
46 => 'item.tax_name3',
|
||||
47 => 'item.tax_rate3',
|
||||
48 => 'item.custom_value1',
|
||||
49 => 'item.custom_value2',
|
||||
50 => 'item.custom_value3',
|
||||
51 => 'item.custom_value4',
|
||||
52 => 'item.type_id',
|
||||
53 => 'client.email',
|
||||
10 => 'invoice.status',
|
||||
11 => 'invoice.public_notes',
|
||||
12 => 'invoice.is_sent',
|
||||
13 => 'invoice.private_notes',
|
||||
14 => 'invoice.uses_inclusive_taxes',
|
||||
15 => 'invoice.tax_name1',
|
||||
16 => 'invoice.tax_rate1',
|
||||
17 => 'invoice.tax_name2',
|
||||
18 => 'invoice.tax_rate2',
|
||||
19 => 'invoice.tax_name3',
|
||||
20 => 'invoice.tax_rate3',
|
||||
21 => 'invoice.is_amount_discount',
|
||||
22 => 'invoice.footer',
|
||||
23 => 'invoice.partial',
|
||||
24 => 'invoice.partial_due_date',
|
||||
25 => 'invoice.custom_value1',
|
||||
26 => 'invoice.custom_value2',
|
||||
27 => 'invoice.custom_value3',
|
||||
28 => 'invoice.custom_value4',
|
||||
29 => 'invoice.custom_surcharge1',
|
||||
30 => 'invoice.custom_surcharge2',
|
||||
31 => 'invoice.custom_surcharge3',
|
||||
32 => 'invoice.custom_surcharge4',
|
||||
33 => 'invoice.exchange_rate',
|
||||
34 => 'payment.date',
|
||||
35 => 'payment.amount',
|
||||
36 => 'payment.transaction_reference',
|
||||
37 => 'item.quantity',
|
||||
38 => 'item.cost',
|
||||
39 => 'item.product_key',
|
||||
40 => 'item.notes',
|
||||
41 => 'item.discount',
|
||||
42 => 'item.is_amount_discount',
|
||||
43 => 'item.tax_name1',
|
||||
44 => 'item.tax_rate1',
|
||||
45 => 'item.tax_name2',
|
||||
46 => 'item.tax_rate2',
|
||||
47 => 'item.tax_name3',
|
||||
48 => 'item.tax_rate3',
|
||||
49 => 'item.custom_value1',
|
||||
50 => 'item.custom_value2',
|
||||
51 => 'item.custom_value3',
|
||||
52 => 'item.custom_value4',
|
||||
53 => 'item.type_id',
|
||||
54 => 'client.email',
|
||||
];
|
||||
}
|
||||
|
||||
@ -86,50 +87,51 @@ class InvoiceMap
|
||||
7 => 'texts.date',
|
||||
8 => 'texts.due_date',
|
||||
9 => 'texts.terms',
|
||||
10 => 'texts.public_notes',
|
||||
11 => 'texts.sent',
|
||||
12 => 'texts.private_notes',
|
||||
13 => 'texts.uses_inclusive_taxes',
|
||||
14 => 'texts.tax_name',
|
||||
15 => 'texts.tax_rate',
|
||||
16 => 'texts.tax_name',
|
||||
17 => 'texts.tax_rate',
|
||||
18 => 'texts.tax_name',
|
||||
19 => 'texts.tax_rate',
|
||||
20 => 'texts.is_amount_discount',
|
||||
21 => 'texts.footer',
|
||||
22 => 'texts.partial',
|
||||
23 => 'texts.partial_due_date',
|
||||
24 => 'texts.custom_value1',
|
||||
25 => 'texts.custom_value2',
|
||||
26 => 'texts.custom_value3',
|
||||
27 => 'texts.custom_value4',
|
||||
28 => 'texts.surcharge',
|
||||
10 => 'texts.status',
|
||||
11 => 'texts.public_notes',
|
||||
12 => 'texts.sent',
|
||||
13 => 'texts.private_notes',
|
||||
14 => 'texts.uses_inclusive_taxes',
|
||||
15 => 'texts.tax_name',
|
||||
16 => 'texts.tax_rate',
|
||||
17 => 'texts.tax_name',
|
||||
18 => 'texts.tax_rate',
|
||||
19 => 'texts.tax_name',
|
||||
20 => 'texts.tax_rate',
|
||||
21 => 'texts.is_amount_discount',
|
||||
22 => 'texts.footer',
|
||||
23 => 'texts.partial',
|
||||
24 => 'texts.partial_due_date',
|
||||
25 => 'texts.custom_value1',
|
||||
26 => 'texts.custom_value2',
|
||||
27 => 'texts.custom_value3',
|
||||
28 => 'texts.custom_value4',
|
||||
29 => 'texts.surcharge',
|
||||
30 => 'texts.surcharge',
|
||||
31 => 'texts.surcharge',
|
||||
32 => 'texts.exchange_rate',
|
||||
33 => 'texts.payment_date',
|
||||
34 => 'texts.payment_amount',
|
||||
35 => 'texts.transaction_reference',
|
||||
36 => 'texts.quantity',
|
||||
37 => 'texts.cost',
|
||||
38 => 'texts.product_key',
|
||||
39 => 'texts.notes',
|
||||
40 => 'texts.discount',
|
||||
41 => 'texts.is_amount_discount',
|
||||
42 => 'texts.tax_name',
|
||||
43 => 'texts.tax_rate',
|
||||
44 => 'texts.tax_name',
|
||||
45 => 'texts.tax_rate',
|
||||
46 => 'texts.tax_name',
|
||||
47 => 'texts.tax_rate',
|
||||
48 => 'texts.custom_value',
|
||||
32 => 'texts.surcharge',
|
||||
33 => 'texts.exchange_rate',
|
||||
34 => 'texts.payment_date',
|
||||
35 => 'texts.payment_amount',
|
||||
36 => 'texts.transaction_reference',
|
||||
37 => 'texts.quantity',
|
||||
38 => 'texts.cost',
|
||||
39 => 'texts.product_key',
|
||||
40 => 'texts.notes',
|
||||
41 => 'texts.discount',
|
||||
42 => 'texts.is_amount_discount',
|
||||
43 => 'texts.tax_name',
|
||||
44 => 'texts.tax_rate',
|
||||
45 => 'texts.tax_name',
|
||||
46 => 'texts.tax_rate',
|
||||
47 => 'texts.tax_name',
|
||||
48 => 'texts.tax_rate',
|
||||
49 => 'texts.custom_value',
|
||||
50 => 'texts.custom_value',
|
||||
51 => 'texts.custom_value',
|
||||
52 => 'texts.type',
|
||||
53 => 'texts.email',
|
||||
52 => 'texts.custom_value',
|
||||
53 => 'texts.type',
|
||||
54 => 'texts.email',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -19,10 +19,10 @@ use Illuminate\Support\Str;
|
||||
class ClientTransformer extends BaseTransformer
|
||||
{
|
||||
/**
|
||||
* @param $data
|
||||
*
|
||||
* @return bool|Item
|
||||
*/
|
||||
* @param $data
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function transform($data)
|
||||
{
|
||||
if (isset($data->name) && $this->hasClient($data->name)) {
|
||||
@ -33,46 +33,46 @@ class ClientTransformer extends BaseTransformer
|
||||
$settings->currency_id = (string)$this->getCurrencyByCode($data);
|
||||
|
||||
return [
|
||||
'company_id' => $this->maps['company']->id,
|
||||
'name' => $this->getString($data, 'client.name'),
|
||||
'work_phone' => $this->getString($data, 'client.phone'),
|
||||
'address1' => $this->getString($data, 'client.address1'),
|
||||
'address2' => $this->getString($data, 'client.address2'),
|
||||
'city' => $this->getString($data, 'client.city'),
|
||||
'state' => $this->getString($data, 'client.state'),
|
||||
'shipping_address1' => $this->getString($data, 'client.shipping_address1'),
|
||||
'shipping_address2' => $this->getString($data, 'client.shipping_address2'),
|
||||
'shipping_city' => $this->getString($data, 'client.shipping_city'),
|
||||
'shipping_state' => $this->getString($data, 'client.shipping_state'),
|
||||
'shipping_postal_code' => $this->getString($data, 'client.shipping_postal_code'),
|
||||
'public_notes' => $this->getString($data, 'client.public_notes'),
|
||||
'private_notes' => $this->getString($data, 'client.private_notes'),
|
||||
'website' => $this->getString($data, 'client.website'),
|
||||
'vat_number' => $this->getString($data, 'client.vat_number'),
|
||||
'id_number' => $this->getString($data, 'client.id_number'),
|
||||
'custom_value1' => $this->getString($data, 'client.custom1'),
|
||||
'custom_value2' => $this->getString($data, 'client.custom2'),
|
||||
'custom_value3' => $this->getString($data, 'client.custom3'),
|
||||
'custom_value4' => $this->getString($data, 'client.custom4'),
|
||||
'balance' => $this->getFloat($data, 'client.balance'),
|
||||
'paid_to_date' => $this->getFloat($data, 'client.paid_to_date'),
|
||||
'credit_balance' => 0,
|
||||
'settings' => $settings,
|
||||
'client_hash' => Str::random(40),
|
||||
'contacts' => [
|
||||
[
|
||||
'first_name' => $this->getString($data, 'contact.first_name'),
|
||||
'last_name' => $this->getString($data, 'contact.last_name'),
|
||||
'email' => $this->getString($data, 'contact.email'),
|
||||
'phone' => $this->getString($data, 'contact.phone'),
|
||||
'custom_value1' => $this->getString($data, 'contact.custom1'),
|
||||
'custom_value2' => $this->getString($data, 'contact.custom2'),
|
||||
'custom_value3' => $this->getString($data, 'contact.custom3'),
|
||||
'custom_value4' => $this->getString($data, 'contact.custom4'),
|
||||
],
|
||||
],
|
||||
'country_id' => isset($data->country_id) ? $this->getCountryId($data->country_id) : null,
|
||||
'shipping_country_id' => isset($data->shipping_country_id) ? $this->getCountryId($data->shipping_country_id) : null,
|
||||
];
|
||||
'company_id' => $this->maps['company']->id,
|
||||
'name' => $this->getString( $data, 'client.name' ),
|
||||
'work_phone' => $this->getString( $data, 'client.phone' ),
|
||||
'address1' => $this->getString( $data, 'client.address1' ),
|
||||
'address2' => $this->getString( $data, 'client.address2' ),
|
||||
'city' => $this->getString( $data, 'client.city' ),
|
||||
'state' => $this->getString( $data, 'client.state' ),
|
||||
'shipping_address1' => $this->getString( $data, 'client.shipping_address1' ),
|
||||
'shipping_address2' => $this->getString( $data, 'client.shipping_address2' ),
|
||||
'shipping_city' => $this->getString( $data, 'client.shipping_city' ),
|
||||
'shipping_state' => $this->getString( $data, 'client.shipping_state' ),
|
||||
'shipping_postal_code' => $this->getString( $data, 'client.shipping_postal_code' ),
|
||||
'public_notes' => $this->getString( $data, 'client.public_notes' ),
|
||||
'private_notes' => $this->getString( $data, 'client.private_notes' ),
|
||||
'website' => $this->getString( $data, 'client.website' ),
|
||||
'vat_number' => $this->getString( $data, 'client.vat_number' ),
|
||||
'id_number' => $this->getString( $data, 'client.id_number' ),
|
||||
'custom_value1' => $this->getString( $data, 'client.custom1' ),
|
||||
'custom_value2' => $this->getString( $data, 'client.custom2' ),
|
||||
'custom_value3' => $this->getString( $data, 'client.custom3' ),
|
||||
'custom_value4' => $this->getString( $data, 'client.custom4' ),
|
||||
'balance' => preg_replace( '/[^0-9,.]+/', '', $this->getFloat( $data, 'client.balance' ) ),
|
||||
'paid_to_date' => preg_replace( '/[^0-9,.]+/', '', $this->getFloat( $data, 'client.paid_to_date' ) ),
|
||||
'credit_balance' => 0,
|
||||
'settings' => $settings,
|
||||
'client_hash' => Str::random( 40 ),
|
||||
'contacts' => [
|
||||
[
|
||||
'first_name' => $this->getString( $data, 'contact.first_name' ),
|
||||
'last_name' => $this->getString( $data, 'contact.last_name' ),
|
||||
'email' => $this->getString( $data, 'contact.email' ),
|
||||
'phone' => $this->getString( $data, 'contact.phone' ),
|
||||
'custom_value1' => $this->getString( $data, 'contact.custom1' ),
|
||||
'custom_value2' => $this->getString( $data, 'contact.custom2' ),
|
||||
'custom_value3' => $this->getString( $data, 'contact.custom3' ),
|
||||
'custom_value4' => $this->getString( $data, 'contact.custom4' ),
|
||||
],
|
||||
],
|
||||
'country_id' => isset( $data->country_id ) ? $this->getCountryId( $data->country_id ) : null,
|
||||
'shipping_country_id' => isset( $data->shipping_country_id ) ? $this->getCountryId( $data->shipping_country_id ) : null,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -19,7 +19,7 @@ class InvoiceTransformer extends BaseTransformer
|
||||
/**
|
||||
* @param $data
|
||||
*
|
||||
* @return bool|Item
|
||||
* @return array
|
||||
*/
|
||||
public function transform($data)
|
||||
{
|
||||
@ -27,8 +27,8 @@ class InvoiceTransformer extends BaseTransformer
|
||||
'company_id' => $this->maps['company']->id,
|
||||
'number' => $this->getString($data, 'invoice.number'),
|
||||
'user_id' => $this->getString($data, 'invoice.user_id'),
|
||||
'amount' => $this->getFloat($data, 'invoice.amount'),
|
||||
'balance' => $this->getFloat($data, 'invoice.balance'),
|
||||
'amount' => $amount = $this->getFloat($data, 'invoice.amount'),
|
||||
'balance' => isset( $data['invoice.balance'] ) ? $this->getFloat( $data, 'invoice.balance' ) : $amount,
|
||||
'client_id' => $this->getClient($this->getString($data, 'client.name'), $this->getString($data, 'client.email')),
|
||||
'discount' => $this->getFloat($data, 'invoice.discount'),
|
||||
'po_number' => $this->getString($data, 'invoice.po_number'),
|
||||
|
@ -13,26 +13,35 @@ namespace App\Jobs\Import;
|
||||
|
||||
use App\Factory\ClientFactory;
|
||||
use App\Factory\InvoiceFactory;
|
||||
use App\Factory\PaymentFactory;
|
||||
use App\Factory\ProductFactory;
|
||||
use App\Http\Requests\Client\StoreClientRequest;
|
||||
use App\Http\Requests\Invoice\StoreInvoiceRequest;
|
||||
use App\Http\Requests\Product\StoreProductRequest;
|
||||
use App\Import\Transformers\BaseTransformer;
|
||||
use App\Import\Transformers\ClientTransformer;
|
||||
use App\Import\Transformers\InvoiceItemTransformer;
|
||||
use App\Import\Transformers\InvoiceTransformer;
|
||||
use App\Import\Transformers\PaymentTransformer;
|
||||
use App\Import\Transformers\ProductTransformer;
|
||||
use App\Jobs\Mail\MailRouter;
|
||||
use App\Libraries\MultiDB;
|
||||
use App\Mail\Import\ImportCompleted;
|
||||
use App\Models\Client;
|
||||
use App\Models\ClientContact;
|
||||
use App\Models\Company;
|
||||
use App\Models\Country;
|
||||
use App\Models\Currency;
|
||||
use App\Models\Expense;
|
||||
use App\Models\ExpenseCategory;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\Payment;
|
||||
use App\Models\Product;
|
||||
use App\Models\TaxRate;
|
||||
use App\Models\User;
|
||||
use App\Repositories\ClientContactRepository;
|
||||
use App\Repositories\ClientRepository;
|
||||
use App\Models\Vendor;
|
||||
use App\Repositories\InvoiceRepository;
|
||||
use App\Repositories\ProductRepository;
|
||||
use App\Repositories\PaymentRepository;
|
||||
use App\Utils\Traits\CleanLineItems;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
@ -42,328 +51,427 @@ use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Illuminate\Support\Str;
|
||||
use League\Csv\Reader;
|
||||
use League\Csv\Statement;
|
||||
|
||||
class CSVImport implements ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels, CleanLineItems;
|
||||
class CSVImport implements ShouldQueue {
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels, CleanLineItems;
|
||||
|
||||
public $invoice;
|
||||
public $invoice;
|
||||
|
||||
public $company;
|
||||
public $company;
|
||||
|
||||
public $hash;
|
||||
public $hash;
|
||||
|
||||
public $entity_type;
|
||||
public $import_type;
|
||||
|
||||
public $skip_header;
|
||||
public $skip_header;
|
||||
|
||||
public $column_map;
|
||||
public $column_map;
|
||||
|
||||
public $import_array;
|
||||
public $import_array;
|
||||
|
||||
public $error_array;
|
||||
public $error_array = [];
|
||||
|
||||
public $maps;
|
||||
public $maps;
|
||||
|
||||
public function __construct(array $request, Company $company)
|
||||
{
|
||||
$this->company = $company;
|
||||
public function __construct( array $request, Company $company ) {
|
||||
$this->company = $company;
|
||||
|
||||
$this->hash = $request['hash'];
|
||||
$this->hash = $request['hash'];
|
||||
|
||||
$this->entity_type = $request['entity_type'];
|
||||
$this->import_type = $request['import_type'];
|
||||
|
||||
$this->skip_header = $request['skip_header'];
|
||||
$this->skip_header = $request['skip_header'] ?? null;
|
||||
|
||||
$this->column_map = $request['column_map'];
|
||||
}
|
||||
$this->column_map = $request['column_map'] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
*
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
MultiDB::setDb($this->company->db);
|
||||
/**
|
||||
* Execute the job.
|
||||
*
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle() {
|
||||
|
||||
$this->company->owner()->setCompany($this->company);
|
||||
Auth::login($this->company->owner(), true);
|
||||
MultiDB::setDb( $this->company->db );
|
||||
|
||||
$this->buildMaps();
|
||||
$this->company->owner()->setCompany( $this->company );
|
||||
Auth::login( $this->company->owner(), true );
|
||||
|
||||
//sort the array by key
|
||||
ksort($this->column_map);
|
||||
$this->buildMaps();
|
||||
|
||||
nlog("import".ucfirst($this->entity_type));
|
||||
$this->{"import".ucfirst($this->entity_type)}();
|
||||
|
||||
$data = [
|
||||
'entity' => ucfirst($this->entity_type),
|
||||
'errors' => $this->error_array,
|
||||
'clients' => $this->maps['clients'],
|
||||
'products' => $this->maps['products'],
|
||||
'invoices' => $this->maps['invoices'],
|
||||
'settings' => $this->company->settings
|
||||
];
|
||||
//sort the array by key
|
||||
foreach ( $this->column_map as $entityType => &$map ) {
|
||||
ksort( $map );
|
||||
}
|
||||
|
||||
//nlog(print_r($data, 1));
|
||||
nlog( "import" . ucfirst( $this->import_type ) );
|
||||
$this->{"import" . ucfirst( $this->import_type )}();
|
||||
|
||||
MailRouter::dispatch(new ImportCompleted($data), $this->company, auth()->user());
|
||||
}
|
||||
$data = [
|
||||
'errors' => $this->error_array,
|
||||
'company'=>$this->company,
|
||||
];
|
||||
|
||||
public function failed($exception)
|
||||
{
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
MailRouter::dispatchNow( new ImportCompleted( $data ), $this->company, auth()->user() );
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private function importInvoice()
|
||||
{
|
||||
$invoice_transformer = new InvoiceTransformer($this->maps);
|
||||
private function importCsv() {
|
||||
foreach ( [ 'client', 'product', 'invoice', 'payment', 'vendor', 'expense' ] as $entityType ) {
|
||||
if ( empty( $this->column_map[ $entityType ] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$records = $this->getCsvData();
|
||||
$csvData = $this->getCsvData( $entityType );
|
||||
|
||||
$invoice_number_key = array_search('Invoice Number', reset($records));
|
||||
if ( ! empty( $csvData ) ) {
|
||||
$importFunction = "import" . Str::plural( Str::title( $entityType ) );
|
||||
|
||||
if ($this->skip_header) {
|
||||
array_shift($records);
|
||||
}
|
||||
if ( method_exists( $this, $importFunction ) ) {
|
||||
// If there's an entity-specific import function, use that.
|
||||
$this->$importFunction( $csvData );
|
||||
} else {
|
||||
// Otherwise, use the generic import function.
|
||||
$this->importEntities( $csvData, $entityType );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!$invoice_number_key) {
|
||||
nlog("no invoice number to use as key - returning");
|
||||
return;
|
||||
}
|
||||
private function importInvoices( $records ) {
|
||||
$invoice_transformer = new InvoiceTransformer( $this->maps );
|
||||
|
||||
$unique_invoices = [];
|
||||
if ( $this->skip_header ) {
|
||||
array_shift( $records );
|
||||
}
|
||||
|
||||
//get an array of unique invoice numbers
|
||||
foreach ($records as $key => $value) {
|
||||
$unique_invoices[] = $value[$invoice_number_key];
|
||||
}
|
||||
$keys = $this->column_map['invoice'];
|
||||
$invoice_number_key = array_search( 'invoice.number', $keys );
|
||||
if ( $invoice_number_key === false ) {
|
||||
nlog( "no invoice number to use as key - returning" );
|
||||
|
||||
foreach ($unique_invoices as $unique) {
|
||||
$invoices = array_filter($records, function ($value) use ($invoice_number_key, $unique) {
|
||||
return $value[$invoice_number_key] == $unique;
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
$keys = $this->column_map;
|
||||
$values = array_intersect_key(reset($invoices), $this->column_map);
|
||||
$invoice_data = array_combine($keys, $values);
|
||||
$items_by_invoice = [];
|
||||
|
||||
$invoice = $invoice_transformer->transform($invoice_data);
|
||||
|
||||
$this->processInvoice($invoices, $invoice);
|
||||
}
|
||||
}
|
||||
|
||||
private function processInvoice($invoices, $invoice)
|
||||
{
|
||||
$invoice_repository = new InvoiceRepository();
|
||||
$item_transformer = new InvoiceItemTransformer($this->maps);
|
||||
$items = [];
|
||||
|
||||
foreach ($invoices as $record) {
|
||||
$keys = $this->column_map;
|
||||
$values = array_intersect_key($record, $this->column_map);
|
||||
$invoice_data = array_combine($keys, $values);
|
||||
|
||||
$items[] = $item_transformer->transform($invoice_data);
|
||||
}
|
||||
|
||||
$invoice['line_items'] = $this->cleanItems($items);
|
||||
|
||||
$validator = Validator::make($invoice, (new StoreInvoiceRequest())->rules());
|
||||
|
||||
if ($validator->fails()) {
|
||||
$this->error_array['invoices'] = ['invoice' => $invoice, 'error' => json_encode($validator->errors())];
|
||||
} else {
|
||||
if ($validator->fails()) {
|
||||
$this->error_array[] = ['invoice' => $invoice, 'error' => json_encode($validator->errors())];
|
||||
} else {
|
||||
$invoice = $invoice_repository->save($invoice, InvoiceFactory::create($this->company->id, $this->setUser($record)));
|
||||
|
||||
$this->maps['invoices'][] = $invoice->id;
|
||||
|
||||
$this->performInvoiceActions($invoice, $record, $invoice_repository);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function performInvoiceActions($invoice, $record, $invoice_repository)
|
||||
{
|
||||
$invoice = $this->actionInvoiceStatus($invoice, $record, $invoice_repository);
|
||||
}
|
||||
|
||||
private function actionInvoiceStatus($invoice, $status, $invoice_repository)
|
||||
{
|
||||
switch ($status) {
|
||||
case 'Archived':
|
||||
$invoice_repository->archive($invoice);
|
||||
$invoice->fresh();
|
||||
break;
|
||||
case 'Sent':
|
||||
$invoice = $invoice->service()->markSent()->save();
|
||||
break;
|
||||
case 'Viewed':
|
||||
$invoice = $invoice->service()->markSent()->save();
|
||||
break;
|
||||
default:
|
||||
# code...
|
||||
break;
|
||||
}
|
||||
|
||||
if ($invoice->balance < $invoice->amount && $invoice->status_id <= Invoice::STATUS_SENT) {
|
||||
$invoice->status_id = Invoice::STATUS_PARTIAL;
|
||||
$invoice->save();
|
||||
}
|
||||
|
||||
return $invoice;
|
||||
}
|
||||
|
||||
//todo limit client imports for hosted version
|
||||
private function importClient()
|
||||
{
|
||||
//clients
|
||||
$records = $this->getCsvData();
|
||||
|
||||
$contact_repository = new ClientContactRepository();
|
||||
$client_repository = new ClientRepository($contact_repository);
|
||||
$client_transformer = new ClientTransformer($this->maps);
|
||||
|
||||
if ($this->skip_header) {
|
||||
array_shift($records);
|
||||
}
|
||||
|
||||
foreach ($records as $record) {
|
||||
$keys = $this->column_map;
|
||||
$values = array_intersect_key($record, $this->column_map);
|
||||
|
||||
$client_data = array_combine($keys, $values);
|
||||
|
||||
$client = $client_transformer->transform($client_data);
|
||||
|
||||
$validator = Validator::make($client, (new StoreClientRequest())->rules());
|
||||
|
||||
if ($validator->fails()) {
|
||||
$this->error_array['clients'] = ['client' => $client, 'error' => json_encode($validator->errors())];
|
||||
} else {
|
||||
$client = $client_repository->save($client, ClientFactory::create($this->company->id, $this->setUser($record)));
|
||||
|
||||
if (array_key_exists('client.balance', $client_data)) {
|
||||
$client->balance = preg_replace('/[^0-9,.]+/', '', $client_data['client.balance']);
|
||||
}
|
||||
|
||||
if (array_key_exists('client.paid_to_date', $client_data)) {
|
||||
$client->paid_to_date = preg_replace('/[^0-9,.]+/', '', $client_data['client.paid_to_date']);
|
||||
}
|
||||
|
||||
$client->save();
|
||||
|
||||
$this->maps['clients'][] = $client->id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private function importProduct()
|
||||
{
|
||||
$product_repository = new ProductRepository();
|
||||
$product_transformer = new ProductTransformer($this->maps);
|
||||
|
||||
$records = $this->getCsvData();
|
||||
|
||||
if ($this->skip_header) {
|
||||
array_shift($records);
|
||||
}
|
||||
|
||||
foreach ($records as $record) {
|
||||
$keys = $this->column_map;
|
||||
$values = array_intersect_key($record, $this->column_map);
|
||||
|
||||
$product_data = array_combine($keys, $values);
|
||||
|
||||
$product = $product_transformer->transform($product_data);
|
||||
|
||||
$validator = Validator::make($product, (new StoreProductRequest())->rules());
|
||||
|
||||
if ($validator->fails()) {
|
||||
$this->error_array['products'] = ['product' => $product, 'error' => json_encode($validator->errors())];
|
||||
} else {
|
||||
$product = $product_repository->save($product, ProductFactory::create($this->company->id, $this->setUser($record)));
|
||||
|
||||
$product->save();
|
||||
|
||||
$this->maps['products'][] = $product->id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
private function buildMaps()
|
||||
{
|
||||
$this->maps['currencies'] = Currency::all();
|
||||
$this->maps['users'] = $this->company->users;
|
||||
$this->maps['company'] = $this->company;
|
||||
$this->maps['clients'] = [];
|
||||
$this->maps['products'] = [];
|
||||
$this->maps['invoices'] = [];
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
private function setUser($record)
|
||||
{
|
||||
$user_key_exists = array_search('client.user_id', $this->column_map);
|
||||
|
||||
if ($user_key_exists) {
|
||||
return $this->findUser($record[$user_key_exists]);
|
||||
} else {
|
||||
return $this->company->owner()->id;
|
||||
}
|
||||
}
|
||||
|
||||
private function findUser($user_hash)
|
||||
{
|
||||
$user = User::where('company_id', $this->company->id)
|
||||
->where(\DB::raw('CONCAT_WS(" ", first_name, last_name)'), 'like', '%' . $user_hash . '%')
|
||||
->first();
|
||||
|
||||
if ($user) {
|
||||
return $user->id;
|
||||
} else {
|
||||
return $this->company->owner()->id;
|
||||
}
|
||||
}
|
||||
|
||||
private function getCsvData()
|
||||
{
|
||||
$base64_encoded_csv = Cache::get($this->hash);
|
||||
$csv = base64_decode($base64_encoded_csv);
|
||||
$csv = Reader::createFromString($csv);
|
||||
|
||||
$stmt = new Statement();
|
||||
$data = iterator_to_array($stmt->process($csv));
|
||||
|
||||
if (count($data) > 0) {
|
||||
$headers = $data[0];
|
||||
|
||||
// Remove Invoice Ninja headers
|
||||
if (count($headers) && count($data) > 4) {
|
||||
$firstCell = $headers[0];
|
||||
if (strstr($firstCell, config('ninja.app_name'))) {
|
||||
array_shift($data); // Invoice Ninja...
|
||||
array_shift($data); // <blank line>
|
||||
array_shift($data); // Enitty Type Header
|
||||
}
|
||||
}
|
||||
}
|
||||
// Group line items by invoice and map columns to keys.
|
||||
foreach ( $records as $key => $value ) {
|
||||
$items_by_invoice[ $value[ $invoice_number_key ] ][] = array_combine( $keys,array_intersect_key( $value , $keys ));
|
||||
}
|
||||
|
||||
foreach ( $items_by_invoice as $invoice_number => $line_items ) {
|
||||
$invoice_data = array_combine( $keys, reset( $line_items ) );
|
||||
|
||||
$invoice = $invoice_transformer->transform( $invoice_data );
|
||||
|
||||
$this->processInvoice( $line_items, $invoice );
|
||||
}
|
||||
}
|
||||
|
||||
private function processInvoice( $line_items, $invoice ) {
|
||||
$invoice_repository = new InvoiceRepository();
|
||||
$item_transformer = new InvoiceItemTransformer( $this->maps );
|
||||
$items = [];
|
||||
|
||||
foreach ( $line_items as $record ) {
|
||||
$items[] = $item_transformer->transform( $record );
|
||||
}
|
||||
|
||||
$invoice['line_items'] = $this->cleanItems( $items );
|
||||
|
||||
$validator = Validator::make( $invoice, ( new StoreInvoiceRequest() )->rules() );
|
||||
|
||||
if ( $validator->fails() ) {
|
||||
$this->error_array['invoice'][] = [ 'invoice' => $invoice, 'error' => $validator->errors()->all() ];
|
||||
} else {
|
||||
$invoice =
|
||||
$invoice_repository->save( $invoice, InvoiceFactory::create( $this->company->id, $this->getUserIDForRecord( $record ) ) );
|
||||
|
||||
$this->addInvoiceToMaps( $invoice );
|
||||
|
||||
// If there's no payment import, try importing payment data from the invoices CSV.
|
||||
if ( empty( $this->column_map['payment'] ) ) {
|
||||
$payment_data = reset( $line_items );
|
||||
// Check for payment columns
|
||||
if ( ! empty( $payment_data['payment.amount'] ) ) {
|
||||
// Transform the payment to be saved
|
||||
$payment_transformer = new PaymentTransformer( $this->maps );
|
||||
|
||||
/** @var PaymentRepository $payment_repository */
|
||||
$payment_repository = app()->make( PaymentRepository::class );
|
||||
$transformed_payment = $payment_transformer->transform( $payment_data );
|
||||
$transformed_payment['user_id'] = $invoice->user_id;
|
||||
$transformed_payment['client_id'] = $invoice->client_id;
|
||||
$transformed_payment['invoices'] = [
|
||||
[
|
||||
'invoice_id' => $invoice->id,
|
||||
'amount' => $transformed_payment['amount'],
|
||||
],
|
||||
];
|
||||
|
||||
$payment_repository->save(
|
||||
$transformed_payment,
|
||||
PaymentFactory::create( $this->company->id, $invoice->user_id, $invoice->client_id )
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$this->actionInvoiceStatus( $invoice, $record['invoice.status']??null, $invoice_repository );
|
||||
}
|
||||
}
|
||||
|
||||
private function actionInvoiceStatus( $invoice, $status, $invoice_repository ) {
|
||||
switch ( $status ) {
|
||||
case 'Archived':
|
||||
$invoice_repository->archive( $invoice );
|
||||
$invoice->fresh();
|
||||
break;
|
||||
case 'Sent':
|
||||
$invoice = $invoice->service()->markSent()->save();
|
||||
break;
|
||||
case 'Viewed':
|
||||
$invoice = $invoice->service()->markSent()->save();
|
||||
break;
|
||||
default:
|
||||
# code...
|
||||
break;
|
||||
}
|
||||
|
||||
if($invoice->status_id <= Invoice::STATUS_SENT){
|
||||
if ( $invoice->balance < $invoice->amount) {
|
||||
$invoice->status_id = Invoice::STATUS_PARTIAL;
|
||||
$invoice->save();
|
||||
} elseif($invoice->balance <=0){
|
||||
$invoice->status_id = Invoice::STATUS_PAID;
|
||||
$invoice->save();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return $invoice;
|
||||
}
|
||||
|
||||
private function importEntities( $records, $entity_type ) {
|
||||
$entity_type = Str::slug( $entity_type, '_' );
|
||||
$formatted_entity_type = Str::title( $entity_type );
|
||||
|
||||
$request = "\\App\\Http\\Requests\\${formatted_entity_type}\\Store${formatted_entity_type}Request";
|
||||
$repository_name = '\\App\\Repositories\\'.$formatted_entity_type . 'Repository';
|
||||
$transformer_name = '\\App\\Import\\Transformers\\'.$formatted_entity_type . 'Transformer';
|
||||
$factoryName = '\\App\\Factory\\'.$formatted_entity_type . 'Factory';
|
||||
|
||||
$repository = app()->make($repository_name);
|
||||
$transformer = new $transformer_name( $this->maps );
|
||||
|
||||
if ( $this->skip_header ) {
|
||||
array_shift( $records );
|
||||
}
|
||||
|
||||
foreach ( $records as $record ) {
|
||||
$keys = $this->column_map[ $entity_type ];
|
||||
$values = array_intersect_key( $record, $keys );
|
||||
|
||||
$data = array_combine( $keys, $values );
|
||||
|
||||
$entity = $transformer->transform( $data );
|
||||
|
||||
$validator = Validator::make( $entity, ( new $request() )->rules() );
|
||||
|
||||
if ( $validator->fails() ) {
|
||||
$this->error_array[ $entity_type ][] =
|
||||
[ $entity_type => $entity, 'error' => $validator->errors()->all() ];
|
||||
} else {
|
||||
$entity =
|
||||
$repository->save( $entity, $factoryName::create( $this->company->id, $this->getUserIDForRecord( $data) ) );
|
||||
|
||||
$entity->save();
|
||||
$this->{'add' . $formatted_entity_type . 'ToMaps'}( $entity );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
private function buildMaps() {
|
||||
$this->maps = [
|
||||
'company' => $this->company,
|
||||
'client' => [],
|
||||
'contact' => [],
|
||||
'invoice' => [],
|
||||
'invoice_client' => [],
|
||||
'product' => [],
|
||||
'countries' => [],
|
||||
'countries2' => [],
|
||||
'currencies' => [],
|
||||
'client_ids' => [],
|
||||
'invoice_ids' => [],
|
||||
'vendors' => [],
|
||||
'expense_categories' => [],
|
||||
'tax_rates' => [],
|
||||
'tax_names' => [],
|
||||
];
|
||||
|
||||
$clients = Client::scope()->get();
|
||||
foreach ( $clients as $client ) {
|
||||
$this->addClientToMaps( $client );
|
||||
}
|
||||
|
||||
$contacts = ClientContact::scope()->get();
|
||||
foreach ( $contacts as $contact ) {
|
||||
$this->addContactToMaps( $contact );
|
||||
}
|
||||
|
||||
$invoices = Invoice::scope()->get();
|
||||
foreach ( $invoices as $invoice ) {
|
||||
$this->addInvoiceToMaps( $invoice );
|
||||
}
|
||||
|
||||
$products = Product::scope()->get();
|
||||
foreach ( $products as $product ) {
|
||||
$this->addProductToMaps( $product );
|
||||
}
|
||||
|
||||
$countries = Country::all();
|
||||
foreach ( $countries as $country ) {
|
||||
$this->maps['countries'][ strtolower( $country->name ) ] = $country->id;
|
||||
$this->maps['countries2'][ strtolower( $country->iso_3166_2 ) ] = $country->id;
|
||||
}
|
||||
|
||||
$currencies = Currency::all();
|
||||
foreach ( $currencies as $currency ) {
|
||||
$this->maps['currencies'][ strtolower( $currency->code ) ] = $currency->id;
|
||||
}
|
||||
|
||||
$vendors = Vendor::scope()->get();
|
||||
foreach ( $vendors as $vendor ) {
|
||||
$this->addVendorToMaps( $vendor );
|
||||
}
|
||||
|
||||
$expenseCaegories = ExpenseCategory::scope()->get();
|
||||
foreach ( $expenseCaegories as $category ) {
|
||||
$this->addExpenseCategoryToMaps( $category );
|
||||
}
|
||||
|
||||
$taxRates = TaxRate::scope()->get();
|
||||
foreach ( $taxRates as $taxRate ) {
|
||||
$name = trim( strtolower( $taxRate->name ) );
|
||||
$this->maps['tax_rates'][ $name ] = $taxRate->rate;
|
||||
$this->maps['tax_names'][ $name ] = $taxRate->name;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Invoice $invoice
|
||||
*/
|
||||
private function addInvoiceToMaps( Invoice $invoice ) {
|
||||
if ( $number = strtolower( trim( $invoice->invoice_number ) ) ) {
|
||||
$this->maps['invoices'][ $number ] = $invoice;
|
||||
$this->maps['invoice'][ $number ] = $invoice->id;
|
||||
$this->maps['invoice_client'][ $number ] = $invoice->client_id;
|
||||
$this->maps['invoice_ids'][ $invoice->public_id ] = $invoice->id;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Client $client
|
||||
*/
|
||||
private function addClientToMaps( Client $client ) {
|
||||
if ( $name = strtolower( trim( $client->name ) ) ) {
|
||||
$this->maps['client'][ $name ] = $client->id;
|
||||
$this->maps['client_ids'][ $client->public_id ] = $client->id;
|
||||
}
|
||||
if ( $client->contacts->count() ) {
|
||||
$contact = $client->contacts[0];
|
||||
if ( $email = strtolower( trim( $contact->email ) ) ) {
|
||||
$this->maps['client'][ $email ] = $client->id;
|
||||
}
|
||||
if ( $name = strtolower( trim($contact->first_name.' '.$contact->last_name) ) ) {
|
||||
$this->maps['client'][ $name ] = $client->id;
|
||||
}
|
||||
$this->maps['client_ids'][ $client->public_id ] = $client->id;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ClientContact $contact
|
||||
*/
|
||||
private function addContactToMaps( ClientContact $contact ) {
|
||||
if ( $key = strtolower( trim( $contact->email ) ) ) {
|
||||
$this->maps['contact'][ $key ] = $contact;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Product $product
|
||||
*/
|
||||
private function addProductToMaps( Product $product ) {
|
||||
if ( $key = strtolower( trim( $product->product_key ) ) ) {
|
||||
$this->maps['product'][ $key ] = $product;
|
||||
}
|
||||
}
|
||||
|
||||
private function addVendorToMaps( Vendor $vendor ) {
|
||||
$this->maps['vendor'][ strtolower( $vendor->name ) ] = $vendor->id;
|
||||
}
|
||||
|
||||
private function addExpenseCategoryToMaps( ExpenseCategory $category ) {
|
||||
if ( $name = strtolower( $category->name ) ) {
|
||||
$this->maps['expense_category'][ $name ] = $category->id;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private function getUserIDForRecord( $record ) {
|
||||
if ( !empty($record['client.user_id']) ) {
|
||||
return $this->findUser( $record[ 'client.user_id' ] );
|
||||
} else {
|
||||
return $this->company->owner()->id;
|
||||
}
|
||||
}
|
||||
|
||||
private function findUser( $user_hash ) {
|
||||
$user = User::where( 'company_id', $this->company->id )
|
||||
->where( \DB::raw( 'CONCAT_WS(" ", first_name, last_name)' ), 'like', '%' . $user_hash . '%' )
|
||||
->first();
|
||||
|
||||
if ( $user ) {
|
||||
return $user->id;
|
||||
} else {
|
||||
return $this->company->owner()->id;
|
||||
}
|
||||
}
|
||||
|
||||
private function getCsvData( $entityType ) {
|
||||
$base64_encoded_csv = Cache::get( $this->hash . '-' . $entityType );
|
||||
if ( empty( $base64_encoded_csv ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$csv = base64_decode( $base64_encoded_csv );
|
||||
$csv = Reader::createFromString( $csv );
|
||||
|
||||
$stmt = new Statement();
|
||||
$data = iterator_to_array( $stmt->process( $csv ) );
|
||||
|
||||
if ( count( $data ) > 0 ) {
|
||||
$headers = $data[0];
|
||||
|
||||
// Remove Invoice Ninja headers
|
||||
if ( count( $headers ) && count( $data ) > 4 && $this->import_type === 'csv' ) {
|
||||
$firstCell = $headers[0];
|
||||
if ( strstr( $firstCell, config( 'ninja.app_name' ) ) ) {
|
||||
array_shift( $data ); // Invoice Ninja...
|
||||
array_shift( $data ); // <blank line>
|
||||
array_shift( $data ); // Enitty Type Header
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
@ -20,6 +20,14 @@ use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\ModelNotFoundException as ModelNotFoundException;
|
||||
use Illuminate\Support\Carbon;
|
||||
|
||||
|
||||
/**
|
||||
* Class BaseModel
|
||||
*
|
||||
* @method scope() static
|
||||
*
|
||||
* @package App\Models
|
||||
*/
|
||||
class BaseModel extends Model
|
||||
{
|
||||
use MakesHash;
|
||||
|
@ -23,6 +23,13 @@ use Illuminate\Notifications\Notifiable;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Laracasts\Presenter\PresentableTrait;
|
||||
|
||||
/**
|
||||
* Class ClientContact
|
||||
*
|
||||
* @method scope() static
|
||||
*
|
||||
* @package App\Models
|
||||
*/
|
||||
class ClientContact extends Authenticatable implements HasLocalePreference
|
||||
{
|
||||
use Notifiable;
|
||||
@ -84,6 +91,27 @@ class ClientContact extends Authenticatable implements HasLocalePreference
|
||||
'client_id',
|
||||
];
|
||||
|
||||
|
||||
/*
|
||||
V2 type of scope
|
||||
*/
|
||||
public function scopeCompany($query)
|
||||
{
|
||||
$query->where('company_id', auth()->user()->companyId());
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
/*
|
||||
V1 type of scope
|
||||
*/
|
||||
public function scopeScope($query)
|
||||
{
|
||||
$query->where($this->getTable().'.company_id', '=', auth()->user()->company()->id);
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
public function getEntityType()
|
||||
{
|
||||
return self::class;
|
||||
|
@ -1,4 +1,4 @@
|
||||
@component('email.template.master', ['design' => 'light', 'settings' => $settings])
|
||||
@component('email.template.master', ['design' => 'light', 'settings' => $company->settings])
|
||||
@slot('header')
|
||||
@include('email.components.header', ['logo' => 'https://www.invoiceninja.com/wp-content/uploads/2015/10/logo-white-horizontal-1.png'])
|
||||
@endslot
|
||||
@ -73,15 +73,32 @@
|
||||
<p><b>Documents Imported:</b> {{ count($company->documents) }} </p>
|
||||
@endif
|
||||
|
||||
@if(!empty($errors) )
|
||||
<p>The following import errors occurred:</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')}}<br/> {{ ctrans('texts.email_from') }}</p>
|
||||
|
||||
@if(!$whitelabel)
|
||||
@slot('footer')
|
||||
@component('email.components.footer', ['url' => 'https://invoiceninja.com', 'url_text' => '© InvoiceNinja'])
|
||||
For any info, please visit InvoiceNinja.
|
||||
@endcomponent
|
||||
@endslot
|
||||
@endif
|
||||
@endcomponent
|
||||
@endcomponent
|
||||
|
Loading…
x
Reference in New Issue
Block a user