From a0439f23b245ff3d9373bc9a0fc6463bae226388 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 10 Feb 2022 13:02:02 +1100 Subject: [PATCH 1/7] Wave tests --- app/Import/Providers/BaseImport.php | 28 +- app/Import/Providers/Csv.php | 21 -- app/Import/Providers/ImportInterface.php | 2 - app/Import/Providers/Wave.php | 79 ++++- app/Import/Transformer/BaseTransformer.php | 22 ++ .../Transformer/Csv/ClientTransformer.php | 2 +- .../Transformer/Wave/ClientTransformer.php | 74 ++++ .../Transformer/Wave/ExpenseTransformer.php | 45 +++ .../Transformer/Wave/InvoiceTransformer.php | 80 +++++ tests/Feature/Import/Wave/WaveTest.php | 334 ++++++++++++++++++ 10 files changed, 658 insertions(+), 29 deletions(-) create mode 100644 app/Import/Transformer/Wave/ClientTransformer.php create mode 100644 app/Import/Transformer/Wave/ExpenseTransformer.php create mode 100644 app/Import/Transformer/Wave/InvoiceTransformer.php create mode 100644 tests/Feature/Import/Wave/WaveTest.php diff --git a/app/Import/Providers/BaseImport.php b/app/Import/Providers/BaseImport.php index 5cec82ffeb42..e6822705b104 100644 --- a/app/Import/Providers/BaseImport.php +++ b/app/Import/Providers/BaseImport.php @@ -148,12 +148,12 @@ class BaseImport public function ingest($data, $entity_type) { $count = 0; - +nlog("record count = ".count($data)); foreach ($data as $key => $record) { try { $entity = $this->transformer->transform($record); - +nlog($entity); /** @var \App\Http\Requests\Request $request */ $request = new $this->request_name(); @@ -174,6 +174,7 @@ class BaseImport $this->getUserIDForRecord($entity) ) ); +nlog("saving {$entity->name}"); $entity->saveQuietly(); $count++; @@ -181,6 +182,8 @@ class BaseImport } } catch (\Exception $ex) { +nlog($e->getMessage()); + if ($ex instanceof ImportException) { $message = $ex->getMessage(); } else { @@ -512,4 +515,25 @@ class BaseImport NinjaMailerJob::dispatch($nmo); } + + public function preTransform(array $data, $entity_type) + { + if (empty($this->column_map[$entity_type])) { + return false; + } + + if ($this->skip_header) { + array_shift($data); + } + + //sort the array by key + $keys = $this->column_map[$entity_type]; + ksort($keys); + + $data = array_map(function ($row) use ($keys) { + return array_combine($keys, array_intersect_key($row, $keys)); + }, $data); + + return $data; + } } diff --git a/app/Import/Providers/Csv.php b/app/Import/Providers/Csv.php index 9aa9d3e4abb2..7d50261227fd 100644 --- a/app/Import/Providers/Csv.php +++ b/app/Import/Providers/Csv.php @@ -237,27 +237,6 @@ class Csv extends BaseImport implements ImportInterface } - public function preTransform(array $data, $entity_type) - { - if (empty($this->column_map[$entity_type])) { - return false; - } - - if ($this->skip_header) { - array_shift($data); - } - - //sort the array by key - $keys = $this->column_map[$entity_type]; - ksort($keys); - - $data = array_map(function ($row) use ($keys) { - return array_combine($keys, array_intersect_key($row, $keys)); - }, $data); - - return $data; - } - public function transform(array $data) { } diff --git a/app/Import/Providers/ImportInterface.php b/app/Import/Providers/ImportInterface.php index b7ec1ce6f12c..967f7b773323 100644 --- a/app/Import/Providers/ImportInterface.php +++ b/app/Import/Providers/ImportInterface.php @@ -15,8 +15,6 @@ interface ImportInterface public function import(string $entity); - public function preTransform(array $data, string $entity_type); - public function transform(array $data); public function client(); diff --git a/app/Import/Providers/Wave.php b/app/Import/Providers/Wave.php index 9b325e350f63..6875a73e886e 100644 --- a/app/Import/Providers/Wave.php +++ b/app/Import/Providers/Wave.php @@ -1,4 +1,5 @@ {$entity}(); + } + + //collate any errors + + $this->finalizeImport(); + } + + public function client() + { + $entity_type = 'client'; + + $data = $this->getCsvData($entity_type); +nlog($data); + + $data = $this->preTransform($data, $entity_type); +nlog($data); + + if (empty($data)) { + $this->entity_count['clients'] = 0; + return; + } + + $this->request_name = StoreClientRequest::class; + $this->repository_name = ClientRepository::class; + $this->factory_name = ClientFactory::class; + + $this->repository = app()->make($this->repository_name); + $this->repository->import_mode = true; + + $this->transformer = new ClientTransformer($this->company); + + $client_count = $this->ingest($data, $entity_type); + + $this->entity_count['clients'] = $client_count; + + nlog($this->entity_count); + } + + public function transform(array $data){} + + public function product() {} + + public function invoice() {} + + public function payment() {} + + public function vendor() {} + + public function expense() {} + +} diff --git a/app/Import/Transformer/BaseTransformer.php b/app/Import/Transformer/BaseTransformer.php index a5e7d7098d00..8a3473ea5557 100644 --- a/app/Import/Transformer/BaseTransformer.php +++ b/app/Import/Transformer/BaseTransformer.php @@ -14,6 +14,7 @@ namespace App\Import\Transformer; use App\Factory\ProjectFactory; use App\Models\ClientContact; use App\Models\Country; +use App\Models\ExpenseCategory; use App\Models\PaymentType; use App\Models\User; use App\Utils\Number; @@ -38,6 +39,11 @@ class BaseTransformer return isset($data[$field]) && $data[$field] ? $data[$field] : ''; } + public function getValueOrNull($data, $field) + { + return isset($data[$field]) && $data[$field] ? $data[$field] : null; + } + public function getCurrencyByCode($data, $key = 'client.currency_id') { $code = array_key_exists($key, $data) ? $data[$key] : false; @@ -429,6 +435,20 @@ class BaseTransformer return $ec ? $ec->id : null; } + public function getOrCreateExpenseCategry($name) + { + $ec = $this->getExpenseCategoryId($name); + + if($ec) + return $ec; + + $expense_category = ExpenseCategory::create($this->company->id, $this->company->owner()->id); + $expense_category->name = $name; + $expense_category->save(); + + return $expense_category->id; + } + /** * @param $name * @@ -473,4 +493,6 @@ class BaseTransformer return $pt ? $pt->id : null; } + + } diff --git a/app/Import/Transformer/Csv/ClientTransformer.php b/app/Import/Transformer/Csv/ClientTransformer.php index 3df03ad95e9f..b66b17b5414a 100644 --- a/app/Import/Transformer/Csv/ClientTransformer.php +++ b/app/Import/Transformer/Csv/ClientTransformer.php @@ -26,7 +26,7 @@ class ClientTransformer extends BaseTransformer */ public function transform($data) { - if (isset($data->name) && $this->hasClient($data->name)) { + if (isset($data->name) && $this->getString($data, 'client.name')) { throw new ImportException('Client already exists'); } diff --git a/app/Import/Transformer/Wave/ClientTransformer.php b/app/Import/Transformer/Wave/ClientTransformer.php new file mode 100644 index 000000000000..537d3f3db651 --- /dev/null +++ b/app/Import/Transformer/Wave/ClientTransformer.php @@ -0,0 +1,74 @@ +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->company->id, + 'name' => $this->getString( $data, 'customer_name' ), + 'number' => $this->getValueOrNull( $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' ), + ], + ], + ]; + } +} \ No newline at end of file diff --git a/app/Import/Transformer/Wave/ExpenseTransformer.php b/app/Import/Transformer/Wave/ExpenseTransformer.php new file mode 100644 index 000000000000..f8d5b5ad31e2 --- /dev/null +++ b/app/Import/Transformer/Wave/ExpenseTransformer.php @@ -0,0 +1,45 @@ + $this->company->id, + 'vendor_id' => $this->getVendorId($vendor_name = $this->getString($data, 'vendor')), + 'number' => $this->getString($data, 'invoice_number'), + 'public_notes'=> $this->getString($data, 'description'), + 'date' => date( 'Y-m-d', strtotime( $data['bill_date'] ) ) ?: now()->format('Y-m-d'), //27-01-2022 + 'currency_id' => $this->getCurrencyByCode( $data, 'currency' ), + 'category_id' => $this->getOrCreateExpenseCategry($data['account']), + 'amount' => $this->getFloat($data['quantity']) * $this->getFloat($data['amount']), + 'tax_name1' => $this->getTaxName($data['taxes']), + 'tax_rate1' => $this->getTaxRate($data['taxes']), + ]; + + + return $transformed; + } +} \ No newline at end of file diff --git a/app/Import/Transformer/Wave/InvoiceTransformer.php b/app/Import/Transformer/Wave/InvoiceTransformer.php new file mode 100644 index 000000000000..8c824f12bffd --- /dev/null +++ b/app/Import/Transformer/Wave/InvoiceTransformer.php @@ -0,0 +1,80 @@ +hasInvoice( $invoice_data['Invoice Number'] ) ) { + throw new ImportException( 'Invoice number already exists' ); + } + + $transformed = [ + 'company_id' => $this->company->id, + 'client_id' => $this->getClient( $customer_name = $this->getString( $invoice_data, 'Customer' ), null ), + 'number' => $invoice_number = $this->getString( $invoice_data, 'Invoice Number' ), + 'date' => date( 'Y-m-d', strtotime( $invoice_data['Transaction Date'] ) ) ?: now()->format('Y-m-d'), //27-01-2022 + '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; + } +} \ No newline at end of file diff --git a/tests/Feature/Import/Wave/WaveTest.php b/tests/Feature/Import/Wave/WaveTest.php new file mode 100644 index 000000000000..68039d982301 --- /dev/null +++ b/tests/Feature/Import/Wave/WaveTest.php @@ -0,0 +1,334 @@ +withoutMiddleware(ThrottleRequests::class); + + config(['database.default' => config('ninja.db.default')]); + + $this->makeTestData(); + + $this->withoutExceptionHandling(); + } + + public function testClientWaveImport() + { + $csv = file_get_contents( + base_path() . '/tests/Feature/Import/wave_clients.csv' + ); + $hash = Str::random(32); + + $column_map = [ + 0 => 'client.name', + 1 => 'contact.email', + 2 => 'contact.first_name', + 3 => 'contact.last_name', + 4 => 'client.currency_id', + 6 => 'client.phone', + 10 => 'client.website', + 11 => 'client.country_id', + 12 => 'client.state', + 13 => 'client.address1', + 14 => 'client.address2', + 15 => 'client.city', + 16 => 'client.postal_code', + 19 => 'client.shipping_country_id', + 20 => 'client.shipping_state', + 21 => 'client.shipping_address1', + 22 => 'client.shipping_address2', + 23 => 'client.shipping_city', + ]; + + $data = [ + 'hash' => $hash, + 'column_map' => ['client' => ['mapping' => $column_map]], + 'skip_header' => true, + 'import_type' => 'wave', + ]; + + Cache::put($hash . '-client', base64_encode($csv), 360); + + $csv_importer = new Wave($data, $this->company); + + $count = $csv_importer->import('client'); + + $base_transformer = new BaseTransformer($this->company); + + $this->assertTrue($base_transformer->hasClient('Homer Simpson')); + // $this->assertTrue($base_transformer->hasClient('Jessica Jones')); + // $this->assertTrue($base_transformer->hasClient('Lucas Cage')); + // $this->assertTrue($base_transformer->hasClient('Mark Walberg')); + + } + + // public function testVendorCsvImport() + // { + // $csv = file_get_contents( + // base_path() . '/tests/Feature/Import/vendors.csv' + // ); + // $hash = Str::random(32); + // $column_map = [ + // 0 => 'vendor.name', + // 19 => 'vendor.currency_id', + // 20 => 'vendor.public_notes', + // 21 => 'vendor.private_notes', + // 22 => 'vendor.first_name', + // 23 => 'vendor.last_name', + // ]; + + // $data = [ + // 'hash' => $hash, + // 'column_map' => ['vendor' => ['mapping' => $column_map]], + // 'skip_header' => true, + // 'import_type' => 'csv', + // ]; + + // $pre_import = Vendor::count(); + + // Cache::put($hash . '-vendor', base64_encode($csv), 360); + + // $csv_importer = new Csv($data, $this->company); + + // $csv_importer->import('vendor'); + + // $base_transformer = new BaseTransformer($this->company); + + // $this->assertTrue($base_transformer->hasVendor('Ludwig Krajcik DVM')); + // } + + // public function testProductImport() + // { + // $csv = file_get_contents( + // base_path() . '/tests/Feature/Import/products.csv' + // ); + // $hash = Str::random(32); + // Cache::put($hash . '-product', base64_encode($csv), 360); + + // $column_map = [ + // 1 => 'product.product_key', + // 2 => 'product.notes', + // 3 => 'product.cost', + // ]; + + // $data = [ + // 'hash' => $hash, + // 'column_map' => ['product' => ['mapping' => $column_map]], + // 'skip_header' => true, + // 'import_type' => 'csv', + // ]; + + // $csv_importer = new Csv($data, $this->company); + + // $this->assertInstanceOf(Csv::class, $csv_importer); + + // $csv_importer->import('product'); + + // $base_transformer = new BaseTransformer($this->company); + + // $this->assertTrue($base_transformer->hasProduct('officiis')); + // // $this->assertTrue($base_transformer->hasProduct('maxime')); + // } + + // public function testClientImport() + // { + // $csv = file_get_contents( + // base_path() . '/tests/Feature/Import/clients.csv' + // ); + // $hash = Str::random(32); + // $column_map = [ + // 1 => 'client.balance', + // 2 => 'client.paid_to_date', + // 0 => 'client.name', + // 19 => 'client.currency_id', + // 20 => 'client.public_notes', + // 21 => 'client.private_notes', + // 22 => 'contact.first_name', + // 23 => 'contact.last_name', + // 24 => 'contact.email', + // ]; + + // $data = [ + // 'hash' => $hash, + // 'column_map' => ['client' => ['mapping' => $column_map]], + // 'skip_header' => true, + // 'import_type' => 'csv', + // ]; + + // Cache::put($hash . '-client', base64_encode($csv), 360); + + // $csv_importer = new Csv($data, $this->company); + + // $this->assertInstanceOf(Csv::class, $csv_importer); + + // $csv_importer->import('client'); + + // $base_transformer = new BaseTransformer($this->company); + + // $this->assertTrue($base_transformer->hasClient('Ludwig Krajcik DVM')); + + // $client_id = $base_transformer->getClient('Ludwig Krajcik DVM', null); + + // $c = Client::find($client_id); + + // $this->assertEquals($client_id, $c->id); + + // $client_id = $base_transformer->getClient( + // 'a non existent clent', + // 'brook59@example.org' + // ); + + // $this->assertEquals($client_id, $c->id); + // } + + // public function testInvoiceImport() + // { + // /*Need to import clients first*/ + // $csv = file_get_contents( + // base_path() . '/tests/Feature/Import/clients.csv' + // ); + // $hash = Str::random(32); + // $column_map = [ + // 1 => 'client.balance', + // 2 => 'client.paid_to_date', + // 0 => 'client.name', + // 19 => 'client.currency_id', + // 20 => 'client.public_notes', + // 21 => 'client.private_notes', + // 22 => 'contact.first_name', + // 23 => 'contact.last_name', + // ]; + + // $data = [ + // 'hash' => $hash, + // 'column_map' => ['client' => ['mapping' => $column_map]], + // 'skip_header' => true, + // 'import_type' => 'csv', + // ]; + + // Cache::put($hash . '-client', base64_encode($csv), 360); + + // $csv_importer = new Csv($data, $this->company); + + // $this->assertInstanceOf(Csv::class, $csv_importer); + + // $csv_importer->import('client'); + + // $base_transformer = new BaseTransformer($this->company); + + // $this->assertTrue($base_transformer->hasClient('Ludwig Krajcik DVM')); + + // /* client import verified*/ + + // /*Now import invoices*/ + // $csv = file_get_contents( + // base_path() . '/tests/Feature/Import/invoice.csv' + // ); + // $hash = Str::random(32); + + // $column_map = [ + // 1 => 'client.email', + // 3 => 'payment.amount', + // 5 => 'invoice.po_number', + // 8 => 'invoice.due_date', + // 9 => 'item.discount', + // 11 => 'invoice.partial_due_date', + // 12 => 'invoice.public_notes', + // 13 => 'invoice.private_notes', + // 0 => 'client.name', + // 2 => 'invoice.number', + // 7 => 'invoice.date', + // 14 => 'item.product_key', + // 15 => 'item.notes', + // 16 => 'item.cost', + // 17 => 'item.quantity', + // ]; + + // $data = [ + // 'hash' => $hash, + // 'column_map' => ['invoice' => ['mapping' => $column_map]], + // 'skip_header' => true, + // 'import_type' => 'csv', + // ]; + + // Cache::put($hash . '-invoice', base64_encode($csv), 360); + + // $csv_importer = new Csv($data, $this->company); + + // $csv_importer->import('invoice'); + + // $this->assertTrue($base_transformer->hasInvoice('801')); + + // /* Lets piggy back payments tests here to save rebuilding the test multiple times*/ + + // $csv = file_get_contents( + // base_path() . '/tests/Feature/Import/payments.csv' + // ); + // $hash = Str::random(32); + + // $column_map = [ + // 0 => 'payment.client_id', + // 1 => 'payment.invoice_number', + // 2 => 'payment.amount', + // 3 => 'payment.date', + // ]; + + // $data = [ + // 'hash' => $hash, + // 'column_map' => ['payment' => ['mapping' => $column_map]], + // 'skip_header' => true, + // 'import_type' => 'csv', + // ]; + + // Cache::put($hash . '-payment', base64_encode($csv), 360); + + // $csv_importer = new Csv($data, $this->company); + + // $csv_importer->import('payment'); + + // $this->assertTrue($base_transformer->hasInvoice('801')); + + // $invoice_id = $base_transformer->getInvoiceId('801'); + + // $invoice = Invoice::find($invoice_id); + + // $this->assertTrue($invoice->payments()->exists()); + // $this->assertEquals(1, $invoice->payments()->count()); + // $this->assertEquals(400, $invoice->payments()->sum('payments.amount')); + // } + + +} + From 95861a2a8ca9841ee96f1313dcf8d5d24b538843 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 10 Feb 2022 13:38:10 +1100 Subject: [PATCH 2/7] Tests for wave --- app/Import/Providers/BaseImport.php | 17 ++++++-- app/Import/Providers/Wave.php | 5 ++- tests/Feature/Import/Wave/WaveTest.php | 56 +++++++++++++++----------- tests/MockAccountData.php | 2 + 4 files changed, 52 insertions(+), 28 deletions(-) diff --git a/app/Import/Providers/BaseImport.php b/app/Import/Providers/BaseImport.php index e6822705b104..6a0e99c294a4 100644 --- a/app/Import/Providers/BaseImport.php +++ b/app/Import/Providers/BaseImport.php @@ -149,9 +149,13 @@ class BaseImport { $count = 0; nlog("record count = ".count($data)); - foreach ($data as $key => $record) { - try { +nlog($data); + foreach ($data as $key => $record) { + + try { +nlog($key); +nlog($record); $entity = $this->transformer->transform($record); nlog($entity); /** @var \App\Http\Requests\Request $request */ @@ -179,10 +183,15 @@ nlog("saving {$entity->name}"); $entity->saveQuietly(); $count++; + nlog("entity number"); + nlog($entity->id); +nlog("after save"); + } } catch (\Exception $ex) { -nlog($e->getMessage()); +nlog("exception"); +nlog($ex->getMessage()); if ($ex instanceof ImportException) { $message = $ex->getMessage(); @@ -191,6 +200,8 @@ nlog($e->getMessage()); $message = 'Unknown error'; } +nlog($message); + $this->error_array[$entity_type][] = [ $entity_type => $record, 'error' => $message, diff --git a/app/Import/Providers/Wave.php b/app/Import/Providers/Wave.php index 6875a73e886e..68b23d8ebbe5 100644 --- a/app/Import/Providers/Wave.php +++ b/app/Import/Providers/Wave.php @@ -48,10 +48,10 @@ class Wave extends BaseImport implements ImportInterface $entity_type = 'client'; $data = $this->getCsvData($entity_type); -nlog($data); +// nlog($data); $data = $this->preTransform($data, $entity_type); -nlog($data); +// nlog($data); if (empty($data)) { $this->entity_count['clients'] = 0; @@ -72,6 +72,7 @@ nlog($data); $this->entity_count['clients'] = $client_count; nlog($this->entity_count); + nlog($this->error_array); } public function transform(array $data){} diff --git a/tests/Feature/Import/Wave/WaveTest.php b/tests/Feature/Import/Wave/WaveTest.php index 68039d982301..4cefccb5b923 100644 --- a/tests/Feature/Import/Wave/WaveTest.php +++ b/tests/Feature/Import/Wave/WaveTest.php @@ -13,6 +13,7 @@ namespace Tests\Feature\Import\CSV; use App\Import\Providers\Wave; use App\Import\Transformer\BaseTransformer; +use App\Models\Client; use App\Utils\Traits\MakesHash; use Illuminate\Foundation\Testing\DatabaseTransactions; use Illuminate\Routing\Middleware\ThrottleRequests; @@ -50,33 +51,42 @@ class WaveTest extends TestCase base_path() . '/tests/Feature/Import/wave_clients.csv' ); $hash = Str::random(32); - + $column_map = [ - 0 => 'client.name', - 1 => 'contact.email', - 2 => 'contact.first_name', - 3 => 'contact.last_name', - 4 => 'client.currency_id', - 6 => 'client.phone', - 10 => 'client.website', - 11 => 'client.country_id', - 12 => 'client.state', - 13 => 'client.address1', - 14 => 'client.address2', - 15 => 'client.city', - 16 => 'client.postal_code', - 19 => 'client.shipping_country_id', - 20 => 'client.shipping_state', - 21 => 'client.shipping_address1', - 22 => 'client.shipping_address2', - 23 => 'client.shipping_city', + 0 => 'customer_name', + 1 => 'email', + 2 => 'contact_first_name', + 3 => 'contact_last_name', + 4 => 'customer_currency', + // 5 => 'account_number', + 6 => 'phone', + 7 => 'fax', + 8 => 'mobile', + 9 => 'toll_free', + 10 => 'website', + 11 => 'country', + 12 => 'province/state', + 13 => 'address_line_1', + 14 => 'address_line_2', + 15 => 'city', + 16 => 'postal_code/zip_code', + 17 => 'shipping_address', + 18 => 'ship-to_contact', + 19 => 'ship-to_country', + 20 => 'ship-to_province/state', + 21 => 'ship-to_address_line_1', + 22 => 'ship-to_address_line_2', + 23 => 'ship-to_city', + 24 => 'ship-to_postal_code/zip_code', + 25 => 'ship-to_phone', + 26 => 'delivery_instructions', ]; $data = [ 'hash' => $hash, 'column_map' => ['client' => ['mapping' => $column_map]], 'skip_header' => true, - 'import_type' => 'wave', + 'import_type' => 'waveaccounting', ]; Cache::put($hash . '-client', base64_encode($csv), 360); @@ -88,9 +98,9 @@ class WaveTest extends TestCase $base_transformer = new BaseTransformer($this->company); $this->assertTrue($base_transformer->hasClient('Homer Simpson')); - // $this->assertTrue($base_transformer->hasClient('Jessica Jones')); - // $this->assertTrue($base_transformer->hasClient('Lucas Cage')); - // $this->assertTrue($base_transformer->hasClient('Mark Walberg')); + $this->assertTrue($base_transformer->hasClient('Jessica Jones')); + $this->assertTrue($base_transformer->hasClient('Lucas Cage')); + $this->assertTrue($base_transformer->hasClient('Mark Walberg')); } diff --git a/tests/MockAccountData.php b/tests/MockAccountData.php index 7c0155185bc9..898a59d75941 100644 --- a/tests/MockAccountData.php +++ b/tests/MockAccountData.php @@ -233,6 +233,8 @@ trait MockAccountData $user_id = $user->id; $this->user = $user; + auth()->login($user); + CreateCompanyTaskStatuses::dispatchNow($this->company, $this->user); $this->cu = CompanyUserFactory::create($user->id, $this->company->id, $this->account->id); From cc4966845cc67a8bf5dea604b8f9083d67a94648 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 10 Feb 2022 13:55:44 +1100 Subject: [PATCH 3/7] Fixes for imports --- app/Import/Providers/BaseImport.php | 20 +++++--------------- tests/Feature/Import/CSV/CsvImportTest.php | 3 ++- tests/Feature/Import/Wave/WaveTest.php | 2 +- 3 files changed, 8 insertions(+), 17 deletions(-) diff --git a/app/Import/Providers/BaseImport.php b/app/Import/Providers/BaseImport.php index 6a0e99c294a4..3e89bac08149 100644 --- a/app/Import/Providers/BaseImport.php +++ b/app/Import/Providers/BaseImport.php @@ -148,16 +148,12 @@ class BaseImport public function ingest($data, $entity_type) { $count = 0; -nlog("record count = ".count($data)); -nlog($data); foreach ($data as $key => $record) { try { -nlog($key); -nlog($record); + $entity = $this->transformer->transform($record); -nlog($entity); /** @var \App\Http\Requests\Request $request */ $request = new $this->request_name(); @@ -178,20 +174,15 @@ nlog($entity); $this->getUserIDForRecord($entity) ) ); -nlog("saving {$entity->name}"); $entity->saveQuietly(); $count++; - nlog("entity number"); - nlog($entity->id); -nlog("after save"); - } + } catch (\Exception $ex) { -nlog("exception"); -nlog($ex->getMessage()); + if ($ex instanceof ImportException) { $message = $ex->getMessage(); @@ -200,16 +191,15 @@ nlog($ex->getMessage()); $message = 'Unknown error'; } -nlog($message); - $this->error_array[$entity_type][] = [ $entity_type => $record, 'error' => $message, ]; } - return $count; } + + return $count; } public function ingestInvoices($invoices, $invoice_number_key) diff --git a/tests/Feature/Import/CSV/CsvImportTest.php b/tests/Feature/Import/CSV/CsvImportTest.php index d295ae5b2975..7b5ec9ade2b1 100644 --- a/tests/Feature/Import/CSV/CsvImportTest.php +++ b/tests/Feature/Import/CSV/CsvImportTest.php @@ -188,7 +188,8 @@ class CsvImportTest extends TestCase $base_transformer = new BaseTransformer($this->company); $this->assertTrue($base_transformer->hasClient('Ludwig Krajcik DVM')); - + $this->assertTrue($base_transformer->hasClient('Bradly Jaskolski Sr.')); + $client_id = $base_transformer->getClient('Ludwig Krajcik DVM', null); $c = Client::find($client_id); diff --git a/tests/Feature/Import/Wave/WaveTest.php b/tests/Feature/Import/Wave/WaveTest.php index 4cefccb5b923..bfb52f546b51 100644 --- a/tests/Feature/Import/Wave/WaveTest.php +++ b/tests/Feature/Import/Wave/WaveTest.php @@ -58,7 +58,7 @@ class WaveTest extends TestCase 2 => 'contact_first_name', 3 => 'contact_last_name', 4 => 'customer_currency', - // 5 => 'account_number', + 5 => 'account_number', 6 => 'phone', 7 => 'fax', 8 => 'mobile', From 8b4f109f7d1cb61deca711aa74a977daf35113cd Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 10 Feb 2022 14:04:30 +1100 Subject: [PATCH 4/7] Tests for wave client imports --- tests/Feature/Import/Wave/WaveTest.php | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/Feature/Import/Wave/WaveTest.php b/tests/Feature/Import/Wave/WaveTest.php index bfb52f546b51..ee1536f60810 100644 --- a/tests/Feature/Import/Wave/WaveTest.php +++ b/tests/Feature/Import/Wave/WaveTest.php @@ -102,6 +102,22 @@ class WaveTest extends TestCase $this->assertTrue($base_transformer->hasClient('Lucas Cage')); $this->assertTrue($base_transformer->hasClient('Mark Walberg')); + $client_id = $base_transformer->getClient('Jessica Jones', 'jessica@jones.com'); + + $client = Client::find($client_id); + + $this->assertInstanceOf(Client::class, $client); + + $this->assertEquals('12', $client->settings->currency_id); + $this->assertEquals('Queensland', $client->state); + $this->assertEquals('NYC', $client->city); + $this->assertEquals('11213', $client->postal_code); + + $this->assertEquals('Jessica Jones', $client->contacts->first()->first_name); + $this->assertEquals('', $client->contacts->first()->last_name); + $this->assertEquals('jessica@jones.com', $client->contacts->first()->email); + $this->assertEquals('555-867-5309', $client->contacts->first()->phone); + } // public function testVendorCsvImport() From ac09ffff32017ee68b94e241ee571d9c173e0315 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 10 Feb 2022 14:53:30 +1100 Subject: [PATCH 5/7] Test for Wave Invoice Import --- app/Import/Providers/BaseImport.php | 6 +- app/Import/Providers/Wave.php | 56 ++++++-- .../Transformer/Wave/InvoiceTransformer.php | 2 +- tests/Feature/Import/Wave/WaveTest.php | 123 ++++++++++++++++++ 4 files changed, 175 insertions(+), 12 deletions(-) diff --git a/app/Import/Providers/BaseImport.php b/app/Import/Providers/BaseImport.php index 3e89bac08149..a375fd8856d9 100644 --- a/app/Import/Providers/BaseImport.php +++ b/app/Import/Providers/BaseImport.php @@ -182,8 +182,6 @@ class BaseImport } catch (\Exception $ex) { - - if ($ex instanceof ImportException) { $message = $ex->getMessage(); } else { @@ -199,7 +197,7 @@ class BaseImport } - return $count; + return $count; } public function ingestInvoices($invoices, $invoice_number_key) @@ -220,8 +218,10 @@ class BaseImport $invoices = $this->groupInvoices($invoices, $invoice_number_key); foreach ($invoices as $raw_invoice) { + nlog($raw_invoice); try { $invoice_data = $invoice_transformer->transform($raw_invoice); + nlog($invoice_data); $invoice_data['line_items'] = $this->cleanItems( $invoice_data['line_items'] ?? [] ); diff --git a/app/Import/Providers/Wave.php b/app/Import/Providers/Wave.php index 68b23d8ebbe5..08f3bba9e881 100644 --- a/app/Import/Providers/Wave.php +++ b/app/Import/Providers/Wave.php @@ -13,10 +13,14 @@ namespace App\Import\Providers; use App\Factory\ClientFactory; +use App\Factory\InvoiceFactory; use App\Http\Requests\Client\StoreClientRequest; +use App\Http\Requests\Invoice\StoreInvoiceRequest; use App\Import\Transformer\Wave\ClientTransformer; +use App\Import\Transformer\Wave\InvoiceTransformer; use App\Models\Client; use App\Repositories\ClientRepository; +use App\Repositories\InvoiceRepository; class Wave extends BaseImport implements ImportInterface { @@ -28,8 +32,8 @@ class Wave extends BaseImport implements ImportInterface if ( in_array($entity, [ 'client', + 'invoice', // 'product', - // 'invoice', // 'payment', // 'vendor', // 'expense', @@ -48,10 +52,8 @@ class Wave extends BaseImport implements ImportInterface $entity_type = 'client'; $data = $this->getCsvData($entity_type); -// nlog($data); $data = $this->preTransform($data, $entity_type); -// nlog($data); if (empty($data)) { $this->entity_count['clients'] = 0; @@ -71,15 +73,52 @@ class Wave extends BaseImport implements ImportInterface $this->entity_count['clients'] = $client_count; - nlog($this->entity_count); - nlog($this->error_array); } - public function transform(array $data){} - public function product() {} + public function product() { - public function invoice() {} + //done automatically inside the invoice() method as we need to harvest the products from the line items + + } + + public function invoice() { + + //make sure we update and create products with wave + $initial_update_products_value = $this->company->update_products; + $this->company->update_products = true; + + $this->company->save(); + + $entity_type = 'invoice'; + + $data = $this->getCsvData($entity_type); +nlog($data); + + $data = $this->preTransform($data, $entity_type); +nlog($data); + if (empty($data)) { + $this->entity_count['invoices'] = 0; + return; + } + + $this->request_name = StoreInvoiceRequest::class; + $this->repository_name = InvoiceRepository::class; + $this->factory_name = InvoiceFactory::class; + + $this->repository = app()->make($this->repository_name); + $this->repository->import_mode = true; + + $this->transformer = new InvoiceTransformer($this->company); + + $invoice_count = $this->ingestInvoices($data, 'Invoice Number'); + + $this->entity_count['invoices'] = $invoice_count; + + $this->company->update_products = $initial_update_products_value; + $this->company->save(); + + } public function payment() {} @@ -87,4 +126,5 @@ class Wave extends BaseImport implements ImportInterface public function expense() {} + public function transform(array $data){} } diff --git a/app/Import/Transformer/Wave/InvoiceTransformer.php b/app/Import/Transformer/Wave/InvoiceTransformer.php index 8c824f12bffd..7b8e4e8e6d45 100644 --- a/app/Import/Transformer/Wave/InvoiceTransformer.php +++ b/app/Import/Transformer/Wave/InvoiceTransformer.php @@ -9,7 +9,7 @@ * @license https://www.elastic.co/licensing/elastic-license */ -namespace App\Import\Transformer\Waveaccounting; +namespace App\Import\Transformer\Wave; use App\Import\ImportException; use App\Import\Transformer\BaseTransformer; diff --git a/tests/Feature/Import/Wave/WaveTest.php b/tests/Feature/Import/Wave/WaveTest.php index ee1536f60810..4b0cab13cb92 100644 --- a/tests/Feature/Import/Wave/WaveTest.php +++ b/tests/Feature/Import/Wave/WaveTest.php @@ -14,6 +14,8 @@ namespace Tests\Feature\Import\CSV; use App\Import\Providers\Wave; use App\Import\Transformer\BaseTransformer; use App\Models\Client; +use App\Models\Invoice; +use App\Models\Product; use App\Utils\Traits\MakesHash; use Illuminate\Foundation\Testing\DatabaseTransactions; use Illuminate\Routing\Middleware\ThrottleRequests; @@ -120,6 +122,127 @@ class WaveTest extends TestCase } + public function testInvoiceWaveImport() + { + //first import all the clients + + $csv = file_get_contents( + base_path() . '/tests/Feature/Import/wave_clients.csv' + ); + $hash = Str::random(32); + + $column_map = [ + 0 => 'customer_name', + 1 => 'email', + 2 => 'contact_first_name', + 3 => 'contact_last_name', + 4 => 'customer_currency', + 5 => 'account_number', + 6 => 'phone', + 7 => 'fax', + 8 => 'mobile', + 9 => 'toll_free', + 10 => 'website', + 11 => 'country', + 12 => 'province/state', + 13 => 'address_line_1', + 14 => 'address_line_2', + 15 => 'city', + 16 => 'postal_code/zip_code', + 17 => 'shipping_address', + 18 => 'ship-to_contact', + 19 => 'ship-to_country', + 20 => 'ship-to_province/state', + 21 => 'ship-to_address_line_1', + 22 => 'ship-to_address_line_2', + 23 => 'ship-to_city', + 24 => 'ship-to_postal_code/zip_code', + 25 => 'ship-to_phone', + 26 => 'delivery_instructions', + ]; + + $data = [ + 'hash' => $hash, + 'column_map' => ['client' => ['mapping' => $column_map]], + 'skip_header' => true, + 'import_type' => 'waveaccounting', + ]; + + Cache::put($hash . '-client', base64_encode($csv), 360); + + $csv_importer = new Wave($data, $this->company); + + $count = $csv_importer->import('client'); + + //now import the invoices + + $csv = file_get_contents( + base_path() . '/tests/Feature/Import/wave_invoices.csv' + ); + $hash = Str::random(32); + + $column_map = [ + 0 => 'Transaction ID', + 1 => 'Transaction Date', + 2 => 'Account Name', + 3 => 'Transaction Description', + 4 => 'Transaction Line Description', + 5 => 'Amount (One column)', + 6 => ' ', + 7 => 'Debit Amount (Two Column Approach)', + 8 => 'Credit Amount (Two Column Approach)', + 9 => 'Other Accounts for this Transaction', + 10 => 'Customer', + 11 => 'Vendor', + 12 => 'Invoice Number', + 13 => 'Bill Number', + 14 => 'Notes / Memo', + 15 => 'Amount Before Sales Tax', + 16 => 'Sales Tax Amount', + 17 => 'Sales Tax Name', + 18 => 'Transaction Date Added', + 19 => 'Transaction Date Last Modified', + 20 => 'Account Group', + 21 => 'Account Type', + 22 => 'Account ID', + ]; + + $data = [ + 'hash' => $hash, + 'column_map' => ['invoice' => ['mapping' => $column_map]], + 'skip_header' => true, + 'import_type' => 'waveaccounting', + ]; + + Cache::put($hash . '-invoice', base64_encode($csv), 360); + + $csv_importer = new Wave($data, $this->company); + + $count = $csv_importer->import('invoice'); + + $base_transformer = new BaseTransformer($this->company); + + $this->assertTrue($base_transformer->hasInvoice("2")); + $this->assertTrue($base_transformer->hasInvoice("3")); + $this->assertTrue($base_transformer->hasInvoice("4")); + + $invoice_id = $base_transformer->getInvoiceId("4"); + $invoice = Invoice::find($invoice_id); + + $this->assertEquals(3500.41 , $invoice->amount); + $this->assertEquals(0 , $invoice->balance); + $this->assertEquals(2 , count($invoice->line_items)); + + $this->assertTrue($invoice->payments()->exists()); + $this->assertEquals(3500.41, $invoice->payments->first()->amount); + + $product = Product::where('notes', 'Lawn Service')->where('company_id', $this->company->id)->first(); + + + nlog(Product::all()->pluck('notes')); + } + + // public function testVendorCsvImport() // { // $csv = file_get_contents( From 52c0d98d8222ec995376c9e3a117f653ef46e9be Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 10 Feb 2022 15:10:56 +1100 Subject: [PATCH 6/7] Tests for vendor import with Wave --- app/Import/Providers/Wave.php | 43 ++++++++++-- app/Import/Transformer/BaseTransformer.php | 2 +- .../Transformer/Wave/VendorTransformer.php | 56 +++++++++++++++ tests/Feature/Import/Wave/WaveTest.php | 69 +++++++++++++++++-- tests/Feature/Import/wave_vendors.csv | 2 + 5 files changed, 162 insertions(+), 10 deletions(-) create mode 100644 app/Import/Transformer/Wave/VendorTransformer.php create mode 100644 tests/Feature/Import/wave_vendors.csv diff --git a/app/Import/Providers/Wave.php b/app/Import/Providers/Wave.php index 08f3bba9e881..3da28843ebd0 100644 --- a/app/Import/Providers/Wave.php +++ b/app/Import/Providers/Wave.php @@ -14,13 +14,17 @@ namespace App\Import\Providers; use App\Factory\ClientFactory; use App\Factory\InvoiceFactory; +use App\Factory\VendorFactory; use App\Http\Requests\Client\StoreClientRequest; use App\Http\Requests\Invoice\StoreInvoiceRequest; +use App\Http\Requests\Vendor\StoreVendorRequest; use App\Import\Transformer\Wave\ClientTransformer; use App\Import\Transformer\Wave\InvoiceTransformer; +use App\Import\Transformer\Wave\VendorTransformer; use App\Models\Client; use App\Repositories\ClientRepository; use App\Repositories\InvoiceRepository; +use App\Repositories\VendorRepository; class Wave extends BaseImport implements ImportInterface { @@ -35,7 +39,7 @@ class Wave extends BaseImport implements ImportInterface 'invoice', // 'product', // 'payment', - // 'vendor', + 'vendor', // 'expense', ]) ) { @@ -93,10 +97,9 @@ class Wave extends BaseImport implements ImportInterface $entity_type = 'invoice'; $data = $this->getCsvData($entity_type); -nlog($data); $data = $this->preTransform($data, $entity_type); -nlog($data); + if (empty($data)) { $this->entity_count['invoices'] = 0; return; @@ -120,9 +123,39 @@ nlog($data); } - public function payment() {} + public function payment() + { + //these are pulled in when processing invoices + } - public function vendor() {} + public function vendor() + { + + $entity_type = 'vendor'; + + $data = $this->getCsvData($entity_type); +nlog($data); + $data = $this->preTransform($data, $entity_type); +nlog($data); + if (empty($data)) { + $this->entity_count['vendors'] = 0; + return; + } + + $this->request_name = StoreVendorRequest::class; + $this->repository_name = VendorRepository::class; + $this->factory_name = VendorFactory::class; + + $this->repository = app()->make($this->repository_name); + $this->repository->import_mode = true; + + $this->transformer = new VendorTransformer($this->company); + + $vendor_count = $this->ingest($data, $entity_type); + + $this->entity_count['vendors'] = $vendor_count; + + } public function expense() {} diff --git a/app/Import/Transformer/BaseTransformer.php b/app/Import/Transformer/BaseTransformer.php index 8a3473ea5557..ec4e6b01a27c 100644 --- a/app/Import/Transformer/BaseTransformer.php +++ b/app/Import/Transformer/BaseTransformer.php @@ -36,7 +36,7 @@ class BaseTransformer public function getString($data, $field) { - return isset($data[$field]) && $data[$field] ? $data[$field] : ''; + return isset($data[$field]) && $data[$field] ? trim($data[$field]) : ''; } public function getValueOrNull($data, $field) diff --git a/app/Import/Transformer/Wave/VendorTransformer.php b/app/Import/Transformer/Wave/VendorTransformer.php new file mode 100644 index 000000000000..0f0fe76b322c --- /dev/null +++ b/app/Import/Transformer/Wave/VendorTransformer.php @@ -0,0 +1,56 @@ +hasVendor( $data['vendor_name'] ) ) { + throw new ImportException('Vendor already exists'); + } + + return [ + 'company_id' => $this->company->id, + 'name' => $this->getString( $data, 'vendor_name' ), + 'number' => $this->getValueOrNull( $data, 'account_number' ), + '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' ), + 'currency_id' => $this->getCurrencyByCode( $data, 'vendor_currency' ), + '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' ), + ], + ], + ]; + } +} \ No newline at end of file diff --git a/tests/Feature/Import/Wave/WaveTest.php b/tests/Feature/Import/Wave/WaveTest.php index 4b0cab13cb92..b474dc472cc2 100644 --- a/tests/Feature/Import/Wave/WaveTest.php +++ b/tests/Feature/Import/Wave/WaveTest.php @@ -16,6 +16,7 @@ use App\Import\Transformer\BaseTransformer; use App\Models\Client; use App\Models\Invoice; use App\Models\Product; +use App\Models\Vendor; use App\Utils\Traits\MakesHash; use Illuminate\Foundation\Testing\DatabaseTransactions; use Illuminate\Routing\Middleware\ThrottleRequests; @@ -47,6 +48,70 @@ class WaveTest extends TestCase $this->withoutExceptionHandling(); } + public function testVendorWaveImport() + { + $csv = file_get_contents( + base_path() . '/tests/Feature/Import/wave_vendors.csv' + ); + $hash = Str::random(32); + + $column_map = [ + 0 => 'vendor_name', + 1 => 'email', + 2 => 'contact_first_name', + 3 => 'contact_last_name', + 4 => 'vendor_currency', + 5 => 'account_number', + 6 => 'phone', + 7 => 'fax', + 8 => 'mobile', + 9 => 'toll_free', + 10 => 'website', + 11 => 'country', + 12 => 'province/state', + 13 => 'address_line_1', + 14 => 'address_line_2', + 15 => 'city', + 16 => 'postal_code/zip_code', + ]; + + $data = [ + 'hash' => $hash, + 'column_map' => ['vendor' => ['mapping' => $column_map]], + 'skip_header' => true, + 'import_type' => 'waveaccounting', + ]; + + Cache::put($hash . '-vendor', base64_encode($csv), 360); + + $csv_importer = new Wave($data, $this->company); + + $count = $csv_importer->import('vendor'); + + $base_transformer = new BaseTransformer($this->company); + + $this->assertTrue($base_transformer->hasVendor('Vendor Name')); + + $vendor_id = $base_transformer->getVendorId('Vendor Name'); + + $vendor = Vendor::find($vendor_id); + + $this->assertInstanceOf(Vendor::class, $vendor); + + $this->assertEquals(12, $vendor->currency_id); + $this->assertEquals('Australian Capital Territory', $vendor->state); + $this->assertEquals('city', $vendor->city); + $this->assertEquals('postal_cod', $vendor->postal_code); + + $this->assertEquals('firstname', $vendor->contacts->first()->first_name); + $this->assertEquals('lastname', $vendor->contacts->first()->last_name); + $this->assertEquals('vendor@gmail.com', $vendor->contacts->first()->email); + $this->assertEquals('phone', $vendor->contacts->first()->phone); + + } + + + public function testClientWaveImport() { $csv = file_get_contents( @@ -236,10 +301,6 @@ class WaveTest extends TestCase $this->assertTrue($invoice->payments()->exists()); $this->assertEquals(3500.41, $invoice->payments->first()->amount); - $product = Product::where('notes', 'Lawn Service')->where('company_id', $this->company->id)->first(); - - - nlog(Product::all()->pluck('notes')); } diff --git a/tests/Feature/Import/wave_vendors.csv b/tests/Feature/Import/wave_vendors.csv new file mode 100644 index 000000000000..a7e23df0efd7 --- /dev/null +++ b/tests/Feature/Import/wave_vendors.csv @@ -0,0 +1,2 @@ +vendor_name,email,contact_first_name,contact_last_name,vendor_currency,account_number,phone,fax,mobile,toll_free,website,country,province/state,address_line_1,address_line_2,city,postal_code/zip_code +Vendor Name,vendor@gmail.com,firstname,lastname,AUD,,phone,fax,mob,,,Australia,Australian Capital Territory,address1,address2,city ,postal_cod From 4fab0f22562fa3071f02c6286c59bedb47f4bb0b Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 10 Feb 2022 21:15:58 +1100 Subject: [PATCH 7/7] Tests for expense imports for Wave --- app/Import/Providers/BaseImport.php | 4 +- app/Import/Providers/Wave.php | 122 +++++++++++++++++- app/Import/Transformer/BaseTransformer.php | 37 +++++- .../Transformer/Wave/ExpenseTransformer.php | 47 +++++-- tests/Feature/Import/Wave/WaveTest.php | 103 ++++++++++++++- tests/Feature/Import/wave_expenses.csv | 26 ++++ 6 files changed, 318 insertions(+), 21 deletions(-) create mode 100644 tests/Feature/Import/wave_expenses.csv diff --git a/app/Import/Providers/BaseImport.php b/app/Import/Providers/BaseImport.php index a375fd8856d9..66f244b2a2ff 100644 --- a/app/Import/Providers/BaseImport.php +++ b/app/Import/Providers/BaseImport.php @@ -78,7 +78,7 @@ class BaseImport ->setCompany($this->company); } - protected function getCsvData($entity_type) + public function getCsvData($entity_type) { $base64_encoded_csv = Cache::pull($this->hash . '-' . $entity_type); if (empty($base64_encoded_csv)) { @@ -218,7 +218,7 @@ class BaseImport $invoices = $this->groupInvoices($invoices, $invoice_number_key); foreach ($invoices as $raw_invoice) { - nlog($raw_invoice); + try { $invoice_data = $invoice_transformer->transform($raw_invoice); nlog($invoice_data); diff --git a/app/Import/Providers/Wave.php b/app/Import/Providers/Wave.php index 3da28843ebd0..7e12ff74e064 100644 --- a/app/Import/Providers/Wave.php +++ b/app/Import/Providers/Wave.php @@ -13,18 +13,24 @@ namespace App\Import\Providers; use App\Factory\ClientFactory; +use App\Factory\ExpenseFactory; use App\Factory\InvoiceFactory; use App\Factory\VendorFactory; use App\Http\Requests\Client\StoreClientRequest; +use App\Http\Requests\Expense\StoreExpenseRequest; use App\Http\Requests\Invoice\StoreInvoiceRequest; use App\Http\Requests\Vendor\StoreVendorRequest; +use App\Import\ImportException; use App\Import\Transformer\Wave\ClientTransformer; +use App\Import\Transformer\Wave\ExpenseTransformer; use App\Import\Transformer\Wave\InvoiceTransformer; use App\Import\Transformer\Wave\VendorTransformer; use App\Models\Client; use App\Repositories\ClientRepository; +use App\Repositories\ExpenseRepository; use App\Repositories\InvoiceRepository; use App\Repositories\VendorRepository; +use Illuminate\Support\Facades\Validator; class Wave extends BaseImport implements ImportInterface { @@ -40,7 +46,7 @@ class Wave extends BaseImport implements ImportInterface // 'product', // 'payment', 'vendor', - // 'expense', + 'expense', ]) ) { $this->{$entity}(); @@ -134,9 +140,8 @@ class Wave extends BaseImport implements ImportInterface $entity_type = 'vendor'; $data = $this->getCsvData($entity_type); -nlog($data); $data = $this->preTransform($data, $entity_type); -nlog($data); + if (empty($data)) { $this->entity_count['vendors'] = 0; return; @@ -157,7 +162,116 @@ nlog($data); } - public function expense() {} + public function expense() + { + $entity_type = 'expense'; + + $data = $this->getCsvData($entity_type); + $data = $this->preTransform($data, $entity_type); + + if (empty($data)) { + $this->entity_count['expense'] = 0; + return; + } + + $this->request_name = StoreExpenseRequest::class; + $this->repository_name = ExpenseRepository::class; + $this->factory_name = ExpenseFactory::class; + + $this->repository = app()->make($this->repository_name); + $this->repository->import_mode = true; + + $this->transformer = new ExpenseTransformer($this->company); + + $expense_count = $this->ingestExpenses($data, $entity_type); + + $this->entity_count['expenses'] = $expense_count; + + } public function transform(array $data){} + + + private function groupExpenses($csvData) + { + + $grouped_expense = []; + $key = 'Transaction ID'; + + foreach($csvData as $expense) + { + if($expense['Account Group'] == 'Expense') + $grouped[$expense[$key]][] = $expense; + } + + return $grouped; + } + + public function ingestExpenses($data) + { + $key = 'Transaction ID'; + + $expense_transformer = $this->transformer; + + $vendor_repository = app()->make(VendorRepository::class); + $expense_repository = app()->make(ExpenseRepository::class); + + $expenses = $this->groupExpenses($data); + +// nlog($expenses); +// exit; + foreach ($expenses as $raw_expense) { + + try { + + $expense_data = $expense_transformer->transform($raw_expense); + + // If we don't have a client ID, but we do have client data, go ahead and create the client. + if (empty($expense_data['vendor_id']) ) { + $vendor_data['user_id'] = $this->getUserIDForRecord($expense_data); + + $vendor_repository->save( + ['name' => $raw_expense['Vendor Name']], + $vendor = VendorFactory::create( + $this->company->id, + $vendor_data['user_id'] + ) + ); + $expense_data['vendor_id'] = $vendor->id; + } + + $validator = Validator::make( + $expense_data, + (new StoreExpenseRequest())->rules() + ); + if ($validator->fails()) { + $this->error_array['expense'][] = [ + 'expense' => $expense_data, + 'error' => $validator->errors()->all(), + ]; + } else { + $expense = ExpenseFactory::create( + $this->company->id, + $this->getUserIDForRecord($expense_data) + ); + + $expense_repository->save($expense_data, $expense); + + } + } catch (\Exception $ex) { + if ($ex instanceof ImportException) { + $message = $ex->getMessage(); + } else { + report($ex); + $message = 'Unknown error'; + } + + $this->error_array['expense'][] = [ + 'expense' => $raw_expense, + 'error' => $message, + ]; + } + } + } + } diff --git a/app/Import/Transformer/BaseTransformer.php b/app/Import/Transformer/BaseTransformer.php index ec4e6b01a27c..179c991fa85b 100644 --- a/app/Import/Transformer/BaseTransformer.php +++ b/app/Import/Transformer/BaseTransformer.php @@ -11,7 +11,9 @@ namespace App\Import\Transformer; +use App\Factory\ExpenseCategoryFactory; use App\Factory\ProjectFactory; +use App\Factory\VendorFactory; use App\Models\ClientContact; use App\Models\Country; use App\Models\ExpenseCategory; @@ -369,6 +371,19 @@ class BaseTransformer ->exists(); } + /** * + * @return bool + */ + public function hasExpense($expense_number) + { + return $this->company + ->expenses() + ->whereRaw("LOWER(REPLACE(`number`, ' ' ,'')) = ?", [ + strtolower(str_replace(' ', '', $expense_number)), + ]) + ->exists(); + } + /** * @param $quote_number * @@ -418,6 +433,23 @@ class BaseTransformer return $vendor ? $vendor->id : null; } + public function getVendorIdOrCreate($name) + { + if(empty($name)) + return null; + + $vendor = $this->getVendorId($name); + + if($vendor) + return $vendor; + + $vendor = VendorFactory::create($this->company->id, $this->company->owner()->id); + $vendor->name = $name; + $vendor->save(); + + return $vendor->id; + } + /** * @param $name * @@ -437,12 +469,15 @@ class BaseTransformer public function getOrCreateExpenseCategry($name) { + if(empty($name)) + return null; + $ec = $this->getExpenseCategoryId($name); if($ec) return $ec; - $expense_category = ExpenseCategory::create($this->company->id, $this->company->owner()->id); + $expense_category = ExpenseCategoryFactory::create($this->company->id, $this->company->owner()->id); $expense_category->name = $name; $expense_category->save(); diff --git a/app/Import/Transformer/Wave/ExpenseTransformer.php b/app/Import/Transformer/Wave/ExpenseTransformer.php index f8d5b5ad31e2..f5431b90c50a 100644 --- a/app/Import/Transformer/Wave/ExpenseTransformer.php +++ b/app/Import/Transformer/Wave/ExpenseTransformer.php @@ -9,7 +9,7 @@ * @license https://www.elastic.co/licensing/elastic-license */ -namespace App\Import\Transformer\Waveaccounting; +namespace App\Import\Transformer\Wave; use App\Import\ImportException; use App\Import\Transformer\BaseTransformer; @@ -24,22 +24,43 @@ class ExpenseTransformer extends BaseTransformer { * * @return bool|array */ - public function transform( $data ) { + public function transform( $line_items_data ) + { + + $data = $line_items_data[0]; + + $amount = 0; + $total_tax = 0; + $tax_rate = 0; + + + foreach ( $line_items_data as $record ) + { + + $amount += floatval($record['Amount (One column)']); + $total_tax += floatval($record['Sales Tax Amount']); + + } + + + + $tax_rate = round(($total_tax/$amount)*100,3); $transformed = [ 'company_id' => $this->company->id, - 'vendor_id' => $this->getVendorId($vendor_name = $this->getString($data, 'vendor')), - 'number' => $this->getString($data, 'invoice_number'), - 'public_notes'=> $this->getString($data, 'description'), - 'date' => date( 'Y-m-d', strtotime( $data['bill_date'] ) ) ?: now()->format('Y-m-d'), //27-01-2022 - 'currency_id' => $this->getCurrencyByCode( $data, 'currency' ), - 'category_id' => $this->getOrCreateExpenseCategry($data['account']), - 'amount' => $this->getFloat($data['quantity']) * $this->getFloat($data['amount']), - 'tax_name1' => $this->getTaxName($data['taxes']), - 'tax_rate1' => $this->getTaxRate($data['taxes']), + 'vendor_id' => $this->getVendorIdOrCreate($this->getString($data, 'Vendor')), + 'number' => $this->getString($data, 'Bill Number'), + 'public_notes'=> $this->getString($data, 'Notes / Memo'), + 'date' => date( 'Y-m-d', strtotime( $data['Transaction Date Added'] ) ) ?: now()->format('Y-m-d'), //27-01-2022 + 'currency_id' => $this->company->settings->currency_id, + 'category_id' => $this->getOrCreateExpenseCategry($data['Account Name']), + 'amount' => $amount, + 'tax_name1' => $data['Sales Tax Name'], + 'tax_rate1' => $tax_rate, ]; - return $transformed; } -} \ No newline at end of file +} + + diff --git a/tests/Feature/Import/Wave/WaveTest.php b/tests/Feature/Import/Wave/WaveTest.php index b474dc472cc2..50fe6122da69 100644 --- a/tests/Feature/Import/Wave/WaveTest.php +++ b/tests/Feature/Import/Wave/WaveTest.php @@ -11,6 +11,7 @@ namespace Tests\Feature\Import\CSV; +use App\Import\Providers\BaseImport; use App\Import\Providers\Wave; use App\Import\Transformer\BaseTransformer; use App\Models\Client; @@ -48,7 +49,57 @@ class WaveTest extends TestCase $this->withoutExceptionHandling(); } - public function testVendorWaveImport() + // public function testExpenseImport() + // { + + // $csv = file_get_contents( + // base_path() . '/tests/Feature/Import/wave_expenses.csv' + // ); + // $hash = Str::random(32); + + // $column_map = [ + // 0 => 'Transaction ID', + // 1 => 'Transaction Date', + // 2 => 'Account Name', + // 3 => 'Transaction Description', + // 4 => 'Transaction Line Description', + // 5 => 'Amount (One column)', + // 6 => ' ', + // 7 => 'Debit Amount (Two Column Approach)', + // 8 => 'Credit Amount (Two Column Approach)', + // 9 => 'Other Accounts for this Transaction', + // 10 => 'Customer', + // 11 => 'Vendor', + // 12 => 'Invoice Number', + // 13 => 'Bill Number', + // 14 => 'Notes / Memo', + // 15 => 'Amount Before Sales Tax', + // 16 => 'Sales Tax Amount', + // 17 => 'Sales Tax Name', + // 18 => 'Transaction Date Added', + // 19 => 'Transaction Date Last Modified', + // 20 => 'Account Group', + // 21 => 'Account Type', + // 22 => 'Account ID', + // ]; + + // $data = [ + // 'hash' => $hash, + // 'column_map' => ['expense' => ['mapping' => $column_map]], + // 'skip_header' => true, + // 'import_type' => 'waveaccounting', + // ]; + + // Cache::put($hash . '-expense', base64_encode($csv), 360); + + // $csv_importer = new Wave($data, $this->company); + + // $count = $csv_importer->import('expense'); + + + // } + + public function testVendorAndExpenseWaveImport() { $csv = file_get_contents( base_path() . '/tests/Feature/Import/wave_vendors.csv' @@ -108,6 +159,56 @@ class WaveTest extends TestCase $this->assertEquals('vendor@gmail.com', $vendor->contacts->first()->email); $this->assertEquals('phone', $vendor->contacts->first()->phone); + + // now lets try importing expenses / bills + $csv = file_get_contents( + base_path() . '/tests/Feature/Import/wave_expenses.csv' + ); + $hash = Str::random(32); + + $column_map = [ + 0 => 'Transaction ID', + 1 => 'Transaction Date', + 2 => 'Account Name', + 3 => 'Transaction Description', + 4 => 'Transaction Line Description', + 5 => 'Amount (One column)', + 6 => ' ', + 7 => 'Debit Amount (Two Column Approach)', + 8 => 'Credit Amount (Two Column Approach)', + 9 => 'Other Accounts for this Transaction', + 10 => 'Customer', + 11 => 'Vendor', + 12 => 'Invoice Number', + 13 => 'Bill Number', + 14 => 'Notes / Memo', + 15 => 'Amount Before Sales Tax', + 16 => 'Sales Tax Amount', + 17 => 'Sales Tax Name', + 18 => 'Transaction Date Added', + 19 => 'Transaction Date Last Modified', + 20 => 'Account Group', + 21 => 'Account Type', + 22 => 'Account ID', + ]; + + $data = [ + 'hash' => $hash, + 'column_map' => ['expense' => ['mapping' => $column_map]], + 'skip_header' => true, + 'import_type' => 'waveaccounting', + ]; + + Cache::put($hash . '-expense', base64_encode($csv), 360); + + $csv_importer = new Wave($data, $this->company); + + $count = $csv_importer->import('expense'); + + $base_transformer = new BaseTransformer($this->company); + + $this->assertTrue($base_transformer->hasExpense("66")); + } diff --git a/tests/Feature/Import/wave_expenses.csv b/tests/Feature/Import/wave_expenses.csv new file mode 100644 index 000000000000..4a5a6426060f --- /dev/null +++ b/tests/Feature/Import/wave_expenses.csv @@ -0,0 +1,26 @@ +Transaction ID,Transaction Date,Account Name,Transaction Description,Transaction Line Description,Amount (One column), ,Debit Amount (Two Column Approach),Credit Amount (Two Column Approach),Other Accounts for this Transaction,Customer,Vendor,Invoice Number,Bill Number,Notes / Memo,Amount Before Sales Tax,Sales Tax Amount,Sales Tax Name,Transaction Date Added,Transaction Date Last Modified,Account Group,Account Type,Account ID +537537669383611013,2015-12-30,Owner Investment / Drawings,-hjj,-hjj,96.00,,,96.00,Computer – Hardware,,,,,,96.00,,,2015-12-30,2022-02-10,Equity,Business Owner Contribution and Drawing, +537537669383611013,2015-12-30,Computer – Hardware,-hjj,-hjj,96.00,,96.00,,Owner Investment / Drawings,,,,,,96.00,,,2015-12-30,2022-02-10,Expense,Operating Expense, +537537671069721225,2016-02-20,Cash on Hand,-,-,-20.00,,,20.00,Business Licenses & Permits,,,,,,-20.00,,,2016-02-21,2022-02-10,Asset,Cash and Bank, +537537671069721225,2016-02-20,Business Licenses & Permits,-,-,20.00,,20.00,,Cash on Hand,,,,,,20.00,,,2016-02-21,2022-02-10,Expense,Operating Expense, +1405423775985459539,2022-02-10,Sales,asa - 2,asa - 2 - bananas,10.00,,,10.00,"Accounts Receivable, Capitation Fees, GST",asa,,2,,,10.00,,,2022-02-10,2022-02-10,Income,Income, +1405423775985459539,2022-02-10,Sales,asa - 2,asa - 2 - bananas,10.00,,,10.00,"Accounts Receivable, Capitation Fees, GST",asa,,2,,,10.00,,,2022-02-10,2022-02-10,Income,Income, +1405423775985459539,2022-02-10,Capitation Fees,asa - 2,asa - 2 - Best Product Name,10.00,,,10.00,"Accounts Receivable, GST, Sales",asa,,2,,,10.00,1.00,GST,2022-02-10,2022-02-10,Income,Income, +1405423775985459539,2022-02-10,GST,asa - 2,asa - 2 - Best Production Description,1.00,,,1.00,"Accounts Receivable, Capitation Fees, Sales",asa,,2,,,1.00,,,2022-02-10,2022-02-10,Liability,Sales Tax on Sales and Purchases, +1405423775985459539,2022-02-10,Capitation Fees,asa - 2,asa - 2 - Best Product Name,10.00,,,10.00,"Accounts Receivable, GST, Sales",asa,,2,,,10.00,1.00,GST,2022-02-10,2022-02-10,Income,Income, +1405423775985459539,2022-02-10,GST,asa - 2,asa - 2 - Best Production Description,1.00,,,1.00,"Accounts Receivable, Capitation Fees, Sales",asa,,2,,,1.00,,,2022-02-10,2022-02-10,Liability,Sales Tax on Sales and Purchases, +1405423775985459539,2022-02-10,Accounts Receivable,asa - 2,asa - 2,42.00,,42.00,,"Capitation Fees, GST, Sales",asa,,2,,,42.00,,,2022-02-10,2022-02-10,Asset,System Receivable Invoice, +1405429262361584366,2022-02-10,Computer – Hosting,Vendor Name - Bill ,Vendor Name - Bill - Best Product Name,10.00,,10.00,,"Accounts Payable, GST",,Vendor Name,,,,10.00,1.00,GST,2022-02-10,2022-02-10,Expense,Operating Expense, +1405429262361584366,2022-02-10,GST,Vendor Name - Bill ,Vendor Name - Bill - Best Product Name,-1.00,,1.00,,"Accounts Payable, Computer – Hosting",,Vendor Name,,,,1.00,,,2022-02-10,2022-02-10,Liability,Sales Tax on Sales and Purchases, +1405429262361584366,2022-02-10,Accounts Payable,Vendor Name - Bill ,Vendor Name - Bill ,11.00,,,11.00,"Computer – Hosting, GST",,Vendor Name,,,,11.00,,,2022-02-10,2022-02-10,Liability,System Payable Bill, +1405437089897538538,2022-02-10,Computer – Hosting,Vendor Name - Bill 66,Vendor Name - Bill 66 - Best Product Name,200.00,,200.00,,"Accounts Payable, GST",,Vendor Name,,66,,200.00,20.00,GST,2022-02-10,2022-02-10,Expense,Operating Expense, +1405437089897538538,2022-02-10,GST,Vendor Name - Bill 66,Vendor Name - Bill 66 - Best Product Name,-20.00,,20.00,,"Accounts Payable, Computer – Hosting",,Vendor Name,,66,,20.00,,,2022-02-10,2022-02-10,Liability,Sales Tax on Sales and Purchases, +1405437089897538538,2022-02-10,Computer – Hosting,Vendor Name - Bill 66,Vendor Name - Bill 66 - Best Product Name,10.00,,10.00,,"Accounts Payable, GST",,Vendor Name,,66,,10.00,1.00,GST,2022-02-10,2022-02-10,Expense,Operating Expense, +1405437089897538538,2022-02-10,GST,Vendor Name - Bill 66,Vendor Name - Bill 66 - Best Product Name,-1.00,,1.00,,"Accounts Payable, Computer – Hosting",,Vendor Name,,66,,1.00,,,2022-02-10,2022-02-10,Liability,Sales Tax on Sales and Purchases, +1405437089897538538,2022-02-10,Computer – Hosting,Vendor Name - Bill 66,Vendor Name - Bill 66 - Best Product Name,10.00,,10.00,,"Accounts Payable, GST",,Vendor Name,,66,,10.00,1.00,GST,2022-02-10,2022-02-10,Expense,Operating Expense, +1405437089897538538,2022-02-10,GST,Vendor Name - Bill 66,Vendor Name - Bill 66 - Best Product Name,-1.00,,1.00,,"Accounts Payable, Computer – Hosting",,Vendor Name,,66,,1.00,,,2022-02-10,2022-02-10,Liability,Sales Tax on Sales and Purchases, +1405437089897538538,2022-02-10,Computer – Hosting,Vendor Name - Bill 66,Vendor Name - Bill 66 - Best Product Name,10.00,,10.00,,"Accounts Payable, GST",,Vendor Name,,66,,10.00,1.00,GST,2022-02-10,2022-02-10,Expense,Operating Expense, +1405437089897538538,2022-02-10,GST,Vendor Name - Bill 66,Vendor Name - Bill 66 - Best Product Name,-1.00,,1.00,,"Accounts Payable, Computer – Hosting",,Vendor Name,,66,,1.00,,,2022-02-10,2022-02-10,Liability,Sales Tax on Sales and Purchases, +1405437089897538538,2022-02-10,Accounts Payable,Vendor Name - Bill 66,Vendor Name - Bill 66,253.00,,,253.00,"Computer – Hosting, GST",,Vendor Name,,66,,253.00,,,2022-02-10,2022-02-10,Liability,System Payable Bill, +1405441958066807314,2022-02-10,Accounts Payable,Bill Payment,Bill Payment,-253.00,,253.00,,Cash on Hand,,Vendor Name,,66,,-253.00,,,2022-02-10,2022-02-10,Liability,System Payable Bill, +1405441958066807314,2022-02-10,Cash on Hand,Bill Payment,Bill Payment,-253.00,,,253.00,Accounts Payable,,,,,,-253.00,,,2022-02-10,2022-02-10,Asset,Cash and Bank,