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

Merge pull request #4927 from joshuadwire/v5-develop

Improve CSV import
This commit is contained in:
David Bomba 2021-02-17 20:17:33 +11:00 committed by GitHub
commit 86e0f906e9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
31 changed files with 1951 additions and 764 deletions

View File

@ -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::dispatch( $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
}
}
}

View File

@ -28,10 +28,12 @@ class ImportRequest extends Request
public function rules()
{
return [
'hash' => 'required|string',
'entity_type' => 'required|string',
'column_map' => 'required|array',
'skip_header' => 'required|boolean'
'import_type' => 'required',
'files' => 'required_without:hash|array|min:1|max:6',
'hash' => 'nullable|string',
'column_map' => 'required_with:hash|array',
'skip_header' => 'required_with:hash|boolean',
'files.*' => 'file|mimes:csv,txt',
];
}
}

View File

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

View File

@ -0,0 +1,51 @@
<?php
/**
* client Ninja (https://clientninja.com).
*
* @link https://github.com/clientninja/clientninja source repository
*
* @copyright Copyright (c) 2021. client Ninja LLC (https://clientninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Import\Definitions;
class ExpenseMap
{
public static function importable()
{
return [
0 => 'expense.vendor',
1 => 'expense.client',
2 => 'expense.project',
3 => 'expense.category',
4 => 'expense.amount',
5 => 'expense.currency',
6 => 'expense.date',
7 => 'expense.payment_type',
8 => 'expense.payment_date',
9 => 'expense.transaction_reference',
10 => 'expense.public_notes',
11 => 'expense.private_notes',
];
}
public static function import_keys()
{
return [
0 => 'texts.vendor',
1 => 'texts.client',
2 => 'texts.project',
3 => 'texts.category',
4 => 'texts.amount',
5 => 'texts.currency',
6 => 'texts.date',
7 => 'texts.payment_type',
8 => 'texts.payment_date',
9 => 'texts.transaction_reference',
10 => 'texts.public_notes',
11 => 'texts.private_notes',
];
}
}

View File

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

View File

@ -0,0 +1,61 @@
<?php
/**
* client Ninja (https://clientninja.com).
*
* @link https://github.com/clientninja/clientninja source repository
*
* @copyright Copyright (c) 2021. client Ninja LLC (https://clientninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Import\Definitions;
class VendorMap
{
public static function importable()
{
return [
0 => 'vendor.name',
1 => 'vendor.phone',
2 => 'vendor.id_number',
3 => 'vendor.vat_number',
4 => 'vendor.website',
5 => 'vendor.first_name',
6 => 'vendor.last_name',
7 => 'vendor.email',
8 => 'vendor.currency_id',
9 => 'vendor.public_notes',
10 => 'vendor.private_notes',
11 => 'vendor.address1',
12 => 'vendor.address2',
13 => 'vendor.city',
14 => 'vendor.state',
15 => 'vendor.postal_code',
16 => 'vendor.country_id',
];
}
public static function import_keys()
{
return [
0 => 'texts.name',
1 => 'texts.phone',
2 => 'texts.id_number',
3 => 'texts.vat_number',
4 => 'texts.website',
5 => 'texts.first_name',
6 => 'texts.last_name',
7 => 'texts.email',
8 => 'texts.currency',
9 => 'texts.public_notes',
10 => 'texts.private_notes',
11 => 'texts.address1',
12 => 'texts.address2',
13 => 'texts.city',
14 => 'texts.state',
15 => 'texts.postal_code',
16 => 'texts.country',
];
}
}

View File

@ -0,0 +1,6 @@
<?php
namespace App\Import;
class ImportException extends \Exception{
}

View File

@ -53,41 +53,32 @@ class BaseTransformer
return (isset($data[$field]) && $data[$field]) ? $data[$field] : '1';
}
public function getCurrencyByCode($data)
{
$code = array_key_exists('client.currency_id', $data) ? $data['client.currency_id'] : false;
public function getCurrencyByCode( $data, $key = 'client.currency_id' ) {
$code = array_key_exists( $key, $data ) ? $data[ $key ] : false;
if ($code) {
$currency = $this->maps['currencies']->where('code', $code)->first();
return $this->maps['currencies'][ $code ] ?? $this->maps['company']->settings->currency_id;
}
if ($currency) {
return $currency->id;
}
}
public function getClient($client_name, $client_email) {
$clients = $this->maps['company']->clients;
return $this->maps['company']->settings->currency_id;
}
$clients = $clients->where( 'name', $client_name );
public function getClient($client_name, $client_email)
{
$clients = $this->maps['company']->clients;
if ( $clients->count() >= 1 ) {
return $clients->first()->id;
}
$clients = $clients->where('name', $client_name);
if ( ! empty( $client_email ) ) {
$contacts = ClientContact::where( 'company_id', $this->maps['company']->id )
->where( 'email', $client_email );
if ($clients->count() >= 1) {
return $clients->first()->id;
}
if ( $contacts->count() >= 1 ) {
return $contacts->first()->client_id;
}
}
$contacts = ClientContact::where('company_id', $this->maps['company']->id)
->where('email', $client_email);
if ($contacts->count() >=1) {
return $contacts->first()->client_id;
}
return null;
}
return null;
}
@ -101,7 +92,7 @@ class BaseTransformer
{
$name = trim(strtolower($name));
return isset($this->maps[ENTITY_CLIENT][$name]);
return isset( $this->maps['client'][ $name ] );
}
/**
@ -113,7 +104,7 @@ class BaseTransformer
{
$name = trim(strtolower($name));
return isset($this->maps[ENTITY_VENDOR][$name]);
return isset( $this->maps['vendor'][ $name ] );
}
@ -126,7 +117,7 @@ class BaseTransformer
{
$key = trim(strtolower($key));
return isset($this->maps[ENTITY_PRODUCT][$key]);
return isset( $this->maps['product'][ $key ] );
}
@ -167,7 +158,7 @@ class BaseTransformer
{
$name = strtolower(trim($name));
return isset($this->maps[ENTITY_CLIENT][$name]) ? $this->maps[ENTITY_CLIENT][$name] : null;
return isset( $this->maps['client'][ $name ] ) ? $this->maps['client'][ $name ] : null;
}
/**
@ -322,7 +313,7 @@ class BaseTransformer
*/
public function getInvoiceNumber($number)
{
return $number ? str_pad(trim($number), 4, '0', STR_PAD_LEFT) : null;
return $number ? ltrim( trim( $number ), '0' ) : null;
}
/**
@ -334,7 +325,8 @@ class BaseTransformer
{
$invoiceNumber = $this->getInvoiceNumber($invoiceNumber);
$invoiceNumber = strtolower($invoiceNumber);
return isset($this->maps[ENTITY_INVOICE][$invoiceNumber]) ? $this->maps[ENTITY_INVOICE][$invoiceNumber] : null;
return isset( $this->maps['invoice'][ $invoiceNumber ] ) ? $this->maps['invoice'][ $invoiceNumber ] : null;
}
/**
@ -346,7 +338,8 @@ class BaseTransformer
{
$invoiceNumber = $this->getInvoiceNumber($invoiceNumber);
$invoiceNumber = strtolower($invoiceNumber);
return isset($this->maps['invoices'][$invoiceNumber]) ? $this->maps['invoices'][$invoiceNumber]->public_id : null;
return isset( $this->maps['invoice'][ $invoiceNumber ] ) ? $this->maps['invoices'][ $invoiceNumber ]->public_id : null;
}
/**
@ -359,7 +352,7 @@ class BaseTransformer
$invoiceNumber = $this->getInvoiceNumber($invoiceNumber);
$invoiceNumber = strtolower($invoiceNumber);
return isset($this->maps[ENTITY_INVOICE][$invoiceNumber]);
return $this->maps['invoice'][ $invoiceNumber ] ?? null;
}
/**
@ -372,7 +365,7 @@ class BaseTransformer
$invoiceNumber = $this->getInvoiceNumber($invoiceNumber);
$invoiceNumber = strtolower($invoiceNumber);
return isset($this->maps[ENTITY_INVOICE.'_'.ENTITY_CLIENT][$invoiceNumber]) ? $this->maps[ENTITY_INVOICE.'_'.ENTITY_CLIENT][$invoiceNumber] : null;
return $this->maps['invoice_client'][ $invoiceNumber ] ?? null;
}
/**
@ -384,18 +377,39 @@ class BaseTransformer
{
$name = strtolower(trim($name));
return isset($this->maps[ENTITY_VENDOR][$name]) ? $this->maps[ENTITY_VENDOR][$name] : null;
return $this->maps['vendor'][ $name ] ?? null;
}
/**
* @param $name
*
* @return null
*/
public function getExpenseCategoryId($name)
{
$name = strtolower(trim($name));
/**
* @param $name
*
* @return null
*/
public function getExpenseCategoryId( $name ) {
$name = strtolower( trim( $name ) );
return isset($this->maps[ENTITY_EXPENSE_CATEGORY][$name]) ? $this->maps[ENTITY_EXPENSE_CATEGORY][$name] : null;
}
return $this->maps['expense_category'][ $name ] ?? null;
}
/**
* @param $name
*
* @return null
*/
public function getProjectId( $name ) {
$name = strtolower( trim( $name ) );
return $this->maps['project'][ $name ] ?? null;
}
/**
* @param $name
*
* @return null
*/
public function getPaymentTypeId( $name ) {
$name = strtolower( trim( $name ) );
return $this->maps['payment_type'][ $name ] ?? null;
}
}

View File

@ -1,78 +0,0 @@
<?php
/**
* client Ninja (https://clientninja.com).
*
* @link https://github.com/clientninja/clientninja source repository
*
* @copyright Copyright (c) 2021. client Ninja LLC (https://clientninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Import\Transformers;
use Illuminate\Support\Str;
/**
* Class ClientTransformer.
*/
class ClientTransformer extends BaseTransformer
{
/**
* @param $data
*
* @return bool|Item
*/
public function transform($data)
{
if (isset($data->name) && $this->hasClient($data->name)) {
return false;
}
$settings = new \stdClass;
$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,
];
}
}

View File

@ -0,0 +1,79 @@
<?php
/**
* client Ninja (https://clientninja.com).
*
* @link https://github.com/clientninja/clientninja source repository
*
* @copyright Copyright (c) 2021. client Ninja LLC (https://clientninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Import\Transformers\Csv;
use App\Import\ImportException;
use App\Import\Transformers\BaseTransformer;
use Illuminate\Support\Str;
/**
* Class ClientTransformer.
*/
class ClientTransformer extends BaseTransformer
{
/**
* @param $data
*
* @return array|bool
*/
public function transform($data)
{
if (isset($data->name) && $this->hasClient($data->name)) {
throw new ImportException('Client already exists');
}
$settings = new \stdClass;
$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' => 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['client.country'] ) ? $this->getCountryId( $data['client.country']) : null,
'shipping_country_id' => isset($data['client.shipping_country'] ) ? $this->getCountryId( $data['client.shipping_country'] ) : null,
];
}
}

View File

@ -0,0 +1,36 @@
<?php
namespace App\Import\Transformers\Csv;
use App\Import\Transformers\BaseTransformer;
/**
* Class InvoiceTransformer.
*/
class ExpenseTransformer extends BaseTransformer {
/**
* @param $data
*
* @return bool|array
*/
public function transform( $data ) {
$clientId = isset( $data['expense.client'] ) ? $this->getClientId( $data['expense.client'] ) : null;
return [
'company_id' => $this->maps['company']->id,
'amount' => $this->getFloat( $data, 'expense.amount' ),
'currency_id' => $this->getCurrencyByCode( $data, 'expense.currency_id' ),
'vendor_id' => isset( $data['expense.vendor'] ) ? $this->getVendorId( $data['expense.vendor'] ) : null,
'client_id' => isset( $data['expense.client'] ) ? $this->getClientId( $data['expense.client'] ) : null,
'expense_date' => isset( $data['expense.date'] ) ? date( 'Y-m-d', strtotime( $data['expense.date'] ) ) : null,
'public_notes' => $this->getString( $data, 'expense.public_notes' ),
'private_notes' => $this->getString( $data, 'expense.private_notes' ),
'expense_category_id' => isset( $data['expense.category'] ) ? $this->getExpenseCategoryId( $data['expense.category'] ) : null,
'project_id' => isset( $data['expense.project'] ) ? $this->getProjectId( $data['expense.project'] ) : null,
'payment_type_id' => isset( $data['expense.payment_type'] ) ? $this->getPaymentTypeId( $data['expense.payment_type'] ) : null,
'payment_date' => isset( $data['expense.payment_date'] ) ? date( 'Y-m-d', strtotime( $data['expense.payment_date'] ) ) : null,
'transaction_reference' => $this->getString( $data, 'expense.transaction_reference' ),
'should_be_invoiced' => $clientId ? true : false,
];
}
}

View File

@ -0,0 +1,131 @@
<?php
/**
* client Ninja (https://clientninja.com).
*
* @link https://github.com/clientninja/clientninja source repository
*
* @copyright Copyright (c) 2021. client Ninja LLC (https://clientninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Import\Transformers\Csv;
use App\Import\ImportException;
use App\Import\Transformers\BaseTransformer;
use App\Models\Invoice;
/**
* Class InvoiceTransformer.
*/
class InvoiceTransformer extends BaseTransformer {
/**
* @param $data
*
* @return bool|array
*/
public function transform( $line_items_data ) {
$invoice_data = reset( $line_items_data );
if ( $this->hasInvoice( $invoice_data['invoice.number'] ) ) {
throw new ImportException( 'Invoice number already exists' );
}
$invoiceStatusMap = [
'sent' => Invoice::STATUS_SENT,
'draft' => Invoice::STATUS_DRAFT,
];
$transformed = [
'company_id' => $this->maps['company']->id,
'number' => $this->getString( $invoice_data, 'invoice.number' ),
'user_id' => $this->getString( $invoice_data, 'invoice.user_id' ),
'amount' => $amount = $this->getFloat( $invoice_data, 'invoice.amount' ),
'balance' => isset( $invoice_data['invoice.balance'] ) ? $this->getFloat( $invoice_data, 'invoice.balance' ) : $amount,
'client_id' => $this->getClient( $this->getString( $invoice_data, 'client.name' ), $this->getString( $invoice_data, 'client.email' ) ),
'discount' => $this->getFloat( $invoice_data, 'invoice.discount' ),
'po_number' => $this->getString( $invoice_data, 'invoice.po_number' ),
'date' => isset( $invoice_data['invoice.date'] ) ? date( 'Y-m-d', strtotime( $invoice_data['invoice.date'] ) ) : null,
'due_date' => isset( $invoice_data['invoice.due_date'] ) ? date( 'Y-m-d', strtotime( $invoice_data['invoice.due_date'] ) ) : null,
'terms' => $this->getString( $invoice_data, 'invoice.terms' ),
'public_notes' => $this->getString( $invoice_data, 'invoice.public_notes' ),
'is_sent' => $this->getString( $invoice_data, 'invoice.is_sent' ),
'private_notes' => $this->getString( $invoice_data, 'invoice.private_notes' ),
'tax_name1' => $this->getString( $invoice_data, 'invoice.tax_name1' ),
'tax_rate1' => $this->getFloat( $invoice_data, 'invoice.tax_rate1' ),
'tax_name2' => $this->getString( $invoice_data, 'invoice.tax_name2' ),
'tax_rate2' => $this->getFloat( $invoice_data, 'invoice.tax_rate2' ),
'tax_name3' => $this->getString( $invoice_data, 'invoice.tax_name3' ),
'tax_rate3' => $this->getFloat( $invoice_data, 'invoice.tax_rate3' ),
'custom_value1' => $this->getString( $invoice_data, 'invoice.custom_value1' ),
'custom_value2' => $this->getString( $invoice_data, 'invoice.custom_value2' ),
'custom_value3' => $this->getString( $invoice_data, 'invoice.custom_value3' ),
'custom_value4' => $this->getString( $invoice_data, 'invoice.custom_value4' ),
'footer' => $this->getString( $invoice_data, 'invoice.footer' ),
'partial' => $this->getFloat( $invoice_data, 'invoice.partial' ),
'partial_due_date' => $this->getString( $invoice_data, 'invoice.partial_due_date' ),
'custom_surcharge1' => $this->getString( $invoice_data, 'invoice.custom_surcharge1' ),
'custom_surcharge2' => $this->getString( $invoice_data, 'invoice.custom_surcharge2' ),
'custom_surcharge3' => $this->getString( $invoice_data, 'invoice.custom_surcharge3' ),
'custom_surcharge4' => $this->getString( $invoice_data, 'invoice.custom_surcharge4' ),
'exchange_rate' => $this->getString( $invoice_data, 'invoice.exchange_rate' ),
'status_id' => $invoiceStatusMap[ $status =
strtolower( $this->getString( $invoice_data, 'invoice.status' ) ) ] ??
Invoice::STATUS_SENT,
'viewed' => $status === 'viewed',
'archived' => $status === 'archived',
];
if ( isset( $invoice_data['payment.amount'] ) ) {
$transformed['payments'] = [
[
'date' => isset( $invoice_data['payment.date'] ) ? date( 'Y-m-d', strtotime( $invoice_data['payment.date'] ) ) : date( 'y-m-d' ),
'transaction_reference' => $this->getString( $invoice_data, 'payment.transaction_reference' ),
'amount' => $this->getFloat( $invoice_data, 'payment.amount' ),
],
];
} elseif ( $status === 'paid' ) {
$transformed['payments'] = [
[
'date' => isset( $invoice_data['payment.date'] ) ? date( 'Y-m-d', strtotime( $invoice_data['payment.date'] ) ) : date( 'y-m-d' ),
'transaction_reference' => $this->getString( $invoice_data, 'payment.transaction_reference' ),
'amount' => $this->getFloat( $invoice_data, 'invoice.amount' ),
],
];
} elseif ( isset( $transformed['amount'] ) && isset( $transformed['balance'] ) ) {
$transformed['payments'] = [
[
'date' => isset( $invoice_data['payment.date'] ) ? date( 'Y-m-d', strtotime( $invoice_data['payment.date'] ) ) : date( 'y-m-d' ),
'transaction_reference' => $this->getString( $invoice_data, 'payment.transaction_reference' ),
'amount' => $transformed['amount'] - $transformed['balance'],
],
];
}
$line_items = [];
foreach ( $line_items_data as $record ) {
$line_items[] = [
'quantity' => $this->getFloat( $record, 'item.quantity' ),
'cost' => $this->getFloat( $record, 'item.cost' ),
'product_key' => $this->getString( $record, 'item.product_key' ),
'notes' => $this->getString( $record, 'item.notes' ),
'discount' => $this->getFloat( $record, 'item.discount' ),
'is_amount_discount' => filter_var( $this->getString( $record, 'item.is_amount_discount' ), FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE ),
'tax_name1' => $this->getString( $record, 'item.tax_name1' ),
'tax_rate1' => $this->getFloat( $record, 'item.tax_rate1' ),
'tax_name2' => $this->getString( $record, 'item.tax_name2' ),
'tax_rate2' => $this->getFloat( $record, 'item.tax_rate2' ),
'tax_name3' => $this->getString( $record, 'item.tax_name3' ),
'tax_rate3' => $this->getFloat( $record, 'item.tax_rate3' ),
'custom_value1' => $this->getString( $record, 'item.custom_value1' ),
'custom_value2' => $this->getString( $record, 'item.custom_value2' ),
'custom_value3' => $this->getString( $record, 'item.custom_value3' ),
'custom_value4' => $this->getString( $record, 'item.custom_value4' ),
'type_id' => $this->getInvoiceTypeId( $record, 'item.type_id' ),
];
}
$transformed['line_items'] = $line_items;
return $transformed;
}
}

View File

@ -0,0 +1,64 @@
<?php
/**
* client Ninja (https://clientninja.com).
*
* @link https://github.com/clientninja/clientninja source repository
*
* @copyright Copyright (c) 2021. client Ninja LLC (https://clientninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Import\Transformers\Csv;
use App\Import\ImportException;
use App\Import\Transformers\BaseTransformer;
/**
* Class PaymentTransformer.
*/
class PaymentTransformer extends BaseTransformer {
/**
* @param $data
*
* @return array
*/
public function transform( $data ) {
$client_id =
$this->getClient( $this->getString( $data, 'payment.client_id' ), $this->getString( $data, 'payment.client_id' ) );
if ( empty( $client_id ) ) {
throw new ImportException( 'Could not find client.' );
}
$transformed = [
'company_id' => $this->maps['company']->id,
'number' => $this->getString( $data, 'payment.number' ),
'user_id' => $this->getString( $data, 'payment.user_id' ),
'amount' => $this->getFloat( $data, 'payment.amount' ),
'refunded' => $this->getFloat( $data, 'payment.refunded' ),
'applied' => $this->getFloat( $data, 'payment.applied' ),
'transaction_reference' => $this->getString( $data, 'payment.transaction_reference ' ),
'date' => $this->getString( $data, 'payment.date' ),
'private_notes' => $this->getString( $data, 'payment.private_notes' ),
'custom_value1' => $this->getString( $data, 'payment.custom_value1' ),
'custom_value2' => $this->getString( $data, 'payment.custom_value2' ),
'custom_value3' => $this->getString( $data, 'payment.custom_value3' ),
'custom_value4' => $this->getString( $data, 'payment.custom_value4' ),
'client_id' => $client_id,
];
if ( isset( $data['payment.invoice_number'] ) &&
$invoice_id = $this->getInvoiceId( $data['payment.invoice_number'] ) ) {
$transformed['invoices'] = [
[
'invoice_id' => $invoice_id,
'amount' => $transformed['amount'] ?? null,
],
];
}
return $transformed;
}
}

View File

@ -9,8 +9,8 @@
* @license https://opensource.org/licenses/AAL
*/
namespace App\Import\Transformers;
namespace App\Import\Transformers\Csv;
use App\Import\Transformers\BaseTransformer;
/**
* Class ProductTransformer.
*/
@ -19,7 +19,7 @@ class ProductTransformer extends BaseTransformer
/**
* @param $data
*
* @return bool|Item
* @return array
*/
public function transform($data)
{

View File

@ -0,0 +1,47 @@
<?php
namespace App\Import\Transformers\Csv;
use App\Import\ImportException;
use App\Import\Transformers\BaseTransformer;
/**
* Class VendorTransformer.
*/
class VendorTransformer extends BaseTransformer {
/**
* @param $data
*
* @return array|bool
*/
public function transform( $data ) {
if ( isset( $data->name ) && $this->hasVendor( $data->name ) ) {
throw new ImportException('Vendor already exists');
}
return [
'company_id' => $this->maps['company']->id,
'name' => $this->getString( $data, 'vendor.name' ),
'phone' => $this->getString( $data, 'vendor.phone' ),
'id_number' => $this->getString( $data, 'vendor.id_number' ),
'vat_number' => $this->getString( $data, 'vendor.vat_number' ),
'website' => $this->getString( $data, 'vendor.website' ),
'currency_id' => $this->getCurrencyByCode( $data, 'vendor.currency_id' ),
'public_notes' => $this->getString( $data, 'vendor.public_notes' ),
'private_notes' => $this->getString( $data, 'vendor.private_notes' ),
'address1' => $this->getString( $data, 'vendor.address1' ),
'address2' => $this->getString( $data, 'vendor.address2' ),
'city' => $this->getString( $data, 'vendor.city' ),
'state' => $this->getString( $data, 'vendor.state' ),
'postal_code' => $this->getString( $data, 'vendor.postal_code' ),
'vendor_contacts' => [
[
'first_name' => $this->getString( $data, 'vendor.first_name' ),
'last_name' => $this->getString( $data, 'vendor.last_name' ),
'email' => $this->getString( $data, 'vendor.email' ),
'phone' => $this->getString( $data, 'vendor.phone' ),
],
],
'country_id' => isset( $data['vendor.country_id'] ) ? $this->getCountryId( $data['vendor.country_id'] ) : null,
];
}
}

View File

@ -0,0 +1,55 @@
<?php
/**
* Invoice Ninja (https://clientninja.com).
*
* @link https://github.com/clientninja/clientninja source repository
*
* @copyright Copyright (c) 2021. client Ninja LLC (https://clientninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Import\Transformers\Freshbooks;
use App\Import\ImportException;
use App\Import\Transformers\BaseTransformer;
use Illuminate\Support\Str;
/**
* Class ClientTransformer.
*/
class ClientTransformer extends BaseTransformer {
/**
* @param $data
*
* @return array|bool
*/
public function transform( $data ) {
if ( isset( $data['Organization'] ) && $this->hasClient( $data['Organization'] ) ) {
throw new ImportException('Client already exists');
}
return [
'company_id' => $this->maps['company']->id,
'name' => $this->getString( $data, 'Organization' ),
'work_phone' => $this->getString( $data, 'Phone' ),
'address1' => $this->getString( $data, 'Street' ),
'city' => $this->getString( $data, 'City' ),
'state' => $this->getString( $data, 'Province/State' ),
'postal_code' => $this->getString( $data, 'Postal Code' ),
'country_id' => isset( $data['Country'] ) ? $this->getCountryId( $data['Country'] ) : null,
'private_notes' => $this->getString( $data, 'Notes' ),
'credit_balance' => 0,
'settings' => new \stdClass,
'client_hash' => Str::random( 40 ),
'contacts' => [
[
'first_name' => $this->getString( $data, 'First Name' ),
'last_name' => $this->getString( $data, 'Last Name' ),
'email' => $this->getString( $data, 'Email' ),
'phone' => $this->getString( $data, 'Phone' ),
],
],
];
}
}

View File

@ -0,0 +1,78 @@
<?php
/**
* client Ninja (https://clientninja.com).
*
* @link https://github.com/clientninja/clientninja source repository
*
* @copyright Copyright (c) 2021. client Ninja LLC (https://clientninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Import\Transformers\Freshbooks;
use App\Import\ImportException;
use App\Import\Transformers\BaseTransformer;
use App\Models\Invoice;
/**
* Class InvoiceTransformer.
*/
class InvoiceTransformer extends BaseTransformer {
/**
* @param $line_items_data
*
* @return bool|array
*/
public function transform( $line_items_data ) {
$invoice_data = reset( $line_items_data );
if ( $this->hasInvoice( $invoice_data['Invoice #'] ) ) {
throw new ImportException( 'Invoice number already exists' );
}
$invoiceStatusMap = [
'sent' => Invoice::STATUS_SENT,
'draft' => Invoice::STATUS_DRAFT,
];
$transformed = [
'company_id' => $this->maps['company']->id,
'client_id' => $this->getClient( $this->getString( $invoice_data, 'Client Name' ), null ),
'number' => $this->getString( $invoice_data, 'Invoice #' ),
'date' => isset( $invoice_data['Date Issued'] ) ? date( 'Y-m-d', strtotime( $invoice_data['Date Issued'] ) ) : null,
'currency_id' => $this->getCurrencyByCode( $invoice_data, 'Currency' ),
'amount' => 0,
'status_id' => $invoiceStatusMap[ $status =
strtolower( $this->getString( $invoice_data, 'Invoice Status' ) ) ] ?? Invoice::STATUS_SENT,
'viewed' => $status === 'viewed',
];
$line_items = [];
foreach ( $line_items_data as $record ) {
$line_items[] = [
'product_key' => $this->getString( $record, 'Item Name' ),
'notes' => $this->getString( $record, 'Item Description' ),
'cost' => $this->getFloat( $record, 'Rate' ),
'quantity' => $this->getFloat( $record, 'Quantity' ),
'discount' => $this->getFloat( $record, 'Discount Percentage' ),
'is_amount_discount' => false,
'tax_name1' => $this->getString( $record, 'Tax 1 Type' ),
'tax_rate1' => $this->getFloat( $record, 'Tax 1 Amount' ),
'tax_name2' => $this->getString( $record, 'Tax 2 Type' ),
'tax_rate2' => $this->getFloat( $record, 'Tax 2 Amount' ),
];
$transformed['amount'] += $this->getFloat( $record, 'Line Total' );
}
$transformed['line_items'] = $line_items;
if ( ! empty( $invoice_data['Date Paid'] ) ) {
$transformed['payments'] = [[
'date' => date( 'Y-m-d', strtotime( $invoice_data['Date Paid'] ) ),
'amount' => $transformed['amount'],
]];
}
return $transformed;
}
}

View File

@ -0,0 +1,89 @@
<?php
/**
* client Ninja (https://clientninja.com).
*
* @link https://github.com/clientninja/clientninja source repository
*
* @copyright Copyright (c) 2021. client Ninja LLC (https://clientninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Import\Transformers\Invoice2Go;
use App\Import\ImportException;
use App\Import\Transformers\BaseTransformer;
use App\Models\Invoice;
use Illuminate\Support\Str;
/**
* Class InvoiceTransformer.
*/
class InvoiceTransformer extends BaseTransformer {
/**
* @param $line_items_data
*
* @return bool|array
*/
public function transform( $invoice_data ) {
if ( $this->hasInvoice( $invoice_data['DocumentNumber'] ) ) {
throw new ImportException( 'Invoice number already exists' );
}
$invoiceStatusMap = [
'unsent' => Invoice::STATUS_DRAFT,
'sent' => Invoice::STATUS_SENT,
];
$transformed = [
'company_id' => $this->maps['company']->id,
'number' => $this->getString( $invoice_data, 'DocumentNumber' ),
'notes' => $this->getString( $invoice_data, 'Comment' ),
'date' => isset( $invoice_data['DocumentDate'] ) ? date( 'Y-m-d', strtotime( $invoice_data['DocumentDate'] ) ) : null,
'currency_id' => $this->getCurrencyByCode( $invoice_data, 'Currency' ),
'amount' => 0,
'status_id' => $invoiceStatusMap[ $status =
strtolower( $this->getString( $invoice_data, 'DocumentStatus' ) ) ] ?? Invoice::STATUS_SENT,
'viewed' => $status === 'viewed',
'line_items' => [
[
'amount' => $amount = $this->getFloat( $invoice_data, 'TotalAmount' ),
'quantity' => 1,
'discount' => $this->getFloat( $invoice_data, 'DiscountValue' ),
'is_amount_discount' => false,
],
],
];
$client_id =
$this->getClient( $this->getString( $invoice_data, 'Name' ), $this->getString( $invoice_data, 'EmailRecipient' ) );
if ( $client_id ) {
$transformed['client_id'] = $client_id;
} else {
$transformed['client'] = [
'name' => $this->getString( $invoice_data, 'Name' ),
'address1' => $this->getString( $invoice_data, 'DocumentRecipientAddress' ),
'shipping_address1' => $this->getString( $invoice_data, 'ShipAddress' ),
'credit_balance' => 0,
'settings' => new \stdClass,
'client_hash' => Str::random( 40 ),
'contacts' => [
[
'email' => $this->getString( $invoice_data, 'Email' ),
],
],
];
}
if ( ! empty( $invoice_data['Date Paid'] ) ) {
$transformed['payments'] = [
[
'date' => date( 'Y-m-d', strtotime( $invoice_data['DatePaid'] ) ),
'amount' => $this->getFloat( $invoice_data, 'Payments' ),
],
];
}
return $transformed;
}
}

View File

@ -1,46 +0,0 @@
<?php
/**
* client Ninja (https://clientninja.com).
*
* @link https://github.com/clientninja/clientninja source repository
*
* @copyright Copyright (c) 2021. client Ninja LLC (https://clientninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Import\Transformers;
/**
* Class InvoiceItemTransformer.
*/
class InvoiceItemTransformer extends BaseTransformer
{
/**
* @param $data
*
* @return bool|Item
*/
public function transform($data)
{
return [
'quantity' => $this->getFloat($data, 'item.quantity'),
'cost' => $this->getFloat($data, 'item.cost'),
'product_key' => $this->getString($data, 'item.product_key'),
'notes' => $this->getString($data, 'item.notes'),
'discount' => $this->getFloat($data, 'item.discount'),
'is_amount_discount' => $this->getString($data, 'item.is_amount_discount'),
'tax_name1' => $this->getString($data, 'item.tax_name1'),
'tax_rate1' => $this->getFloat($data, 'item.tax_rate1'),
'tax_name2' => $this->getString($data, 'item.tax_name2'),
'tax_rate2' => $this->getFloat($data, 'item.tax_rate2'),
'tax_name3' => $this->getString($data, 'item.tax_name3'),
'tax_rate3' => $this->getFloat($data, 'item.tax_rate3'),
'custom_value1' => $this->getString($data, 'item.custom_value1'),
'custom_value2' => $this->getString($data, 'item.custom_value2'),
'custom_value3' => $this->getString($data, 'item.custom_value3'),
'custom_value4' => $this->getString($data, 'item.custom_value4'),
'type_id' => $this->getInvoiceTypeId($data, 'item.type_id'),
];
}
}

View File

@ -1,61 +0,0 @@
<?php
/**
* client Ninja (https://clientninja.com).
*
* @link https://github.com/clientninja/clientninja source repository
*
* @copyright Copyright (c) 2021. client Ninja LLC (https://clientninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Import\Transformers;
/**
* Class InvoiceTransformer.
*/
class InvoiceTransformer extends BaseTransformer
{
/**
* @param $data
*
* @return bool|Item
*/
public function transform($data)
{
return [
'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'),
'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'),
'date' => $this->getString($data, 'invoice.date'),
'due_date' => $this->getString($data, 'invoice.due_date'),
'terms' => $this->getString($data, 'invoice.terms'),
'public_notes' => $this->getString($data, 'invoice.public_notes'),
'is_sent' => $this->getString($data, 'invoice.is_sent'),
'private_notes' => $this->getString($data, 'invoice.private_notes'),
'tax_name1' => $this->getString($data, 'invoice.tax_name1'),
'tax_rate1' => $this->getFloat($data, 'invoice.tax_rate1'),
'tax_name2' => $this->getString($data, 'invoice.tax_name2'),
'tax_rate2' => $this->getFloat($data, 'invoice.tax_rate2'),
'tax_name3' => $this->getString($data, 'invoice.tax_name3'),
'tax_rate3' => $this->getFloat($data, 'invoice.tax_rate3'),
'custom_value1' => $this->getString($data, 'invoice.custom_value1'),
'custom_value2' => $this->getString($data, 'invoice.custom_value2'),
'custom_value3' => $this->getString($data, 'invoice.custom_value3'),
'custom_value4' => $this->getString($data, 'invoice.custom_value4'),
'footer' => $this->getString($data, 'invoice.footer'),
'partial' => $this->getFloat($data, 'invoice.partial'),
'partial_due_date' => $this->getString($data, 'invoice.partial_due_date'),
'custom_surcharge1' => $this->getString($data, 'invoice.custom_surcharge1'),
'custom_surcharge2' => $this->getString($data, 'invoice.custom_surcharge2'),
'custom_surcharge3' => $this->getString($data, 'invoice.custom_surcharge3'),
'custom_surcharge4' => $this->getString($data, 'invoice.custom_surcharge4'),
'exchange_rate' => $this->getString($data, 'invoice.exchange_rate'),
];
}
}

View File

@ -0,0 +1,48 @@
<?php
/**
* Invoice Ninja (https://clientninja.com).
*
* @link https://github.com/clientninja/clientninja source repository
*
* @copyright Copyright (c) 2021. client Ninja LLC (https://clientninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Import\Transformers\Invoicely;
use App\Import\ImportException;
use App\Import\Transformers\BaseTransformer;
use Illuminate\Support\Str;
/**
* Class ClientTransformer.
*/
class ClientTransformer extends BaseTransformer {
/**
* @param $data
*
* @return array|bool
*/
public function transform( $data ) {
if ( isset( $data['Client Name'] ) && $this->hasClient( $data['Client Name'] ) ) {
throw new ImportException('Client already exists');
}
return [
'company_id' => $this->maps['company']->id,
'name' => $this->getString( $data, 'Client Name' ),
'work_phone' => $this->getString( $data, 'Phone' ),
'country_id' => isset( $data['Country'] ) ? $this->getCountryIdBy2( $data['Country'] ) : null,
'credit_balance' => 0,
'settings' => new \stdClass,
'client_hash' => Str::random( 40 ),
'contacts' => [
[
'email' => $this->getString( $data, 'Email' ),
'phone' => $this->getString( $data, 'Phone' ),
],
],
];
}
}

View File

@ -0,0 +1,58 @@
<?php
/**
* client Ninja (https://clientninja.com).
*
* @link https://github.com/clientninja/clientninja source repository
*
* @copyright Copyright (c) 2021. client Ninja LLC (https://clientninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Import\Transformers\Invoicely;
use App\Import\ImportException;
use App\Import\Transformers\BaseTransformer;
use App\Models\Invoice;
/**
* Class InvoiceTransformer.
*/
class InvoiceTransformer extends BaseTransformer {
/**
* @param $data
*
* @return bool|array
*/
public function transform( $data ) {
if ( $this->hasInvoice( $data['Details'] ) ) {
throw new ImportException( 'Invoice number already exists' );
}
$transformed = [
'company_id' => $this->maps['company']->id,
'client_id' => $this->getClient( $this->getString( $data, 'Client' ), null ),
'number' => $this->getString( $data, 'Details' ),
'date' => isset( $data['Date'] ) ? date( 'Y-m-d', strtotime( $data['Date'] ) ) : null,
'due_date' => isset( $data['Due'] ) ? date( 'Y-m-d', strtotime( $data['Due'] ) ) : null,
'status_id' => Invoice::STATUS_SENT,
'line_items' => [
[
'cost' => $amount = $this->getFloat( $data, 'Total' ),
'quantity' => 1,
],
],
];
if ( strtolower( $data['Status'] ) === 'paid' ) {
$transformed['payments'] = [
[
'date' => date( 'Y-m-d' ),
'amount' => $amount,
],
];
}
return $transformed;
}
}

View File

@ -1,46 +0,0 @@
<?php
/**
* client Ninja (https://clientninja.com).
*
* @link https://github.com/clientninja/clientninja source repository
*
* @copyright Copyright (c) 2021. client Ninja LLC (https://clientninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Import\Transformers;
/**
* Class PaymentTransformer.
*/
class PaymentTransformer extends BaseTransformer
{
/**
* @param $data
*
* @return bool|Item
*/
public function transform($data)
{
return [
'company_id' => $this->maps['company']->id,
'number' => $this->getString($data, 'payment.number'),
'user_id' => $this->getString($data, 'payment.user_id'),
'amount' => $this->getFloat($data, 'payment.amount'),
'refunded' => $this->getFloat($data, 'payment.refunded'),
'applied' => $this->getFloat($data, 'payment.applied'),
'transaction_reference' => $this->getString($data, 'payment.transaction_reference '),
'date' => $this->getString($data, 'payment.date'),
'private_notes' => $this->getString($data, 'payment.private_notes'),
'number' => $this->getString($data, 'number'),
'custom_value1' => $this->getString($data, 'custom_value1'),
'custom_value2' => $this->getString($data, 'custom_value2'),
'custom_value3' => $this->getString($data, 'custom_value3'),
'custom_value4' => $this->getString($data, 'custom_value4'),
'client_id' => $this->getString($data, 'client_id'),
'invoice_number' => $this->getString($data, 'payment.invoice_number'),
'method' => $this
];
}
}

View File

@ -0,0 +1,74 @@
<?php
/**
* Invoice Ninja (https://clientninja.com).
*
* @link https://github.com/clientninja/clientninja source repository
*
* @copyright Copyright (c) 2021. client Ninja LLC (https://clientninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Import\Transformers\Waveaccounting;
use App\Import\ImportException;
use App\Import\Transformers\BaseTransformer;
use Illuminate\Support\Str;
/**
* Class ClientTransformer.
*/
class ClientTransformer extends BaseTransformer {
/**
* @param $data
*
* @return array|bool
*/
public function transform( $data ) {
if ( isset( $data['customer_name'] ) && $this->hasClient( $data['customer_name'] ) ) {
throw new ImportException('Client already exists');
}
$settings = new \stdClass;
$settings->currency_id = (string) $this->getCurrencyByCode( $data, 'customer_currency' );
if ( strval( $data['Payment Terms'] ?? '' ) > 0 ) {
$settings->payment_terms = $data['Payment Terms'];
}
return [
'company_id' => $this->maps['company']->id,
'name' => $this->getString( $data, 'customer_name' ),
'number' => $this->getString( $data, 'account_number' ),
'work_phone' => $this->getString( $data, 'phone' ),
'website' => $this->getString( $data, 'website' ),
'country_id' => !empty( $data['country'] ) ? $this->getCountryId( $data['country'] ) : null,
'state' => $this->getString( $data, 'province/state' ),
'address1' => $this->getString( $data, 'address_line_1' ),
'address2' => $this->getString( $data, 'address_line_2' ),
'city' => $this->getString( $data, 'city' ),
'postal_code' => $this->getString( $data, 'postal_code/zip_code' ),
'shipping_country_id' => !empty( $data['ship-to_country'] ) ? $this->getCountryId( $data['country'] ) : null,
'shipping_state' => $this->getString( $data, 'ship-to_province/state' ),
'shipping_address1' => $this->getString( $data, 'ship-to_address_line_1' ),
'shipping_address2' => $this->getString( $data, 'ship-to_address_line_2' ),
'shipping_city' => $this->getString( $data, 'ship-to_city' ),
'shipping_postal_code' => $this->getString( $data, 'ship-to_postal_code/zip_code' ),
'public_notes' => $this->getString( $data, 'delivery_instructions' ),
'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, 'email' ),
'phone' => $this->getString( $data, 'phone' ),
],
],
];
}
}

View File

@ -0,0 +1,80 @@
<?php
/**
* client Ninja (https://clientninja.com).
*
* @link https://github.com/clientninja/clientninja source repository
*
* @copyright Copyright (c) 2021. client Ninja LLC (https://clientninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Import\Transformers\Waveaccounting;
use App\Import\ImportException;
use App\Import\Transformers\BaseTransformer;
use App\Models\Invoice;
/**
* Class InvoiceTransformer.
*/
class InvoiceTransformer extends BaseTransformer {
/**
* @param $line_items_data
*
* @return bool|array
*/
public function transform( $line_items_data ) {
$invoice_data = reset( $line_items_data );
if ( $this->hasInvoice( $invoice_data['Invoice Number'] ) ) {
throw new ImportException( 'Invoice number already exists' );
}
$transformed = [
'company_id' => $this->maps['company']->id,
'client_id' => $this->getClient( $customer_name = $this->getString( $invoice_data, 'Customer' ), null ),
'number' => $invoice_number = $this->getString( $invoice_data, 'Invoice Number' ),
'date' => isset( $invoice_data['Invoice Date'] ) ? date( 'Y-m-d', strtotime( $invoice_data['Transaction Date'] ) ) : null,
'currency_id' => $this->getCurrencyByCode( $invoice_data, 'Currency' ),
'status_id' => Invoice::STATUS_SENT,
];
$line_items = [];
$payments = [];
foreach ( $line_items_data as $record ) {
if ( $record['Account Type'] === 'Income' ) {
$description = $this->getString( $record, 'Transaction Line Description' );
// Remove duplicate data from description
if ( substr( $description, 0, strlen( $customer_name ) + 3 ) === $customer_name . ' - ' ) {
$description = substr( $description, strlen( $customer_name ) + 3 );
}
if ( substr( $description, 0, strlen( $invoice_number ) + 3 ) === $invoice_number . ' - ' ) {
$description = substr( $description, strlen( $invoice_number ) + 3 );
}
$line_items[] = [
'notes' => $description,
'cost' => $this->getFloat( $record, 'Amount Before Sales Tax' ),
'tax_name1' => $this->getString( $record, 'Sales Tax Name' ),
'tax_rate1' => $this->getFloat( $record, 'Sales Tax Amount' ),
'quantity' => 1,
];
} elseif ( $record['Account Type'] === 'System Receivable Invoice' ) {
// This is a payment
$payments[] = [
'date' => date( 'Y-m-d', strtotime( $invoice_data['Transaction Date'] ) ),
'amount' => $this->getFloat( $record, 'Amount (One column)' ),
];
}
}
$transformed['line_items'] = $line_items;
$transformed['payments'] = $payments;
return $transformed;
}
}

View File

@ -0,0 +1,72 @@
<?php
/**
* Invoice Ninja (https://clientninja.com).
*
* @link https://github.com/clientninja/clientninja source repository
*
* @copyright Copyright (c) 2021. client Ninja LLC (https://clientninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Import\Transformers\Zoho;
use App\Import\ImportException;
use App\Import\Transformers\BaseTransformer;
use Illuminate\Support\Str;
/**
* Class ClientTransformer.
*/
class ClientTransformer extends BaseTransformer {
/**
* @param $data
*
* @return array|bool
*/
public function transform( $data ) {
if ( isset( $data['Company Name'] ) && $this->hasClient( $data['Company Name'] ) ) {
throw new ImportException( 'Client already exists' );
}
$settings = new \stdClass;
$settings->currency_id = (string) $this->getCurrencyByCode( $data, 'Currency' );
if ( strval( $data['Payment Terms'] ?? '' ) > 0 ) {
$settings->payment_terms = $data['Payment Terms'];
}
return [
'company_id' => $this->maps['company']->id,
'name' => $this->getString( $data, 'Company Name' ),
'work_phone' => $this->getString( $data, 'Phone' ),
'private_notes' => $this->getString( $data, 'Notes' ),
'website' => $this->getString( $data, 'Website' ),
'address1' => $this->getString( $data, 'Billing Address' ),
'address2' => $this->getString( $data, 'Billing Street2' ),
'city' => $this->getString( $data, 'Billing City' ),
'state' => $this->getString( $data, 'Billing State' ),
'postal_code' => $this->getString( $data, 'Billing Code' ),
'country_id' => isset( $data['Billing Country'] ) ? $this->getCountryId( $data['Billing Country'] ) : null,
'shipping_address1' => $this->getString( $data, 'Shipping Address' ),
'shipping_address2' => $this->getString( $data, 'Shipping Street2' ),
'shipping_city' => $this->getString( $data, 'Shipping City' ),
'shipping_state' => $this->getString( $data, 'Shipping State' ),
'shipping_postal_code' => $this->getString( $data, 'Shipping Code' ),
'shipping_country_id' => isset( $data['Shipping Country'] ) ? $this->getCountryId( $data['Shipping Country'] ) : null,
'credit_balance' => 0,
'settings' => $settings,
'client_hash' => Str::random( 40 ),
'contacts' => [
[
'first_name' => $this->getString( $data, 'First Name' ),
'last_name' => $this->getString( $data, 'Last Name' ),
'email' => $this->getString( $data, 'Email' ),
'phone' => $this->getString( $data, 'Phone' ),
],
],
];
}
}

View File

@ -0,0 +1,77 @@
<?php
/**
* client Ninja (https://clientninja.com).
*
* @link https://github.com/clientninja/clientninja source repository
*
* @copyright Copyright (c) 2021. client Ninja LLC (https://clientninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Import\Transformers\Zoho;
use App\Import\ImportException;
use App\Import\Transformers\BaseTransformer;
use App\Models\Invoice;
/**
* Class InvoiceTransformer.
*/
class InvoiceTransformer extends BaseTransformer {
/**
* @param $line_items_data
*
* @return bool|array
*/
public function transform( $line_items_data ) {
$invoice_data = reset( $line_items_data );
if ( $this->hasInvoice( $invoice_data['Invoice Number'] ) ) {
throw new ImportException( 'Invoice number already exists' );
}
$invoiceStatusMap = [
'sent' => Invoice::STATUS_SENT,
'draft' => Invoice::STATUS_DRAFT,
];
$transformed = [
'company_id' => $this->maps['company']->id,
'client_id' => $this->getClient( $this->getString( $invoice_data, 'Company Name' ), null ),
'number' => $this->getString( $invoice_data, 'Invoice Number' ),
'date' => isset( $invoice_data['Invoice Date'] ) ? date( 'Y-m-d', strtotime( $invoice_data['Invoice Date'] ) ) : null,
'due_date' => isset( $invoice_data['Due Date'] ) ? date( 'Y-m-d', strtotime( $invoice_data['Due Date'] ) ) : null,
'po_number' => $this->getString( $invoice_data, 'PurchaseOrder' ),
'public_notes' => $this->getString( $invoice_data, 'Notes' ),
'currency_id' => $this->getCurrencyByCode( $invoice_data, 'Currency' ),
'amount' => $this->getFloat( $invoice_data, 'Total' ),
'balance' => $this->getFloat( $invoice_data, 'Balance' ),
'status_id' => $invoiceStatusMap[ $status =
strtolower( $this->getString( $invoice_data, 'Invoice Status' ) ) ] ?? Invoice::STATUS_SENT,
'viewed' => $status === 'viewed',
];
$line_items = [];
foreach ( $line_items_data as $record ) {
$line_items[] = [
'product_key' => $this->getString( $record, 'Item Name' ),
'notes' => $this->getString( $record, 'Item Description' ),
'cost' => $this->getFloat( $record, 'Item Price' ),
'quantity' => $this->getFloat( $record, 'Quantity' ),
'discount' => $this->getFloat( $record, 'Discount Amount' ),
'is_amount_discount' => true,
];
}
$transformed['line_items'] = $line_items;
if ( $transformed['balance'] < $transformed['amount'] ) {
$transformed['payments'] = [[
'date' => date( 'Y-m-d' ),
'amount' => $transformed['amount'] - $transformed['balance'],
]];
}
return $transformed;
}
}

View File

@ -13,26 +13,29 @@ namespace App\Jobs\Import;
use App\Factory\ClientFactory;
use App\Factory\InvoiceFactory;
use App\Factory\ProductFactory;
use App\Http\Requests\Client\StoreClientRequest;
use App\Factory\PaymentFactory;
use App\Http\Requests\Invoice\StoreInvoiceRequest;
use App\Http\Requests\Product\StoreProductRequest;
use App\Import\Transformers\ClientTransformer;
use App\Import\Transformers\InvoiceItemTransformer;
use App\Import\Transformers\InvoiceTransformer;
use App\Import\Transformers\ProductTransformer;
use App\Import\ImportException;
use App\Import\Transformers\BaseTransformer;
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\ExpenseCategory;
use App\Models\Invoice;
use App\Models\PaymentType;
use App\Models\Product;
use App\Models\Project;
use App\Models\TaxRate;
use App\Models\User;
use App\Repositories\ClientContactRepository;
use App\Models\Vendor;
use App\Repositories\ClientRepository;
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,329 +45,547 @@ 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;
use Symfony\Component\HttpFoundation\ParameterBag;
use Symfony\Component\HttpFoundation\Request;
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->import_type = $request['import_type'];
$this->skip_header = $request['skip_header'] ?? null;
$this->column_map = $request['column_map'] ?? null;
}
$this->hash = $request['hash'];
/**
* Execute the job.
*
*
* @return void
*/
public function handle() {
$this->entity_type = $request['entity_type'];
MultiDB::setDb( $this->company->db );
$this->skip_header = $request['skip_header'];
$this->company->owner()->setCompany( $this->company );
Auth::login( $this->company->owner(), true );
$this->column_map = $request['column_map'];
}
$this->buildMaps();
/**
* Execute the job.
*
*
* @return void
*/
public function handle()
{
MultiDB::setDb($this->company->db);
nlog( "import " . $this->import_type );
foreach ( [ 'client', 'product', 'invoice', 'payment', 'vendor', 'expense' ] as $entityType ) {
$csvData = $this->getCsvData( $entityType );
$this->company->owner()->setCompany($this->company);
Auth::login($this->company->owner(), true);
if ( ! empty( $csvData ) ) {
$importFunction = "import" . Str::plural( Str::title( $entityType ) );
$preTransformFunction = "preTransform" . Str::title( $this->import_type );
$this->buildMaps();
if ( method_exists( $this, $preTransformFunction ) ) {
$csvData = $this->$preTransformFunction( $csvData, $entityType );
}
//sort the array by key
ksort($this->column_map);
if ( empty( $csvData ) ) {
continue;
}
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
];
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 );
}
}
}
//nlog(print_r($data, 1));
$data = [
'errors' => $this->error_array,
'company' => $this->company,
];
MailRouter::dispatch(new ImportCompleted($data), $this->company, auth()->user());
}
MailRouter::dispatch( new ImportCompleted( $data ), $this->company, auth()->user() );
}
public function failed($exception)
{
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
private function preTransformCsv( $csvData, $entityType ) {
if ( empty( $this->column_map[ $entityType ] ) ) {
return false;
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
if ( $this->skip_header ) {
array_shift( $csvData );
}
//sort the array by key
$keys = $this->column_map[ $entityType ];
ksort( $keys );
private function importInvoice()
{
$invoice_transformer = new InvoiceTransformer($this->maps);
$csvData = array_map( function ( $row ) use ( $keys ) {
return array_combine( $keys, array_intersect_key( $row, $keys ) );
}, $csvData );
$records = $this->getCsvData();
if ( $entityType === 'invoice' ) {
$csvData = $this->groupInvoices( $csvData, 'invoice.number' );
}
$invoice_number_key = array_search('Invoice Number', reset($records));
return $csvData;
}
if ($this->skip_header) {
array_shift($records);
}
private function preTransformFreshbooks( $csvData, $entityType ) {
$csvData = $this->mapCSVHeaderToKeys( $csvData );
if (!$invoice_number_key) {
nlog("no invoice number to use as key - returning");
return;
}
if ( $entityType === 'invoice' ) {
$csvData = $this->groupInvoices( $csvData, 'Invoice #' );
}
$unique_invoices = [];
//get an array of unique invoice numbers
foreach ($records as $key => $value) {
$unique_invoices[] = $value[$invoice_number_key];
}
foreach ($unique_invoices as $unique) {
$invoices = array_filter($records, function ($value) use ($invoice_number_key, $unique) {
return $value[$invoice_number_key] == $unique;
});
$keys = $this->column_map;
$values = array_intersect_key(reset($invoices), $this->column_map);
$invoice_data = array_combine($keys, $values);
$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
}
}
}
return $data;
}
return $csvData;
}
private function preTransformInvoicely( $csvData, $entityType ) {
$csvData = $this->mapCSVHeaderToKeys( $csvData );
return $csvData;
}
private function preTransformInvoice2go( $csvData, $entityType ) {
$csvData = $this->mapCSVHeaderToKeys( $csvData );
return $csvData;
}
private function preTransformZoho( $csvData, $entityType ) {
$csvData = $this->mapCSVHeaderToKeys( $csvData );
if ( $entityType === 'invoice' ) {
$csvData = $this->groupInvoices( $csvData, 'Invoice Number' );
}
return $csvData;
}
private function preTransformWaveaccounting( $csvData, $entityType ) {
$csvData = $this->mapCSVHeaderToKeys( $csvData );
if ( $entityType === 'invoice' ) {
$csvData = $this->groupInvoices( $csvData, 'Invoice Number' );
}
return $csvData;
}
private function groupInvoices( $csvData, $key ) {
// Group by invoice.
$grouped = [];
foreach ( $csvData as $line_item ) {
if ( empty( $line_item[ $key ] ) ) {
$this->error_array['invoice'][] = [ 'invoice' => $line_item, 'error' => 'No invoice number' ];
} else {
$grouped[ $line_item[ $key ] ][] = $line_item;
}
}
return $grouped;
}
private function mapCSVHeaderToKeys( $csvData ) {
$keys = array_shift( $csvData );
return array_map( function ( $values ) use ( $keys ) {
return array_combine( $keys, $values );
}, $csvData );
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
private function importInvoices( $invoices ) {
$invoice_transformer = $this->getTransformer( 'invoice' );
/** @var PaymentRepository $payment_repository */
$payment_repository = app()->make( PaymentRepository::class );
/** @var ClientRepository $client_repository */
$client_repository = app()->make( ClientRepository::class );
foreach ( $invoices as $raw_invoice ) {
try {
$invoice_data = $invoice_transformer->transform( $raw_invoice );
$invoice_repository = new InvoiceRepository();
$invoice_data['line_items'] = $this->cleanItems( $invoice_data['line_items'] ?? [] );
// If we don't have a client ID, but we do have client data, go ahead and create the client.
if ( empty( $invoice_data['client_id'] ) && ! empty( $invoice_data['client'] ) ) {
$client_data = $invoice_data['client'];
$client_data['user_id'] = $this->getUserIDForRecord( $invoice_data );
$client_repository->save(
$client_data,
$client = ClientFactory::create( $this->company->id, $client_data['user_id'] )
);
$invoice_data['client_id'] = $client->id;
unset( $invoice_data['client'] );
}
$validator = Validator::make( $invoice_data, ( new StoreInvoiceRequest() )->rules() );
if ( $validator->fails() ) {
$this->error_array['invoice'][] =
[ 'invoice' => $invoice_data, 'error' => $validator->errors()->all() ];
} else {
$invoice = InvoiceFactory::create( $this->company->id, $this->getUserIDForRecord( $invoice_data ) );
if ( ! empty( $invoice_data['status_id'] ) ) {
$invoice->status_id = $invoice_data['status_id'];
}
$invoice_repository->save( $invoice_data, $invoice );
$this->addInvoiceToMaps( $invoice );
// 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 ( $this->import_type !== 'csv' || empty( $this->column_map['payment'] ) ) {
// Check for payment columns
if ( ! empty( $invoice_data['payments'] ) ) {
foreach ( $invoice_data['payments'] as $payment_data ) {
$payment_data['user_id'] = $invoice->user_id;
$payment_data['client_id'] = $invoice->client_id;
$payment_data['invoices'] = [
[
'invoice_id' => $invoice->id,
'amount' => $payment_data['amount'] ?? null,
],
];
$payment_repository->save(
$payment_data,
PaymentFactory::create( $this->company->id, $invoice->user_id, $invoice->client_id )
);
}
}
}
$this->actionInvoiceStatus( $invoice, $invoice_data, $invoice_repository );
}
} catch ( \Exception $ex ) {
if ( $ex instanceof ImportException ) {
$message = $ex->getMessage();
} else {
report( $ex );
$message = 'Unknown error';
}
$this->error_array['invoice'][] = [ 'invoice' => $raw_invoice, 'error' => $message ];
}
}
}
private function actionInvoiceStatus( $invoice, $invoice_data, $invoice_repository ) {
if ( ! empty( $invoice_data['archived'] ) ) {
$invoice_repository->archive( $invoice );
$invoice->fresh();
}
if ( ! empty( $invoice_data['viewed'] ) ) {
$invoice = $invoice->service()->markViewed()->save();
}
if ( $invoice->status_id === Invoice::STATUS_SENT ) {
$invoice = $invoice->service()->markSent()->save();
}
if ( $invoice->status_id <= Invoice::STATUS_SENT && $invoice->amount > 0 ) {
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_name = "\\App\\Http\\Requests\\${formatted_entity_type}\\Store${formatted_entity_type}Request";
$repository_name = '\\App\\Repositories\\' . $formatted_entity_type . 'Repository';
$factoryName = '\\App\\Factory\\' . $formatted_entity_type . 'Factory';
$repository = app()->make( $repository_name );
$transformer = $this->getTransformer( $entity_type );
foreach ( $records as $record ) {
try {
$entity = $transformer->transform( $record );
/** @var \App\Http\Requests\Request $request */
$request = new $request_name();
// Pass entity data to request so it can be validated
$request->query = $request->request = new ParameterBag( $entity );
$validator = Validator::make( $entity, $request->rules() );
if ( $validator->fails() ) {
$this->error_array[ $entity_type ][] =
[ $entity_type => $record, 'error' => $validator->errors()->all() ];
} else {
$entity =
$repository->save(
array_diff_key( $entity, [ 'user_id' => false ] ),
$factoryName::create( $this->company->id, $this->getUserIDForRecord( $entity ) ) );
$entity->save();
if ( method_exists( $this, 'add' . $formatted_entity_type . 'ToMaps' ) ) {
$this->{'add' . $formatted_entity_type . 'ToMaps'}( $entity );
}
}
} catch ( \Exception $ex ) {
if ( $ex instanceof ImportException ) {
$message = $ex->getMessage();
} else {
report( $ex );
$message = 'Unknown error';
}
$this->error_array[ $entity_type ][] = [ $entity_type => $record, 'error' => $message ];
}
}
}
/**
* @param $entity_type
*
* @return BaseTransformer
*/
private function getTransformer( $entity_type ) {
$formatted_entity_type = Str::title( $entity_type );
$formatted_import_type = Str::title( $this->import_type );
$transformer_name =
'\\App\\Import\\Transformers\\' . $formatted_import_type . '\\' . $formatted_entity_type . 'Transformer';
return new $transformer_name( $this->maps );
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
private function buildMaps() {
$this->maps = [
'company' => $this->company,
'client' => [],
'contact' => [],
'invoice' => [],
'invoice_client' => [],
'product' => [],
'countries' => [],
'countries2' => [],
'currencies' => [],
'client_ids' => [],
'invoice_ids' => [],
'vendors' => [],
'expense_categories' => [],
'payment_types' => [],
'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 );
}
$projects = Project::scope()->get();
foreach ( $projects as $project ) {
$this->addProjectToMaps( $projects );
}
$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;
}
$payment_types = PaymentType::all();
foreach ( $payment_types as $payment_type ) {
$this->maps['payment_types'][ strtolower( $payment_type->name ) ] = $payment_type->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->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;
}
}
/**
* @param Project $project
*/
private function addProjectToMaps( Project $project ) {
if ( $key = strtolower( trim( $project->name ) ) ) {
$this->maps['project'][ $key ] = $project;
}
}
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['user_id'] ) ) {
return $this->findUser( $record['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;
}
}

View File

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

View File

@ -27,6 +27,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;
@ -88,6 +95,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;

View File

@ -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
@ -74,18 +74,34 @@
@endif
<p><b>Data Quality:</b></p>
<p> {!! $check_data !!} </p>
@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' => '&copy; InvoiceNinja'])
For any info, please visit InvoiceNinja.
@endcomponent
@endslot
@endif
@endcomponent
@endcomponent