diff --git a/app/Factory/RecurringQuoteFactory.php b/app/Factory/RecurringQuoteFactory.php index 7b76b6dbfc9b..cb326e2917e0 100644 --- a/app/Factory/RecurringQuoteFactory.php +++ b/app/Factory/RecurringQuoteFactory.php @@ -22,6 +22,7 @@ class RecurringQuoteFactory $quote->discount = 0; $quote->is_amount_discount = true; $quote->po_number = ''; + $quote->number = ''; $quote->footer = ''; $quote->terms = ''; $quote->public_notes = ''; @@ -48,6 +49,7 @@ class RecurringQuoteFactory $quote->last_sent_date = null; $quote->next_send_date = null; $quote->remaining_cycles = 0; + $quote->paid_to_date = 0; return $quote; } diff --git a/app/Http/Requests/RecurringQuote/StoreRecurringQuoteRequest.php b/app/Http/Requests/RecurringQuote/StoreRecurringQuoteRequest.php index 375b076aee24..51ee3917501a 100644 --- a/app/Http/Requests/RecurringQuote/StoreRecurringQuoteRequest.php +++ b/app/Http/Requests/RecurringQuote/StoreRecurringQuoteRequest.php @@ -63,42 +63,7 @@ class StoreRecurringQuoteRequest extends Request protected function prepareForValidation() { $input = $this->all(); - - if (array_key_exists('design_id', $input) && is_string($input['design_id'])) { - $input['design_id'] = $this->decodePrimaryKey($input['design_id']); - } - - if (array_key_exists('client_id', $input) && is_string($input['client_id'])) { - $input['client_id'] = $this->decodePrimaryKey($input['client_id']); - } - - if (array_key_exists('assigned_user_id', $input) && is_string($input['assigned_user_id'])) { - $input['assigned_user_id'] = $this->decodePrimaryKey($input['assigned_user_id']); - } - - if (isset($input['client_contacts'])) { - foreach ($input['client_contacts'] as $key => $contact) { - if (! array_key_exists('send_email', $contact) || ! array_key_exists('id', $contact)) { - unset($input['client_contacts'][$key]); - } - } - } - - if (isset($input['invitations'])) { - foreach ($input['invitations'] as $key => $value) { - if (isset($input['invitations'][$key]['id']) && is_numeric($input['invitations'][$key]['id'])) { - unset($input['invitations'][$key]['id']); - } - - if (isset($input['invitations'][$key]['id']) && is_string($input['invitations'][$key]['id'])) { - $input['invitations'][$key]['id'] = $this->decodePrimaryKey($input['invitations'][$key]['id']); - } - - if (is_string($input['invitations'][$key]['client_contact_id'])) { - $input['invitations'][$key]['client_contact_id'] = $this->decodePrimaryKey($input['invitations'][$key]['client_contact_id']); - } - } - } + $input = $this->decodePrimaryKeys($input); $input['line_items'] = isset($input['line_items']) ? $this->cleanItems($input['line_items']) : []; diff --git a/app/Http/Requests/RecurringQuote/UpdateRecurringQuoteRequest.php b/app/Http/Requests/RecurringQuote/UpdateRecurringQuoteRequest.php index 7278d8d1b246..2f620cfcce1e 100644 --- a/app/Http/Requests/RecurringQuote/UpdateRecurringQuoteRequest.php +++ b/app/Http/Requests/RecurringQuote/UpdateRecurringQuoteRequest.php @@ -58,34 +58,7 @@ class UpdateRecurringQuoteRequest extends Request protected function prepareForValidation() { $input = $this->all(); - - if (array_key_exists('design_id', $input) && is_string($input['design_id'])) { - $input['design_id'] = $this->decodePrimaryKey($input['design_id']); - } - - if (isset($input['client_id'])) { - $input['client_id'] = $this->decodePrimaryKey($input['client_id']); - } - - if (array_key_exists('assigned_user_id', $input) && is_string($input['assigned_user_id'])) { - $input['assigned_user_id'] = $this->decodePrimaryKey($input['assigned_user_id']); - } - - if (isset($input['invitations'])) { - foreach ($input['invitations'] as $key => $value) { - if (is_numeric($input['invitations'][$key]['id'])) { - unset($input['invitations'][$key]['id']); - } - - if (array_key_exists('id', $input['invitations'][$key]) && is_string($input['invitations'][$key]['id'])) { - $input['invitations'][$key]['id'] = $this->decodePrimaryKey($input['invitations'][$key]['id']); - } - - if (is_string($input['invitations'][$key]['client_contact_id'])) { - $input['invitations'][$key]['client_contact_id'] = $this->decodePrimaryKey($input['invitations'][$key]['client_contact_id']); - } - } - } + $input = $this->decodePrimaryKeys($input); if (isset($input['line_items'])) { $input['line_items'] = isset($input['line_items']) ? $this->cleanItems($input['line_items']) : []; diff --git a/app/Transformers/RecurringQuoteInvitationTransformer.php b/app/Transformers/RecurringQuoteInvitationTransformer.php new file mode 100644 index 000000000000..db03289b0678 --- /dev/null +++ b/app/Transformers/RecurringQuoteInvitationTransformer.php @@ -0,0 +1,38 @@ + $this->encodePrimaryKey($invitation->id), + 'client_contact_id' => $this->encodePrimaryKey($invitation->client_contact_id), + 'key' => $invitation->key, + 'link' => $invitation->getLink() ?: '', + 'sent_date' => $invitation->sent_date ?: '', + 'viewed_date' => $invitation->viewed_date ?: '', + 'opened_date' => $invitation->opened_date ?: '', + 'updated_at' => (int) $invitation->updated_at, + 'archived_at' => (int) $invitation->deleted_at, + 'created_at' => (int) $invitation->created_at, + 'email_status' => $invitation->email_status ?: '', + 'email_error' => (string)$invitation->email_error, + ]; + } +} diff --git a/database/migrations/2021_08_23_101529_recurring_expenses_schema.php b/database/migrations/2021_08_23_101529_recurring_expenses_schema.php index cc99d58d543d..93adb406f418 100644 --- a/database/migrations/2021_08_23_101529_recurring_expenses_schema.php +++ b/database/migrations/2021_08_23_101529_recurring_expenses_schema.php @@ -77,6 +77,49 @@ class RecurringExpensesSchema extends Migration $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade')->onUpdate('cascade'); }); + + + Schema::create('recurring_quote_invitations', function ($t) { + $t->increments('id'); + $t->unsignedInteger('company_id'); + $t->unsignedInteger('user_id'); + $t->unsignedInteger('client_contact_id'); + $t->unsignedInteger('recurring_quote_id')->index(); + $t->string('key')->index(); + + $t->foreign('recurring_quote_id')->references('id')->on('recurring_invoices')->onDelete('cascade')->onUpdate('cascade'); + $t->foreign('user_id')->references('id')->on('users')->onDelete('cascade')->onUpdate('cascade'); + $t->foreign('client_contact_id')->references('id')->on('client_contacts')->onDelete('cascade')->onUpdate('cascade'); + $t->foreign('company_id')->references('id')->on('companies')->onDelete('cascade')->onUpdate('cascade'); + + $t->string('transaction_reference')->nullable(); + $t->string('message_id')->nullable(); + $t->mediumText('email_error')->nullable(); + $t->text('signature_base64')->nullable(); + $t->datetime('signature_date')->nullable(); + + $t->datetime('sent_date')->nullable(); + $t->datetime('viewed_date')->nullable(); + $t->datetime('opened_date')->nullable(); + $t->enum('email_status', ['delivered', 'bounced', 'spam'])->nullable(); + + $t->timestamps(6); + $t->softDeletes('deleted_at', 6); + + $t->index(['deleted_at', 'recurring_quote_id', 'company_id'], 'rec_co_del_q'); + $t->unique(['client_contact_id', 'recurring_quote_id'], 'cli_rec_q'); + }); + + + + + + + + + + + } /** diff --git a/routes/api.php b/routes/api.php index 35f42a9ed604..3b41ab6888fb 100644 --- a/routes/api.php +++ b/routes/api.php @@ -138,6 +138,8 @@ Route::group(['middleware' => ['api_db', 'token_auth', 'locale'], 'prefix' => 'a Route::post('recurring_invoices/bulk', 'RecurringInvoiceController@bulk')->name('recurring_invoices.bulk'); Route::put('recurring_invoices/{recurring_invoice}/upload', 'RecurringInvoiceController@upload'); Route::resource('recurring_quotes', 'RecurringQuoteController'); // name = (recurring_invoices. index / create / show / update / destroy / edit + Route::post('recurring_quotes/bulk', 'RecurringQuoteController@bulk')->name('recurring_quotes.bulk'); + Route::put('recurring_quotes/{recurring_quote}/upload', 'RecurringQuoteController@upload'); Route::post('recurring_quotes/bulk', 'RecurringQuoteController@bulk')->name('recurring_quotes.bulk'); diff --git a/tests/Feature/RecurringQuotesTest.php b/tests/Feature/RecurringQuotesTest.php new file mode 100644 index 000000000000..ee66395827f3 --- /dev/null +++ b/tests/Feature/RecurringQuotesTest.php @@ -0,0 +1,195 @@ +faker = \Faker\Factory::create(); + + Model::reguard(); + + $this->withoutMiddleware( + ThrottleRequests::class + ); + + $this->makeTestData(); + } + + public function testRecurringQuoteList() + { + // Client::factory()->create(['user_id' => $this->user->id, 'company_id' => $this->company->id])->each(function ($c) { + // ClientContact::factory()->create([ + // 'user_id' => $this->user->id, + // 'client_id' => $c->id, + // 'company_id' => $this->company->id, + // 'is_primary' => 1, + // ]); + + // ClientContact::factory()->create([ + // 'user_id' => $this->user->id, + // 'client_id' => $c->id, + // 'company_id' => $this->company->id, + // ]); + // }); + + // $client = Client::all()->first(); + + // RecurringQuote::factory()->create(['user_id' => $this->user->id, 'company_id' => $this->company->id, 'client_id' => $this->client->id]); + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->get('/api/v1/recurring_quotes'); + + $response->assertStatus(200); + } + + public function testRecurringQuoteRESTEndPoints() + { + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->get('/api/v1/recurring_quotes/'.$this->recurring_quote->hashed_id); + + $response->assertStatus(200); + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->get('/api/v1/recurring_quotes/'.$this->recurring_quote->hashed_id.'/edit'); + + $response->assertStatus(200); + + $RecurringQuote_update = [ + 'status_id' => RecurringQuote::STATUS_DRAFT, + 'number' => 'customnumber' + ]; + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->put('/api/v1/recurring_quotes/'.$this->recurring_quote->hashed_id, $RecurringQuote_update); + + $response->assertStatus(200); + $arr = $response->json(); + + $this->assertEquals('customnumber', $arr['data']['number']); + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->put('/api/v1/recurring_quotes/'.$this->recurring_quote->hashed_id, $RecurringQuote_update) + ->assertStatus(200); + + $RecurringQuote_update = [ + 'status_id' => RecurringQuote::STATUS_DRAFT, + 'client_id' => $this->recurring_quote->hashed_id, + 'number' => 'customnumber' + ]; + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->post('/api/v1/recurring_quotes/', $RecurringQuote_update) + ->assertStatus(302); + + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->delete('/api/v1/recurring_quotes/'.$this->encodePrimaryKey($this->recurring_quote->id)); + + $response->assertStatus(200); + } + + public function testSubscriptionIdPassesToQuote() + { + $recurring_invoice = QuoteToRecurringQuoteFactory::create($this->invoice); + $recurring_invoice->user_id = $this->user->id; + $recurring_invoice->next_send_date = \Carbon\Carbon::now()->addDays(10); + $recurring_invoice->status_id = RecurringQuote::STATUS_ACTIVE; + $recurring_invoice->remaining_cycles = 2; + $recurring_invoice->next_send_date = \Carbon\Carbon::now()->addDays(10); + $recurring_invoice->save(); + + $recurring_invoice->number = $this->getNextRecurringQuoteNumber($this->invoice->client, $this->invoice); + $recurring_invoice->subscription_id = 10; + $recurring_invoice->save(); + + $invoice = RecurringQuoteToQuoteFactory::create($recurring_invoice, $this->client); + + $this->assertEquals(10, $invoice->subscription_id); + } + + public function testSubscriptionIdPassesToQuoteIfNull() + { + $recurring_invoice = QuoteToRecurringQuoteFactory::create($this->invoice); + $recurring_invoice->user_id = $this->user->id; + $recurring_invoice->next_send_date = \Carbon\Carbon::now()->addDays(10); + $recurring_invoice->status_id = RecurringQuote::STATUS_ACTIVE; + $recurring_invoice->remaining_cycles = 2; + $recurring_invoice->next_send_date = \Carbon\Carbon::now()->addDays(10); + $recurring_invoice->save(); + + $recurring_invoice->number = $this->getNextRecurringQuoteNumber($this->invoice->client, $this->invoice); + $recurring_invoice->save(); + + $invoice = RecurringQuoteToQuoteFactory::create($recurring_invoice, $this->client); + + $this->assertEquals(null, $invoice->subscription_id); + } + + public function testSubscriptionIdPassesToQuoteIfNothingSet() + { + $recurring_invoice = QuoteToRecurringQuoteFactory::create($this->invoice); + $recurring_invoice->user_id = $this->user->id; + $recurring_invoice->next_send_date = \Carbon\Carbon::now()->addDays(10); + $recurring_invoice->status_id = RecurringQuote::STATUS_ACTIVE; + $recurring_invoice->remaining_cycles = 2; + $recurring_invoice->next_send_date = \Carbon\Carbon::now()->addDays(10); + $recurring_invoice->save(); + + $recurring_invoice->number = $this->getNextRecurringQuoteNumber($this->invoice->client, $this->invoice); + $recurring_invoice->save(); + + $invoice = RecurringQuoteToQuoteFactory::create($recurring_invoice, $this->client); + + $this->assertEquals(null, $invoice->subscription_id); + } +} diff --git a/tests/MockAccountData.php b/tests/MockAccountData.php index 194b5a9ee2fa..f63e3603193b 100644 --- a/tests/MockAccountData.php +++ b/tests/MockAccountData.php @@ -38,6 +38,7 @@ use App\Models\Quote; use App\Models\QuoteInvitation; use App\Models\RecurringExpense; use App\Models\RecurringInvoice; +use App\Models\RecurringQuote; use App\Models\Task; use App\Models\TaskStatus; use App\Models\User; @@ -88,7 +89,12 @@ trait MockAccountData * @var */ public $recurring_expense; - + + /** + * @var + */ + public $recurring_quote; + /** * @var */ @@ -297,6 +303,11 @@ trait MockAccountData 'company_id' => $this->company->id, ]); + $this->recurring_quote = RecurringQuote::factory()->create([ + 'user_id' => $user_id, + 'company_id' => $this->company->id, + 'client_id' => $this->client->id, + ]); $this->task = Task::factory()->create([ 'user_id' => $user_id,