From b2f8479abfc0cdadcc532bc6b6d44e3d0821d1b2 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Wed, 2 Feb 2022 15:56:37 +1100 Subject: [PATCH 1/4] Import CSV refactor --- app/Import/Providers/BaseImport.php | 48 ++++++++-- app/Import/Providers/Csv.php | 5 +- app/Import/Transformer/BaseTransformer.php | 14 +-- .../Transformer/Csv/InvoiceTransformer.php | 15 +++- tests/Feature/Import/CSV/CsvImportTest.php | 89 +++++++++++++++++++ 5 files changed, 154 insertions(+), 17 deletions(-) diff --git a/app/Import/Providers/BaseImport.php b/app/Import/Providers/BaseImport.php index 995c759fff64..987d52a3b418 100644 --- a/app/Import/Providers/BaseImport.php +++ b/app/Import/Providers/BaseImport.php @@ -10,10 +10,17 @@ */ namespace App\Import\Providers; +use App\Factory\ClientFactory; +use App\Factory\InvoiceFactory; +use App\Factory\PaymentFactory; +use App\Http\Requests\Invoice\StoreInvoiceRequest; use App\Import\ImportException; use App\Models\Company; +use App\Models\Invoice; use App\Models\User; +use App\Repositories\ClientRepository; use App\Repositories\InvoiceRepository; +use App\Repositories\PaymentRepository; use App\Utils\Traits\CleanLineItems; use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\DB; @@ -161,7 +168,7 @@ class BaseImport { } } - public function ingestInvoices( $invoices ) { + public function ingestInvoices( $invoices , $invoice_number_key) { $invoice_transformer = $this->transformer; /** @var PaymentRepository $payment_repository */ @@ -175,13 +182,14 @@ class BaseImport { $invoice_repository = new InvoiceRepository(); $invoice_repository->import_mode = true; + $invoices = $this->groupInvoices($invoices, $invoice_number_key); + foreach ( $invoices as $raw_invoice ) { try { + $invoice_data = $invoice_transformer->transform( $raw_invoice ); - $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']; @@ -205,7 +213,6 @@ class BaseImport { $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. @@ -273,13 +280,36 @@ class BaseImport { + 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_DRAFT ){ + + } + elseif ( $invoice->status_id === Invoice::STATUS_SENT ) { + $invoice = $invoice->service()->markSent()->save(); + } + elseif ( $invoice->status_id <= Invoice::STATUS_SENT && $invoice->amount > 0 ) { + if ( $invoice->balance <= 0 ) { + $invoice->status_id = Invoice::STATUS_PAID; + $invoice->save(); + } + elseif ( $invoice->balance != $invoice->amount ) { + $invoice->status_id = Invoice::STATUS_PARTIAL; + $invoice->save(); + } + } - - - - - + return $invoice; + } diff --git a/app/Import/Providers/Csv.php b/app/Import/Providers/Csv.php index 0eee3c42a85c..d7e0ffa73c24 100644 --- a/app/Import/Providers/Csv.php +++ b/app/Import/Providers/Csv.php @@ -20,6 +20,7 @@ use App\Import\ImportException; use App\Import\Providers\BaseImport; use App\Import\Providers\ImportInterface; use App\Import\Transformer\Csv\ClientTransformer; +use App\Import\Transformer\Csv\InvoiceTransformer; use App\Import\Transformer\Csv\ProductTransformer; use App\Repositories\ClientRepository; use App\Repositories\InvoiceRepository; @@ -123,9 +124,9 @@ class Csv extends BaseImport implements ImportInterface $this->repository = app()->make( $this->repository_name ); $this->repository->import_mode = true; - $this->transformer = new ProductTransformer($this->company); + $this->transformer = new InvoiceTransformer($this->company); - $invoice_count = $this->ingest($data, $entity_type); + $invoice_count = $this->ingestInvoices($data, 'invoice.number'); $this->entity_count['invoices'] = $invoice_count; diff --git a/app/Import/Transformer/BaseTransformer.php b/app/Import/Transformer/BaseTransformer.php index ca6d2a038ece..8476bb59b4b1 100644 --- a/app/Import/Transformer/BaseTransformer.php +++ b/app/Import/Transformer/BaseTransformer.php @@ -53,20 +53,23 @@ class BaseTransformer } - public function getClient($client_name, $client_email) { + public function getClient($client_name, $client_email) + { + // nlog("searching for {$client_name} with email {$client_email}"); + $client_id_search = $this->company->clients()->where( 'id_number', $client_name ); if ( $client_id_search->count() >= 1 ) { + // nlog("found via id number => {$client_id_search->first()->id}"); return $client_id_search->first()->id; - nlog("found via id number"); } $client_name_search = $this->company->clients()->where( 'name', $client_name ); if ( $client_name_search->count() >= 1 ) { + // nlog("found via name {$client_name_search->first()->id}"); return $client_name_search->first()->id; - nlog("found via name"); } if ( ! empty( $client_email ) ) { @@ -74,11 +77,12 @@ class BaseTransformer ->where( 'email', $client_email ); if ( $contacts->count() >= 1 ) { + // nlog("found via contact {$contacts->first()->client_id}"); return $contacts->first()->client_id; - nlog("found via contact"); } } - nlog("did not find client"); + + // nlog("did not find client"); return null; } diff --git a/app/Import/Transformer/Csv/InvoiceTransformer.php b/app/Import/Transformer/Csv/InvoiceTransformer.php index d182e4cadb0a..8567ee7129df 100644 --- a/app/Import/Transformer/Csv/InvoiceTransformer.php +++ b/app/Import/Transformer/Csv/InvoiceTransformer.php @@ -13,18 +13,22 @@ namespace App\Import\Transformer\Csv; use App\Import\ImportException; use App\Import\Transformer\BaseTransformer; +use App\Import\Transformer\Csv\ClientTransformer; 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'] ) ) { @@ -74,6 +78,15 @@ class InvoiceTransformer extends BaseTransformer { 'archived' => $status === 'archived', ]; + if(!$transformed['client_id']){ + + $client_transformer = new ClientTransformer($this->company); + + $transformed['client'] = $client_transformer->transform($invoice_data); + + } + + if ( isset( $invoice_data['payment.amount'] ) ) { $transformed['payments'] = [ [ @@ -119,7 +132,7 @@ class InvoiceTransformer extends BaseTransformer { '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' ), + 'type_id' => "1", //$this->getInvoiceTypeId( $record, 'item.type_id' ), ]; } $transformed['line_items'] = $line_items; diff --git a/tests/Feature/Import/CSV/CsvImportTest.php b/tests/Feature/Import/CSV/CsvImportTest.php index 76c2631bba4b..3d53f12b4f0f 100644 --- a/tests/Feature/Import/CSV/CsvImportTest.php +++ b/tests/Feature/Import/CSV/CsvImportTest.php @@ -101,6 +101,95 @@ class CsvImportTest extends TestCase } + 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")); + + } + + + + + + + + + + + + + + } From 4322a31aeb6919dbedec999f53194e90b2ef0f46 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Wed, 2 Feb 2022 16:44:14 +1100 Subject: [PATCH 2/4] Tests for importing Products from csv --- .../Transformer/Csv/InvoiceTransformer.php | 3 +- tests/Feature/Import/CSV/CsvImportTest.php | 34 ++++++++++++++++++- 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/app/Import/Transformer/Csv/InvoiceTransformer.php b/app/Import/Transformer/Csv/InvoiceTransformer.php index 8567ee7129df..fb683ac32737 100644 --- a/app/Import/Transformer/Csv/InvoiceTransformer.php +++ b/app/Import/Transformer/Csv/InvoiceTransformer.php @@ -78,12 +78,13 @@ class InvoiceTransformer extends BaseTransformer { 'archived' => $status === 'archived', ]; + /* If we can't find the client, then lets try and create a client */ if(!$transformed['client_id']){ $client_transformer = new ClientTransformer($this->company); $transformed['client'] = $client_transformer->transform($invoice_data); - + } diff --git a/tests/Feature/Import/CSV/CsvImportTest.php b/tests/Feature/Import/CSV/CsvImportTest.php index 3d53f12b4f0f..3c756238b7e1 100644 --- a/tests/Feature/Import/CSV/CsvImportTest.php +++ b/tests/Feature/Import/CSV/CsvImportTest.php @@ -54,7 +54,39 @@ class CsvImportTest extends TestCase $this->withoutExceptionHandling(); } - public function testCsvFeature() + 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); From 8918431b5164bbd1cc4a2f5db09452566ca57b84 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Wed, 2 Feb 2022 17:07:37 +1100 Subject: [PATCH 3/4] Payment Transformer for CSV imports --- .../Transformer/Csv/PaymentTransformer.php | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 app/Import/Transformer/Csv/PaymentTransformer.php diff --git a/app/Import/Transformer/Csv/PaymentTransformer.php b/app/Import/Transformer/Csv/PaymentTransformer.php new file mode 100644 index 000000000000..b1264d938f2a --- /dev/null +++ b/app/Import/Transformer/Csv/PaymentTransformer.php @@ -0,0 +1,64 @@ +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; + } +} From c1081f46ce778fb59e3ca7287ebbe4c0ac688124 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Wed, 2 Feb 2022 19:51:29 +1100 Subject: [PATCH 4/4] Fixes for blank currency_id in manually created payments --- app/PaymentDrivers/Stripe/CreditCard.php | 7 ++++++- app/Repositories/PaymentRepository.php | 3 +++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/app/PaymentDrivers/Stripe/CreditCard.php b/app/PaymentDrivers/Stripe/CreditCard.php index 4fb3691010d7..7f8451224cf2 100644 --- a/app/PaymentDrivers/Stripe/CreditCard.php +++ b/app/PaymentDrivers/Stripe/CreditCard.php @@ -58,11 +58,16 @@ class CreditCard public function paymentView(array $data) { + + + $description = $this->stripe->decodeUnicodeString(ctrans('texts.invoices') . ': ' . collect($data['invoices'])->pluck('invoice_number')) . " for client {$this->stripe->client->present()->name()}"; + + $payment_intent_data = [ 'amount' => $this->stripe->convertToStripeAmount($data['total']['amount_with_fee'], $this->stripe->client->currency()->precision, $this->stripe->client->currency()), 'currency' => $this->stripe->client->getCurrencyCode(), 'customer' => $this->stripe->findOrCreateCustomer(), - 'description' => $this->stripe->decodeUnicodeString(ctrans('texts.invoices') . ': ' . collect($data['invoices'])->pluck('invoice_number')), + 'description' => $description, 'metadata' => [ 'payment_hash' => $this->stripe->payment_hash->hash, 'gateway_type_id' => GatewayType::CREDIT_CARD, diff --git a/app/Repositories/PaymentRepository.php b/app/Repositories/PaymentRepository.php index b66356a42d99..be59fe955d59 100644 --- a/app/Repositories/PaymentRepository.php +++ b/app/Repositories/PaymentRepository.php @@ -211,6 +211,9 @@ class PaymentRepository extends BaseRepository { $payment->currency_id = $client_currency; } + + $payment->currency_id = $company_currency; + return $payment; }