diff --git a/app/Console/Commands/SetupDummyAccount.php b/app/Console/Commands/SetupDummyAccount.php index c7027e652..7eb554775 100644 --- a/app/Console/Commands/SetupDummyAccount.php +++ b/app/Console/Commands/SetupDummyAccount.php @@ -5,6 +5,7 @@ namespace App\Console\Commands; use App\Contact\ManageContact\Services\CreateContact; use App\Contact\ManageContactImportantDates\Services\CreateContactImportantDate; use App\Contact\ManageNotes\Services\CreateNote; +use App\Contact\ManageTasks\Services\CreateContactTask; use App\Models\Contact; use App\Models\ContactImportantDate; use App\Models\User; @@ -57,6 +58,7 @@ class SetupDummyAccount extends Command $this->createVaults(); $this->createContacts(); $this->createNotes(); + $this->createTasks(); $this->stop(); } @@ -189,6 +191,24 @@ class SetupDummyAccount extends Command } } + private function createTasks(): void + { + $this->info('☐ Create tasks'); + + foreach (Contact::all() as $contact) { + for ($i = 0; $i < 4; $i++) { + (new CreateContactTask)->execute([ + 'account_id' => $this->user->account_id, + 'author_id' => $this->user->id, + 'vault_id' => $contact->vault_id, + 'contact_id' => $contact->id, + 'label' => $this->faker->sentence(rand(3, 6)), + 'description' => null, + ]); + } + } + } + private function artisan(string $message, string $command, array $arguments = []): void { $this->info($message); diff --git a/app/Jobs/SetupAccount.php b/app/Jobs/SetupAccount.php index dca39b41f..bc2d5b23f 100644 --- a/app/Jobs/SetupAccount.php +++ b/app/Jobs/SetupAccount.php @@ -363,6 +363,22 @@ class SetupAccount implements ShouldQueue 'template_page_id' => $templatePageInformation->id, 'module_id' => $module->id, ]); + + // Calls + $module = (new CreateModule)->execute([ + 'account_id' => $this->user->account_id, + 'author_id' => $this->user->id, + 'name' => trans('app.module_calls'), + 'type' => Module::TYPE_CALLS, + 'can_be_deleted' => false, + ]); + (new AssociateModuleToTemplatePage)->execute([ + 'account_id' => $this->user->account_id, + 'author_id' => $this->user->id, + 'template_id' => $this->template->id, + 'template_page_id' => $templatePageInformation->id, + 'module_id' => $module->id, + ]); } private function addFirstInformation(): void diff --git a/app/Models/Call.php b/app/Models/Call.php new file mode 100644 index 000000000..197ff6ae2 --- /dev/null +++ b/app/Models/Call.php @@ -0,0 +1,88 @@ + 'boolean', + ]; + + /** + * The attributes that should be mutated to dates. + * + * @var array + */ + protected $dates = [ + 'called_at', + ]; + + /** + * Get the contact associated with the call. + * + * @return BelongsTo + */ + public function contact() + { + return $this->belongsTo(Contact::class); + } + + /** + * Get the author associated with the call. + * + * @return BelongsTo + */ + public function author() + { + return $this->belongsTo(User::class); + } + + /** + * Get the call reason associated with the call. + * + * @return BelongsTo + */ + public function callReason() + { + return $this->belongsTo(CallReason::class, 'call_reason_id'); + } +} diff --git a/app/Models/Contact.php b/app/Models/Contact.php index bd32623d8..167cf0698 100644 --- a/app/Models/Contact.php +++ b/app/Models/Contact.php @@ -269,6 +269,16 @@ class Contact extends Model return $this->hasMany(ContactTask::class); } + /** + * Get the calls associated with the contact. + * + * @return HasMany + */ + public function calls() + { + return $this->hasMany(Call::class); + } + /** * Get the name of the contact, according to the user preference. * diff --git a/app/Models/Module.php b/app/Models/Module.php index 42c0e6d1a..bd53492a0 100644 --- a/app/Models/Module.php +++ b/app/Models/Module.php @@ -27,6 +27,7 @@ class Module extends Model const TYPE_LOANS = 'loans'; const TYPE_RELATIONSHIPS = 'relationships'; const TYPE_TASKS = 'tasks'; + const TYPE_CALLS = 'calls'; /** * The attributes that are mass assignable. diff --git a/database/factories/CallFactory.php b/database/factories/CallFactory.php new file mode 100644 index 000000000..6c739b036 --- /dev/null +++ b/database/factories/CallFactory.php @@ -0,0 +1,40 @@ + Contact::factory(), + 'author_id' => User::factory(), + 'call_reason_id' => CallReason::factory(), + 'author_name' => $this->faker->name, + 'called_at' => $this->faker->dateTimeThisCentury(), + 'duration' => 100, + 'type' => Call::TYPE_AUDIO, + 'answered' => true, + 'who_initiated' => Call::INITIATOR_ME, + 'description' => $this->faker->sentence, + ]; + } +} diff --git a/database/migrations/2022_05_16_193917_create_calls_table.php b/database/migrations/2022_05_16_193917_create_calls_table.php new file mode 100644 index 000000000..8e695c670 --- /dev/null +++ b/database/migrations/2022_05_16_193917_create_calls_table.php @@ -0,0 +1,47 @@ +id(); + $table->unsignedBigInteger('contact_id'); + $table->unsignedBigInteger('call_reason_id')->nullable(); + $table->unsignedBigInteger('author_id')->nullable(); + $table->string('author_name'); + $table->datetime('called_at'); + $table->integer('duration')->nullable(); + $table->string('type'); + $table->text('description')->nullable(); + $table->boolean('answered')->default(true); + $table->string('who_initiated'); + $table->timestamps(); + $table->foreign('contact_id')->references('id')->on('contacts')->onDelete('cascade'); + $table->foreign('call_reason_id')->references('id')->on('call_reasons')->onDelete('cascade'); + $table->foreign('author_id')->references('id')->on('users')->onDelete('set null'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('calls'); + } +}; diff --git a/domains/Contact/ManageCalls/Services/CreateCall.php b/domains/Contact/ManageCalls/Services/CreateCall.php new file mode 100644 index 000000000..e9a37404d --- /dev/null +++ b/domains/Contact/ManageCalls/Services/CreateCall.php @@ -0,0 +1,97 @@ + 'required|integer|exists:accounts,id', + 'vault_id' => 'required|integer|exists:vaults,id', + 'author_id' => 'required|integer|exists:users,id', + 'contact_id' => 'required|integer|exists:contacts,id', + 'call_reason_id' => 'nullable|integer|exists:call_reasons,id', + 'called_at' => 'required|date_format:Y-m-d', + 'duration' => 'nullable|integer', + 'description' => 'nullable|string|max:65535', + 'type' => 'required|string', + 'answered' => 'nullable|boolean', + 'who_initiated' => 'required|string', + ]; + } + + /** + * Get the permissions that apply to the user calling the service. + * + * @return array + */ + public function permissions(): array + { + return [ + 'author_must_belong_to_account', + 'vault_must_belong_to_account', + 'author_must_be_vault_editor', + 'contact_must_belong_to_vault', + ]; + } + + /** + * Create a call. + * + * @param array $data + * @return Call + */ + public function execute(array $data): Call + { + $this->data = $data; + $this->validate(); + + $this->createCall(); + $this->updateLastEditedDate(); + + return $this->call; + } + + private function validate(): void + { + $this->validateRules($this->data); + } + + private function createCall(): void + { + $this->call = Call::create([ + 'contact_id' => $this->data['contact_id'], + 'author_id' => $this->author->id, + 'author_name' => $this->author->name, + 'called_at' => $this->data['called_at'], + 'duration' => $this->valueOrNull($this->data, 'duration'), + 'call_reason_id' => $this->valueOrNull($this->data, 'call_reason_id'), + 'description' => $this->valueOrNull($this->data, 'description'), + 'type' => $this->data['type'], + 'answered' => $this->valueOrTrue($this->data, 'answered'), + 'who_initiated' => $this->data['who_initiated'], + ]); + } + + private function updateLastEditedDate(): void + { + $this->contact->last_updated_at = Carbon::now(); + $this->contact->save(); + } +} diff --git a/domains/Contact/ManageCalls/Services/DestroyCall.php b/domains/Contact/ManageCalls/Services/DestroyCall.php new file mode 100644 index 000000000..0e9a5f562 --- /dev/null +++ b/domains/Contact/ManageCalls/Services/DestroyCall.php @@ -0,0 +1,62 @@ + 'required|integer|exists:accounts,id', + 'vault_id' => 'required|integer|exists:vaults,id', + 'author_id' => 'required|integer|exists:users,id', + 'contact_id' => 'required|integer|exists:contacts,id', + 'call_id' => 'required|integer|exists:calls,id', + ]; + } + + /** + * Get the permissions that apply to the user calling the service. + * + * @return array + */ + public function permissions(): array + { + return [ + 'author_must_belong_to_account', + 'vault_must_belong_to_account', + 'contact_must_belong_to_vault', + 'author_must_be_vault_editor', + ]; + } + + /** + * Destroy a call. + * + * @param array $data + */ + public function execute(array $data): void + { + $this->validateRules($data); + + $this->call = Call::where('contact_id', $data['contact_id']) + ->findOrFail($data['call_id']); + + $this->call->delete(); + + $this->contact->last_updated_at = Carbon::now(); + $this->contact->save(); + } +} diff --git a/domains/Contact/ManageCalls/Services/UpdateCall.php b/domains/Contact/ManageCalls/Services/UpdateCall.php new file mode 100644 index 000000000..a192f9499 --- /dev/null +++ b/domains/Contact/ManageCalls/Services/UpdateCall.php @@ -0,0 +1,83 @@ + 'required|integer|exists:accounts,id', + 'vault_id' => 'required|integer|exists:vaults,id', + 'author_id' => 'required|integer|exists:users,id', + 'contact_id' => 'required|integer|exists:contacts,id', + 'call_id' => 'required|integer|exists:calls,id', + 'call_reason_id' => 'nullable|integer|exists:call_reasons,id', + 'called_at' => 'required|date_format:Y-m-d', + 'duration' => 'nullable|integer', + 'type' => 'required|string', + 'answered' => 'nullable|boolean', + 'who_initiated' => 'required|string', + ]; + } + + /** + * Get the permissions that apply to the user calling the service. + * + * @return array + */ + public function permissions(): array + { + return [ + 'author_must_belong_to_account', + 'vault_must_belong_to_account', + 'contact_must_belong_to_vault', + 'author_must_be_vault_editor', + ]; + } + + /** + * Update a call. + * + * @param array $data + * @return Call + */ + public function execute(array $data): Call + { + $this->data = $data; + $this->validate(); + + $call = Call::where('contact_id', $data['contact_id']) + ->findOrFail($data['call_id']); + + $call->called_at = $data['called_at']; + $call->duration = $this->valueOrNull($data, 'duration'); + $call->call_reason_id = $this->valueOrNull($data, 'call_reason_id'); + $call->type = $data['type']; + $call->answered = $this->valueOrTrue($data, 'answered'); + $call->who_initiated = $data['who_initiated']; + $call->save(); + + $this->contact->last_updated_at = Carbon::now(); + $this->contact->save(); + + return $call; + } + + private function validate(): void + { + $this->validateRules($this->data); + } +} diff --git a/domains/Contact/ManageCalls/Web/Controllers/ContactModuleCallController.php b/domains/Contact/ManageCalls/Web/Controllers/ContactModuleCallController.php new file mode 100644 index 000000000..2cef4cdab --- /dev/null +++ b/domains/Contact/ManageCalls/Web/Controllers/ContactModuleCallController.php @@ -0,0 +1,133 @@ +input('called_at')); + + switch ($request->input('who_initiated')) { + case 'contact_not_answered': + $whoInitiated = 'contact'; + $answered = false; + break; + + case 'me_not_answered': + $whoInitiated = 'me'; + $answered = false; + break; + + case 'contact': + $whoInitiated = 'contact'; + $answered = true; + break; + + case 'me': + $whoInitiated = 'me'; + $answered = true; + break; + } + + $data = [ + 'account_id' => Auth::user()->account_id, + 'author_id' => Auth::user()->id, + 'vault_id' => $vaultId, + 'contact_id' => $contactId, + 'call_reason_id' => $request->input('call_reason_id') == 0 ? null : $request->input('call_reason_id'), + 'called_at' => $carbonDate->format('Y-m-d'), + 'duration' => $request->input('duration'), + 'description' => $request->input('description'), + 'type' => $request->input('type'), + 'answered' => $answered, + 'who_initiated' => $whoInitiated, + ]; + + $call = (new CreateCall)->execute($data); + $contact = Contact::find($contactId); + + return response()->json([ + 'data' => ModuleCallsViewHelper::dto($contact, $call, Auth::user()), + ], 201); + } + + public function update(Request $request, int $vaultId, int $contactId, int $callId) + { + $carbonDate = Carbon::parse($request->input('called_at')); + + switch ($request->input('who_initiated')) { + case 'contact_not_answered': + $whoInitiated = 'contact'; + $answered = false; + break; + + case 'me_not_answered': + $whoInitiated = 'me'; + $answered = false; + break; + + case 'contact': + $whoInitiated = 'contact'; + $answered = true; + break; + + case 'me': + $whoInitiated = 'me'; + $answered = true; + break; + + default: + break; + } + + $data = [ + 'account_id' => Auth::user()->account_id, + 'author_id' => Auth::user()->id, + 'vault_id' => $vaultId, + 'contact_id' => $contactId, + 'call_id' => $callId, + 'call_reason_id' => $request->input('call_reason_id') == 0 ? null : $request->input('call_reason_id'), + 'called_at' => $carbonDate->format('Y-m-d'), + 'duration' => $request->input('duration'), + 'description' => $request->input('description'), + 'type' => $request->input('type'), + 'answered' => $answered, + 'who_initiated' => $whoInitiated, + ]; + + $call = (new UpdateCall())->execute($data); + $contact = Contact::find($contactId); + + return response()->json([ + 'data' => ModuleCallsViewHelper::dto($contact, $call, Auth::user()), + ], 200); + } + + public function destroy(Request $request, int $vaultId, int $contactId, int $callId) + { + $data = [ + 'account_id' => Auth::user()->account_id, + 'author_id' => Auth::user()->id, + 'vault_id' => $vaultId, + 'contact_id' => $contactId, + 'call_id' => $callId, + ]; + + (new DestroyCall())->execute($data); + + return response()->json([ + 'data' => true, + ], 200); + } +} diff --git a/domains/Contact/ManageCalls/Web/ViewHelpers/ModuleCallsViewHelper.php b/domains/Contact/ManageCalls/Web/ViewHelpers/ModuleCallsViewHelper.php new file mode 100644 index 000000000..fb19a8eb6 --- /dev/null +++ b/domains/Contact/ManageCalls/Web/ViewHelpers/ModuleCallsViewHelper.php @@ -0,0 +1,85 @@ +calls() + ->orderBy('called_at', 'desc') + ->get() + ->map(function ($call) use ($contact, $user) { + return self::dto($contact, $call, $user); + }); + + $callReasonTypes = $contact->vault->account->callReasonTypes() + ->with('callReasons') + ->get(); + + $callReasonTypesCollection = collect(); + foreach ($callReasonTypes as $callReasonType) { + $callReasons = $callReasonType->callReasons; + + $callReasonsCollection = collect(); + foreach ($callReasons as $callReason) { + $callReasonsCollection->push([ + 'id' => $callReason->id, + 'label' => $callReason->label, + ]); + } + + $callReasonTypesCollection->push([ + 'id' => $callReasonType->id, + 'label' => $callReasonType->label, + 'reasons' => $callReasonsCollection, + ]); + } + + return [ + 'contact_name' => $contact->getName($user), + 'calls' => $callsCollection, + 'call_reason_types' => $callReasonTypesCollection, + 'url' => [ + 'store' => route('contact.call.store', [ + 'vault' => $contact->vault_id, + 'contact' => $contact->id, + ]), + ], + ]; + } + + public static function dto(Contact $contact, Call $call, User $user): array + { + return [ + 'id' => $call->id, + 'called_at' => DateHelper::format($call->called_at, $user), + 'duration' => $call->duration, + 'description' => $call->description, + 'who_initiated' => $call->who_initiated, + 'type' => $call->type, + 'answered' => $call->answered, + 'reason' => $call->callReason ? [ + 'id' => $call->callReason->id, + 'label' => $call->callReason->label, + ] : null, + 'url' => [ + 'update' => route('contact.call.update', [ + 'vault' => $contact->vault_id, + 'contact' => $contact->id, + 'call' => $call->id, + ]), + 'destroy' => route('contact.call.destroy', [ + 'vault' => $contact->vault_id, + 'contact' => $contact->id, + 'call' => $call->id, + ]), + ], + ]; + } +} diff --git a/domains/Contact/ManageContact/Web/ViewHelpers/ContactShowViewHelper.php b/domains/Contact/ManageContact/Web/ViewHelpers/ContactShowViewHelper.php index e1f59f719..98fe7d90b 100644 --- a/domains/Contact/ManageContact/Web/ViewHelpers/ContactShowViewHelper.php +++ b/domains/Contact/ManageContact/Web/ViewHelpers/ContactShowViewHelper.php @@ -3,6 +3,7 @@ namespace App\Contact\ManageContact\Web\ViewHelpers; use App\Contact\ManageAvatar\Web\ViewHelpers\ModuleAvatarViewHelper; +use App\Contact\ManageCalls\Web\ViewHelpers\ModuleCallsViewHelper; use App\Contact\ManageContactFeed\Web\ViewHelpers\ModuleFeedViewHelper; use App\Contact\ManageContactImportantDates\Web\ViewHelpers\ModuleImportantDatesViewHelper; use App\Contact\ManageContactName\Web\ViewHelpers\ModuleContactNameViewHelper; @@ -173,6 +174,10 @@ class ContactShowViewHelper $data = ModuleContactTasksViewHelper::data($contact, $user); } + if ($module->type == Module::TYPE_CALLS) { + $data = ModuleCallsViewHelper::data($contact, $user); + } + $modulesCollection->push([ 'id' => $module->id, 'type' => $module->type, diff --git a/resources/js/Pages/Vault/Contact/Show.vue b/resources/js/Pages/Vault/Contact/Show.vue index f2a086a9b..d255715b7 100644 --- a/resources/js/Pages/Vault/Contact/Show.vue +++ b/resources/js/Pages/Vault/Contact/Show.vue @@ -97,6 +97,8 @@ + + @@ -120,6 +122,7 @@ import Loans from '@/Shared/Modules/Loans'; import JobInformation from '@/Shared/Modules/JobInformation'; import Relationships from '@/Shared/Modules/Relationships'; import Tasks from '@/Shared/Modules/Tasks'; +import Calls from '@/Shared/Modules/Calls'; export default { components: { @@ -136,6 +139,7 @@ export default { JobInformation, Relationships, Tasks, + Calls, }, props: { @@ -163,6 +167,7 @@ export default { jobInformation: [], relationships: [], tasks: [], + calls: [], }; }, @@ -228,6 +233,10 @@ export default { if (this.data.modules.findIndex((x) => x.type == 'tasks') > -1) { this.tasks = this.data.modules[this.data.modules.findIndex((x) => x.type == 'tasks')].data; } + + if (this.data.modules.findIndex((x) => x.type == 'calls') > -1) { + this.calls = this.data.modules[this.data.modules.findIndex((x) => x.type == 'calls')].data; + } } }, diff --git a/resources/js/Shared/Modules/Calls.vue b/resources/js/Shared/Modules/Calls.vue new file mode 100644 index 000000000..1dc830fe2 --- /dev/null +++ b/resources/js/Shared/Modules/Calls.vue @@ -0,0 +1,615 @@ + + + + + diff --git a/resources/lang/en/app.php b/resources/lang/en/app.php index 6ad65512a..d4bfa8ae1 100644 --- a/resources/lang/en/app.php +++ b/resources/lang/en/app.php @@ -91,6 +91,7 @@ return [ 'module_loans' => 'Loans', 'module_companies' => 'Job information', 'module_tasks' => 'Tasks', + 'module_calls' => 'Calls', 'module_option_default_number_of_items_to_display' => 'Default number of items to display', diff --git a/routes/web.php b/routes/web.php index 51d8dbbbe..902d106a8 100644 --- a/routes/web.php +++ b/routes/web.php @@ -1,5 +1,6 @@ group(function () { Route::put('tasks/{task}', [ContactModuleTaskController::class, 'update'])->name('contact.task.update'); Route::put('tasks/{task}/toggle', [ContactModuleTaskController::class, 'toggle'])->name('contact.task.toggle'); Route::delete('tasks/{task}', [ContactModuleTaskController::class, 'destroy'])->name('contact.task.destroy'); + + // calls + Route::post('calls', [ContactModuleCallController::class, 'store'])->name('contact.call.store'); + Route::put('calls/{call}', [ContactModuleCallController::class, 'update'])->name('contact.call.update'); + Route::delete('calls/{call}', [ContactModuleCallController::class, 'destroy'])->name('contact.call.destroy'); }); }); diff --git a/tests/Unit/Domains/Contact/ManageCalls/Services/CreateCallTest.php b/tests/Unit/Domains/Contact/ManageCalls/Services/CreateCallTest.php new file mode 100644 index 000000000..be1a717ae --- /dev/null +++ b/tests/Unit/Domains/Contact/ManageCalls/Services/CreateCallTest.php @@ -0,0 +1,138 @@ +createUser(); + $vault = $this->createVault($regis->account); + $vault = $this->setPermissionInVault($regis, Vault::PERMISSION_EDIT, $vault); + $contact = Contact::factory()->create(['vault_id' => $vault->id]); + $type = CallReasonType::factory()->create([ + 'account_id' => $regis->account->id, + ]); + $callReason = CallReason::factory()->create([ + 'call_reason_type_id' => $type->id, + ]); + + $this->executeService($regis, $regis->account, $vault, $contact, $callReason); + } + + /** @test */ + public function it_fails_if_wrong_parameters_are_given(): void + { + $request = [ + 'title' => 'Ross', + ]; + + $this->expectException(ValidationException::class); + (new CreateCall)->execute($request); + } + + /** @test */ + public function it_fails_if_user_doesnt_belong_to_account(): void + { + $this->expectException(ModelNotFoundException::class); + + $regis = $this->createUser(); + $account = Account::factory()->create(); + $vault = $this->createVault($regis->account); + $vault = $this->setPermissionInVault($regis, Vault::PERMISSION_EDIT, $vault); + $contact = Contact::factory()->create(['vault_id' => $vault->id]); + $type = CallReasonType::factory()->create([ + 'account_id' => $regis->account_id, + ]); + $callReason = CallReason::factory()->create([ + 'call_reason_type_id' => $type->id, + ]); + + $this->executeService($regis, $account, $vault, $contact, $callReason); + } + + /** @test */ + public function it_fails_if_contact_doesnt_belong_to_vault(): void + { + $this->expectException(ModelNotFoundException::class); + + $regis = $this->createUser(); + $vault = $this->createVault($regis->account); + $vault = $this->setPermissionInVault($regis, Vault::PERMISSION_EDIT, $vault); + $contact = Contact::factory()->create(); + $type = CallReasonType::factory()->create([ + 'account_id' => $regis->account_id, + ]); + $callReason = CallReason::factory()->create([ + 'call_reason_type_id' => $type->id, + ]); + + $this->executeService($regis, $regis->account, $vault, $contact, $callReason); + } + + /** @test */ + public function it_fails_if_user_doesnt_have_right_permission_in_vault(): void + { + $this->expectException(NotEnoughPermissionException::class); + + $regis = $this->createUser(); + $vault = $this->createVault($regis->account); + $vault = $this->setPermissionInVault($regis, Vault::PERMISSION_VIEW, $vault); + $contact = Contact::factory()->create(['vault_id' => $vault->id]); + $type = CallReasonType::factory()->create([ + 'account_id' => $regis->account_id, + ]); + $callReason = CallReason::factory()->create([ + 'call_reason_type_id' => $type->id, + ]); + + $this->executeService($regis, $regis->account, $vault, $contact, $callReason); + } + + private function executeService(User $author, Account $account, Vault $vault, Contact $contact, CallReason $reason): void + { + Queue::fake(); + + $request = [ + 'account_id' => $account->id, + 'vault_id' => $vault->id, + 'author_id' => $author->id, + 'contact_id' => $contact->id, + 'call_reason_id' => $reason->id, + 'called_at' => '1999-01-01', + 'duration' => 100, + 'type' => 'audio', + 'answered' => true, + 'who_initiated' => 'contact', + ]; + + $call = (new CreateCall)->execute($request); + + $this->assertDatabaseHas('calls', [ + 'id' => $call->id, + 'contact_id' => $contact->id, + 'called_at' => '1999-01-01 00:00:00', + 'duration' => 100, + 'type' => 'audio', + 'answered' => true, + 'who_initiated' => 'contact', + ]); + } +} diff --git a/tests/Unit/Domains/Contact/ManageCalls/Services/DestroyCallTest.php b/tests/Unit/Domains/Contact/ManageCalls/Services/DestroyCallTest.php new file mode 100644 index 000000000..e60a146eb --- /dev/null +++ b/tests/Unit/Domains/Contact/ManageCalls/Services/DestroyCallTest.php @@ -0,0 +1,128 @@ +createUser(); + $vault = $this->createVault($regis->account); + $vault = $this->setPermissionInVault($regis, Vault::PERMISSION_EDIT, $vault); + $contact = Contact::factory()->create(['vault_id' => $vault->id]); + $call = Call::factory()->create([ + 'contact_id' => $contact->id, + ]); + + $this->executeService($regis, $regis->account, $vault, $contact, $call); + } + + /** @test */ + public function it_fails_if_wrong_parameters_are_given(): void + { + $request = [ + 'title' => 'Ross', + ]; + + $this->expectException(ValidationException::class); + (new DestroyCall)->execute($request); + } + + /** @test */ + public function it_fails_if_user_doesnt_belong_to_account(): void + { + $this->expectException(ModelNotFoundException::class); + + $regis = $this->createUser(); + $account = Account::factory()->create(); + $vault = $this->createVault($regis->account); + $vault = $this->setPermissionInVault($regis, Vault::PERMISSION_EDIT, $vault); + $contact = Contact::factory()->create(['vault_id' => $vault->id]); + $call = Call::factory()->create([ + 'contact_id' => $contact->id, + ]); + + $this->executeService($regis, $account, $vault, $contact, $call); + } + + /** @test */ + public function it_fails_if_contact_doesnt_belong_to_vault(): void + { + $this->expectException(ModelNotFoundException::class); + + $regis = $this->createUser(); + $vault = $this->createVault($regis->account); + $vault = $this->setPermissionInVault($regis, Vault::PERMISSION_EDIT, $vault); + $contact = Contact::factory()->create(); + $call = Call::factory()->create([ + 'contact_id' => $contact->id, + ]); + + $this->executeService($regis, $regis->account, $vault, $contact, $call); + } + + /** @test */ + public function it_fails_if_user_doesnt_have_right_permission_in_vault(): void + { + $this->expectException(NotEnoughPermissionException::class); + + $regis = $this->createUser(); + $vault = $this->createVault($regis->account); + $vault = $this->setPermissionInVault($regis, Vault::PERMISSION_VIEW, $vault); + $contact = Contact::factory()->create(['vault_id' => $vault->id]); + $call = Call::factory()->create([ + 'contact_id' => $contact->id, + ]); + + $this->executeService($regis, $regis->account, $vault, $contact, $call); + } + + /** @test */ + public function it_fails_if_reminder_does_not_exist(): void + { + $this->expectException(ModelNotFoundException::class); + + $regis = $this->createUser(); + $vault = $this->createVault($regis->account); + $vault = $this->setPermissionInVault($regis, Vault::PERMISSION_EDIT, $vault); + $contact = Contact::factory()->create(['vault_id' => $vault->id]); + $call = Call::factory()->create(); + + $this->executeService($regis, $regis->account, $vault, $contact, $call); + } + + private function executeService(User $author, Account $account, Vault $vault, Contact $contact, Call $call): void + { + Queue::fake(); + + $request = [ + 'account_id' => $account->id, + 'vault_id' => $vault->id, + 'author_id' => $author->id, + 'contact_id' => $contact->id, + 'call_id' => $call->id, + ]; + + (new DestroyCall)->execute($request); + + $this->assertDatabaseMissing('calls', [ + 'id' => $call->id, + ]); + } +} diff --git a/tests/Unit/Domains/Contact/ManageCalls/Services/UpdateCallTest.php b/tests/Unit/Domains/Contact/ManageCalls/Services/UpdateCallTest.php new file mode 100644 index 000000000..fd5673791 --- /dev/null +++ b/tests/Unit/Domains/Contact/ManageCalls/Services/UpdateCallTest.php @@ -0,0 +1,176 @@ +createUser(); + $vault = $this->createVault($regis->account); + $vault = $this->setPermissionInVault($regis, Vault::PERMISSION_EDIT, $vault); + $contact = Contact::factory()->create(['vault_id' => $vault->id]); + $call = Call::factory()->create([ + 'contact_id' => $contact->id, + ]); + $type = CallReasonType::factory()->create([ + 'account_id' => $regis->account_id, + ]); + $callReason = CallReason::factory()->create([ + 'call_reason_type_id' => $type->id, + ]); + + $this->executeService($regis, $regis->account, $vault, $contact, $call, $callReason); + } + + /** @test */ + public function it_fails_if_wrong_parameters_are_given(): void + { + $request = [ + 'title' => 'Ross', + ]; + + $this->expectException(ValidationException::class); + (new UpdateCall)->execute($request); + } + + /** @test */ + public function it_fails_if_user_doesnt_belong_to_account(): void + { + $this->expectException(ModelNotFoundException::class); + + $regis = $this->createUser(); + $account = Account::factory()->create(); + $vault = $this->createVault($regis->account); + $vault = $this->setPermissionInVault($regis, Vault::PERMISSION_EDIT, $vault); + $contact = Contact::factory()->create(['vault_id' => $vault->id]); + $call = Call::factory()->create([ + 'contact_id' => $contact->id, + ]); + $type = CallReasonType::factory()->create([ + 'account_id' => $regis->account_id, + ]); + $callReason = CallReason::factory()->create(['call_reason_type_id' => $type->id]); + + $this->executeService($regis, $account, $vault, $contact, $call, $callReason); + } + + /** @test */ + public function it_fails_if_contact_doesnt_belong_to_vault(): void + { + $this->expectException(ModelNotFoundException::class); + + $regis = $this->createUser(); + $vault = $this->createVault($regis->account); + $vault = $this->setPermissionInVault($regis, Vault::PERMISSION_EDIT, $vault); + $contact = Contact::factory()->create(); + $call = Call::factory()->create([ + 'contact_id' => $contact->id, + ]); + $type = CallReasonType::factory()->create([ + 'account_id' => $regis->account_id, + ]); + $callReason = CallReason::factory()->create(['call_reason_type_id' => $type->id]); + + $this->executeService($regis, $regis->account, $vault, $contact, $call, $callReason); + } + + /** @test */ + public function it_fails_if_user_doesnt_have_right_permission_in_vault(): void + { + $this->expectException(NotEnoughPermissionException::class); + + $regis = $this->createUser(); + $vault = $this->createVault($regis->account); + $vault = $this->setPermissionInVault($regis, Vault::PERMISSION_VIEW, $vault); + $contact = Contact::factory()->create(['vault_id' => $vault->id]); + $call = Call::factory()->create([ + 'contact_id' => $contact->id, + ]); + $type = CallReasonType::factory()->create([ + 'account_id' => $regis->account_id, + ]); + $callReason = CallReason::factory()->create(['call_reason_type_id' => $type->id]); + + $this->executeService($regis, $regis->account, $vault, $contact, $call, $callReason); + } + + /** @test */ + public function it_fails_if_reminder_is_not_in_the_contact(): void + { + $this->expectException(ModelNotFoundException::class); + + $regis = $this->createUser(); + $vault = $this->createVault($regis->account); + $vault = $this->setPermissionInVault($regis, Vault::PERMISSION_EDIT, $vault); + $contact = Contact::factory()->create(['vault_id' => $vault->id]); + $call = Call::factory()->create(); + $type = CallReasonType::factory()->create([ + 'account_id' => $regis->account_id, + ]); + $callReason = CallReason::factory()->create(['call_reason_type_id' => $type->id]); + + $this->executeService($regis, $regis->account, $vault, $contact, $call, $callReason); + } + + /** @test */ + public function it_fails_if_call_reason_doesnt_belong_to_account(): void + { + $this->expectException(ModelNotFoundException::class); + + $regis = $this->createUser(); + $vault = $this->createVault($regis->account); + $vault = $this->setPermissionInVault($regis, Vault::PERMISSION_EDIT, $vault); + $contact = Contact::factory()->create(['vault_id' => $vault->id]); + $call = Call::factory()->create(); + $callReason = CallReason::factory()->create(); + + $this->executeService($regis, $regis->account, $vault, $contact, $call, $callReason); + } + + private function executeService(User $author, Account $account, Vault $vault, Contact $contact, Call $call, CallReason $reason): void + { + $request = [ + 'account_id' => $account->id, + 'vault_id' => $vault->id, + 'author_id' => $author->id, + 'contact_id' => $contact->id, + 'call_id' => $call->id, + 'call_reason_id' => $reason->id, + 'called_at' => '1999-01-01', + 'duration' => 100, + 'type' => 'audio', + 'answered' => true, + 'who_initiated' => 'contact', + ]; + + $call = (new UpdateCall)->execute($request); + + $this->assertDatabaseHas('calls', [ + 'id' => $call->id, + 'contact_id' => $contact->id, + 'called_at' => '1999-01-01 00:00:00', + 'duration' => 100, + 'type' => 'audio', + 'answered' => true, + 'who_initiated' => 'contact', + ]); + } +} diff --git a/tests/Unit/Domains/Contact/ManageCalls/Web/Services/ModuleCallsViewHelperTest.php b/tests/Unit/Domains/Contact/ManageCalls/Web/Services/ModuleCallsViewHelperTest.php new file mode 100644 index 000000000..9fbb2b086 --- /dev/null +++ b/tests/Unit/Domains/Contact/ManageCalls/Web/Services/ModuleCallsViewHelperTest.php @@ -0,0 +1,88 @@ +create(); + $user = User::factory()->create(); + + $call = Call::factory()->create([ + 'contact_id' => $contact->id, + ]); + + $array = ModuleCallsViewHelper::data($contact, $user); + + $this->assertEquals( + 4, + count($array) + ); + + $this->assertArrayHasKey('contact_name', $array); + $this->assertArrayHasKey('calls', $array); + $this->assertArrayHasKey('call_reason_types', $array); + $this->assertArrayHasKey('url', $array); + + $this->assertEquals( + $contact->getName($user), + $array['contact_name'] + ); + + $this->assertEquals( + [ + 'store' => env('APP_URL').'/vaults/'.$contact->vault->id.'/contacts/'.$contact->id.'/calls', + ], + $array['url'] + ); + } + + /** @test */ + public function it_gets_the_data_transfer_object(): void + { + Carbon::setTestNow(Carbon::create(2018, 1, 1)); + $contact = Contact::factory()->create(); + $user = User::factory()->create(); + $call = Call::factory()->create([ + 'contact_id' => $contact->id, + 'called_at' => Carbon::now(), + 'description' => null, + ]); + + $collection = ModuleCallsViewHelper::dto($contact, $call, $user); + + $this->assertEquals( + [ + 'id' => $call->id, + 'called_at' => 'Jan 01, 2018', + 'duration' => $call->duration, + 'description' => null, + 'who_initiated' => 'me', + 'type' => 'audio', + 'answered' => true, + 'reason' => [ + 'id' => $call->callReason->id, + 'label' => $call->callReason->label, + ], + 'url' => [ + 'update' => env('APP_URL').'/vaults/'.$contact->vault->id.'/contacts/'.$contact->id.'/calls/'.$call->id, + 'destroy' => env('APP_URL').'/vaults/'.$contact->vault->id.'/contacts/'.$contact->id.'/calls/'.$call->id, + ], + ], + $collection + ); + } +} diff --git a/tests/Unit/Domains/Settings/ManageCallReasons/Services/CreateCallReasonTest.php b/tests/Unit/Domains/Settings/ManageCallReasons/Services/CreateCallReasonTest.php index 920c8329e..e92c97c6b 100644 --- a/tests/Unit/Domains/Settings/ManageCallReasons/Services/CreateCallReasonTest.php +++ b/tests/Unit/Domains/Settings/ManageCallReasons/Services/CreateCallReasonTest.php @@ -76,7 +76,7 @@ class CreateCallReasonTest extends TestCase $this->assertDatabaseHas('call_reasons', [ 'id' => $reason->id, - 'call_reason_type_id' => $reason->id, + 'call_reason_type_id' => $type->id, 'label' => 'type name', ]); diff --git a/tests/Unit/Jobs/SetupAccountTest.php b/tests/Unit/Jobs/SetupAccountTest.php index f0fccd91f..eef7cbb57 100644 --- a/tests/Unit/Jobs/SetupAccountTest.php +++ b/tests/Unit/Jobs/SetupAccountTest.php @@ -88,6 +88,10 @@ class SetupAccountTest extends TestCase 'account_id' => $regis->account_id, 'name' => trans('app.module_tasks'), ]); + $this->assertDatabaseHas('modules', [ + 'account_id' => $regis->account_id, + 'name' => trans('app.module_calls'), + ]); $this->assertDatabaseHas('relationship_group_types', [ 'name' => trans('account.relationship_type_love'), diff --git a/tests/Unit/Models/CallTest.php b/tests/Unit/Models/CallTest.php new file mode 100644 index 000000000..5b07b9dc8 --- /dev/null +++ b/tests/Unit/Models/CallTest.php @@ -0,0 +1,36 @@ +create(); + + $this->assertTrue($call->contact()->exists()); + } + + /** @test */ + public function it_has_one_author() + { + $call = Call::factory()->create(); + + $this->assertTrue($call->author()->exists()); + } + + /** @test */ + public function it_has_one_call_reason() + { + $call = Call::factory()->create(); + + $this->assertTrue($call->callReason()->exists()); + } +} diff --git a/tests/Unit/Models/ContactTest.php b/tests/Unit/Models/ContactTest.php index 60d9ce939..0d6bedf0b 100644 --- a/tests/Unit/Models/ContactTest.php +++ b/tests/Unit/Models/ContactTest.php @@ -4,6 +4,7 @@ namespace Tests\Unit\Models; use App\Models\Address; use App\Models\Avatar; +use App\Models\Call; use App\Models\Company; use App\Models\Contact; use App\Models\ContactImportantDate; @@ -211,6 +212,17 @@ class ContactTest extends TestCase $this->assertTrue($ross->tasks()->exists()); } + /** @test */ + public function it_has_many_calls(): void + { + $ross = Contact::factory()->create(); + Call::factory()->count(2)->create([ + 'contact_id' => $ross->id, + ]); + + $this->assertTrue($ross->calls()->exists()); + } + /** @test */ public function it_gets_the_name(): void {