feat: enable activities edit (#3422)

This commit is contained in:
Alexis Saettler
2020-01-08 08:24:51 +01:00
committed by GitHub
parent 9c39bf49cb
commit 3e007af34e
40 changed files with 1382 additions and 425 deletions
+1
View File
@@ -2,6 +2,7 @@
### New features:
* Add ability to edit activities
* Associate a photo to a gift
### Enhancements:
+1 -1
View File
@@ -204,7 +204,7 @@ class ImportCSV extends Command
app(CreateReminder::class)->execute([
'account_id' => $contact->account_id,
'contact_id' => $contact->id,
'initial_date' => $specialDate->date->toDateString(),
'initial_date' => DateHelper::getDate($specialDate),
'frequency_type' => 'year',
'frequency_number' => 1,
'title' => trans(
+21
View File
@@ -110,6 +110,27 @@ class DateHelper
return $date->format(config('api.timestamp_format'));
}
/**
* Return date timestamp format.
*
* @param Carbon|\App\Models\Instance\SpecialDate|string|null $date
* @return string|null
*/
public static function getDate($date)
{
if (is_null($date)) {
return;
}
if ($date instanceof \App\Models\Instance\SpecialDate) {
$date = $date->date;
}
if (! $date instanceof Carbon) {
$date = Carbon::create($date);
}
return $date->format(config('api.date_timestamp_format'));
}
/**
* Get timezone of the current user, or null.
*
@@ -0,0 +1,164 @@
<?php
namespace App\Http\Controllers\Api;
use Illuminate\Http\Request;
use App\Models\Contact\Contact;
use App\Models\Account\Activity;
use Illuminate\Database\QueryException;
use Illuminate\Validation\ValidationException;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use App\Services\Account\Activity\Activity\CreateActivity;
use App\Services\Account\Activity\Activity\UpdateActivity;
use App\Services\Account\Activity\Activity\DestroyActivity;
use App\Http\Resources\Activity\Activity as ActivityResource;
class ApiActivitiesController extends ApiController
{
/**
* Get the list of activities.
*
* @return \Illuminate\Http\Resources\Json\AnonymousResourceCollection|\Illuminate\Http\JsonResponse
*/
public function index(Request $request)
{
try {
$activities = auth()->user()->account->activities()
->orderBy($this->sort, $this->sortDirection)
->paginate($this->getLimitPerPage());
} catch (QueryException $e) {
return $this->respondInvalidQuery();
}
return ActivityResource::collection($activities)->additional(['meta' => [
'statistics' => auth()->user()->account->getYearlyActivitiesStatistics(),
]]);
}
/**
* Get the detail of a given activity.
*
* @param Request $request
*
* @return ActivityResource|\Illuminate\Http\JsonResponse
*/
public function show(Request $request, $activityId)
{
try {
$activity = Activity::where('account_id', auth()->user()->account_id)
->findOrFail($activityId);
} catch (ModelNotFoundException $e) {
return $this->respondNotFound();
}
return new ActivityResource($activity);
}
/**
* Store the activity.
*
* @param Request $request
*
* @return ActivityResource|\Illuminate\Http\JsonResponse
*/
public function store(Request $request)
{
try {
$activity = app(CreateActivity::class)->execute(
$request->except(['account_id'])
+
[
'account_id' => auth()->user()->account->id,
]
);
} catch (ModelNotFoundException $e) {
return $this->respondNotFound();
} catch (ValidationException $e) {
return $this->respondValidatorFailed($e->validator);
} catch (QueryException $e) {
return $this->respondInvalidQuery();
}
return new ActivityResource($activity);
}
/**
* Update the activity.
*
* @param Request $request
* @param int $activityId
*
* @return ActivityResource|\Illuminate\Http\JsonResponse
*/
public function update(Request $request, $activityId)
{
try {
$activity = app(UpdateActivity::class)->execute(
$request->except(['account_id', 'activity_id'])
+
[
'account_id' => auth()->user()->account->id,
'activity_id' => $activityId,
]
);
} catch (ModelNotFoundException $e) {
return $this->respondNotFound();
} catch (ValidationException $e) {
return $this->respondValidatorFailed($e->validator);
} catch (QueryException $e) {
return $this->respondInvalidQuery();
}
return new ActivityResource($activity);
}
/**
* Delete an activity.
*
* @param Request $request
*
* @return \Illuminate\Http\JsonResponse
*/
public function destroy(Request $request, $activityId)
{
try {
app(DestroyActivity::class)->execute([
'account_id' => auth()->user()->account_id,
'activity_id' => $activityId,
]);
} catch (ModelNotFoundException $e) {
return $this->respondNotFound();
} catch (ValidationException $e) {
return $this->respondValidatorFailed($e->validator);
}
return $this->respondObjectDeleted($activityId);
}
/**
* Get the list of activities for the given contact.
*
* @return \Illuminate\Http\Resources\Json\AnonymousResourceCollection|\Illuminate\Http\JsonResponse
*/
public function activities(Request $request, $contactId)
{
try {
$contact = Contact::where('account_id', auth()->user()->account_id)
->findOrFail($contactId);
} catch (ModelNotFoundException $e) {
return $this->respondNotFound();
}
try {
$activities = $contact->activities()
->orderBy($this->sort, $this->sortDirection)
->paginate($this->getLimitPerPage());
} catch (QueryException $e) {
return $this->respondInvalidQuery();
}
return ActivityResource::collection($activities)->additional(['meta' => [
'statistics' => auth()->user()->account->getYearlyActivitiesStatistics(),
]]);
}
}
@@ -10,12 +10,8 @@ use Illuminate\Support\Collection;
use App\Http\Controllers\Controller;
use App\Models\Account\ActivityType;
use App\Traits\JsonRespondController;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use App\Services\Account\Activity\Activity\CreateActivity;
use App\Services\Account\Activity\Activity\DestroyActivity;
use App\Services\Account\Activity\ActivityStatisticService;
use App\Http\Resources\Activity\Activity as ActivityResource;
use App\Services\Account\Activity\Activity\AttachContactToActivity;
class ActivitiesController extends Controller
{
@@ -65,77 +61,16 @@ class ActivitiesController extends Controller
*/
public function contacts(Request $request, Contact $contact)
{
$contactsCollection = collect([]);
$contacts = auth()->user()->account->contacts;
foreach ($contacts as $singleContact) {
if ($contact->id != $singleContact->id) {
$contactsCollection->push([
'id' => $singleContact->id,
'name' => $singleContact->name,
]);
}
}
return $contactsCollection;
}
/**
* Store an activity.
*
* @param Contact $contact
* @return ActivityResource|\Illuminate\Http\JsonResponse
*/
public function store(Request $request, Contact $contact)
{
$activity = app(CreateActivity::class)->execute([
'account_id' => auth()->user()->account->id,
'activity_type_id' => $request->input('activity_type_id'),
'summary' => $request->input('summary'),
'description' => $request->input('description'),
'date' => $request->input('happened_at'),
'emotions' => $request->input('emotions'),
]);
$arrayParticipants = array_map(function ($participant) {
return $participant['id'];
}, $request->input('participants'));
// also push the current contact
array_push($arrayParticipants, $contact->id);
try {
$activity = app(AttachContactToActivity::class)->execute([
'account_id' => auth()->user()->account->id,
'activity_id' => $activity->id,
'contacts' => $arrayParticipants,
]);
} catch (ModelNotFoundException $e) {
return $this->respondNotFound();
}
return new ActivityResource($activity);
}
/**
* Delete the activity.
*
* @param Request $request
* @param Contact $contact
* @param int $activityId
* @return bool|null|\Illuminate\Http\JsonResponse
*/
public function destroy(Request $request, Contact $contact, $activityId)
{
$data = [
'account_id' => auth()->user()->account->id,
'activity_id' => $activityId,
];
try {
app(DestroyActivity::class)->execute($data);
} catch (ModelNotFoundException $e) {
return $this->respondNotFound();
}
return auth()->user()->account->contacts
->filter(function ($c) use ($contact) {
return $contact->id !== $c->id;
})
->map(function ($c) {
return [
'id' => $c->id,
'name' => $c->name,
];
});
}
/**
@@ -216,9 +216,9 @@ class ConversationsController extends Controller
// find out what the date is
$chosenDate = $request->input('conversationDateRadio');
if ($chosenDate == 'today') {
$date = now()->format('Y-m-d');
$date = DateHelper::getDate(now());
} elseif ($chosenDate == 'yesterday') {
$date = now()->subDay()->format('Y-m-d');
$date = DateHelper::getDate(now()->subDay());
} else {
$date = $request->input('conversationDate');
}
+1 -1
View File
@@ -253,7 +253,7 @@ class ContactsController extends Controller
foreach ($reminders as $reminder) {
$next_expected_date = $reminder->calculateNextExpectedDateOnTimezone();
$reminder->next_expected_date_human_readable = DateHelper::getShortDate($next_expected_date);
$reminder->next_expected_date = $next_expected_date->format('Y-m-d');
$reminder->next_expected_date = DateHelper::getDate($next_expected_date);
}
$reminders = $reminders->sortBy('next_expected_date');
+1 -1
View File
@@ -22,7 +22,7 @@ class Activity extends Resource
'object' => 'activity',
'summary' => $this->summary,
'description' => $this->description,
'happened_at' => DateHelper::getTimestamp($this->happened_at),
'happened_at' => DateHelper::getDate($this->happened_at),
'activity_type' => new ActivityTypeResource($this->type),
'attendees' => [
'total' => $this->contacts()->count(),
+1 -1
View File
@@ -26,7 +26,7 @@ class Gift extends Resource
'amount' => $this->value,
'amount_with_currency' => $this->amount,
'status' => $this->status,
'date' => $this->date ? $this->date->format('Y-m-d') : null,
'date' => DateHelper::getDate($this->date),
'recipient' => new ContactShortResource($this->recipient),
'photos' => PhotoResource::collection($this->photos),
'contact' => new ContactShortResource($this->contact),
+2 -2
View File
@@ -25,8 +25,8 @@ class Occupation extends Resource
'salary' => $this->salary,
'salary_unit' => $this->salary_unit,
'currently_works_here' => (bool) $this->currently_works_here,
'start_date' => is_null($this->start_date) ? null : $this->start_date->format('Y-m-d'),
'end_date' => is_null($this->end_date) ? null : $this->end_date->format('Y-m-d'),
'start_date' => DateHelper::getDate($this->start_date),
'end_date' => DateHelper::getDate($this->end_date),
'company' => new CompanyResource($this->company),
'account' => [
'id' => $this->account_id,
+5 -10
View File
@@ -139,17 +139,12 @@ class Activity extends Model implements IsJournalableInterface
*/
public function getContactsForAPI()
{
$attendees = collect([]);
$attendees = $this->contacts->filter(function ($contact) {
// This should not be possible!
return $contact->account_id === $this->account_id;
});
foreach ($this->contacts as $contact) {
if ($contact->account_id !== $this->account_id) {
// This should not be possible!
continue;
}
$attendees->push(new ContactShortResource($contact));
}
return $attendees;
return ContactShortResource::collection($attendees);
}
/**
@@ -22,6 +22,27 @@ class AttachContactToActivity extends BaseService
];
}
/**
* Validate all datas to execute the service.
*
* @param array $data
* @return bool
*/
public function validate(array $data): bool
{
parent::validate($data);
Activity::where('account_id', $data['account_id'])
->findOrFail($data['activity_id']);
foreach ($data['contacts'] as $contactId) {
Contact::where('account_id', $data['account_id'])
->findOrFail($contactId);
}
return true;
}
/**
* Attach contacts to an activity.
*
@@ -32,8 +53,7 @@ class AttachContactToActivity extends BaseService
{
$this->validate($data);
$activity = Activity::where('account_id', $data['account_id'])
->findOrFail($data['activity_id']);
$activity = Activity::find($data['activity_id']);
$this->attach($data, $activity);
@@ -49,18 +69,19 @@ class AttachContactToActivity extends BaseService
*/
private function attach(array $data, Activity $activity)
{
// reset current associations
$activity->contacts()->sync([]);
$attendees = [];
foreach ($data['contacts'] as $contact) {
$attendees[$contact] = ['account_id' => $activity->account_id];
}
foreach ($data['contacts'] as $contactId) {
$contact = Contact::where('account_id', $data['account_id'])
->findOrFail($contactId);
// sync attendees: old contacts will be detached automatically
$changes = $activity->contacts()->sync($attendees);
$activity->contacts()->syncWithoutDetaching([$contactId => [
'account_id' => $activity->account_id,
]]);
$contact->calculateActivitiesStatistics();
foreach ($changes as $change) {
// detached, attached, and updated attendees
foreach ($change as $contactId) {
Contact::find($contactId)->calculateActivitiesStatistics();
}
}
}
}
@@ -3,6 +3,7 @@
namespace App\Services\Account\Activity\Activity;
use App\Services\BaseService;
use App\Models\Contact\Contact;
use App\Models\Account\Activity;
use App\Models\Account\ActivityType;
use App\Models\Journal\JournalEntry;
@@ -19,14 +20,46 @@ class CreateActivity extends BaseService
{
return [
'account_id' => 'required|integer|exists:accounts,id',
'activity_type_id' => 'nullable|integer',
'activity_type_id' => 'nullable|integer|exists:activity_types,id',
'summary' => 'required|string:255',
'description' => 'nullable|string:400000000',
'date' => 'required|date|date_format:Y-m-d',
'description' => 'nullable|string:65535',
'happened_at' => 'required|date|date_format:Y-m-d',
'emotions' => 'nullable|array',
'contacts' => 'required|array',
];
}
/**
* Validate all datas to execute the service.
*
* @param array $data
* @return bool
*/
public function validate(array $data): bool
{
parent::validate($data);
if (count($data['contacts']) > 0) {
foreach ($data['contacts'] as $contactId) {
Contact::where('account_id', $data['account_id'])
->findOrFail($contactId);
}
}
if (! empty($data['activity_type_id']) && $data['activity_type_id'] != '') {
ActivityType::where('account_id', $data['account_id'])
->findOrFail($data['activity_type_id']);
}
if (! empty($data['emotions']) && $data['emotions'] != '') {
foreach ($data['emotions'] as $emotionId) {
Emotion::findOrFail($emotionId);
}
}
return true;
}
/**
* Create an activity.
*
@@ -37,28 +70,41 @@ class CreateActivity extends BaseService
{
$this->validate($data);
if ($data['activity_type_id']) {
ActivityType::where('account_id', $data['account_id'])
->findOrFail($data['activity_type_id']);
}
$activity = $this->create($data);
// Log a journal entry
JournalEntry::add($activity);
// Now we associate the activity with each one of the attendees
app(AttachContactToActivity::class)->execute([
'account_id' => $data['account_id'],
'activity_id' => $activity->id,
'contacts' => $data['contacts'],
]);
return $activity;
}
/**
* Create the activity.
*
* @param array $data
* @return Activity
*/
private function create(array $data): Activity
{
$activity = Activity::create([
'account_id' => $data['account_id'],
'activity_type_id' => $this->nullOrValue($data, 'activity_type_id'),
'summary' => $data['summary'],
'description' => $this->nullOrValue($data, 'description'),
'happened_at' => $data['date'],
'happened_at' => $data['happened_at'],
]);
if (! empty($data['emotions'])) {
if ($data['emotions'] != '') {
$this->addEmotions($data['emotions'], $activity);
}
if (! empty($data['emotions']) && $data['emotions'] != '') {
$this->addEmotions($data['emotions'], $activity);
}
// Log a journal entry
(new JournalEntry)->add($activity);
return $activity;
}
@@ -71,11 +117,11 @@ class CreateActivity extends BaseService
*/
private function addEmotions(array $emotions, Activity $activity)
{
foreach ($emotions as $emotionId) {
$emotion = Emotion::findOrFail($emotionId);
$activity->emotions()->syncWithoutDetaching([$emotion->id => [
'account_id' => $activity->account_id,
]]);
$emotionsSync = [];
foreach ($emotions as $emotion) {
$emotionsSync[$emotion] = ['account_id' => $activity->account_id];
}
$activity->emotions()->sync($emotionsSync);
}
}
@@ -20,6 +20,22 @@ class DestroyActivity extends BaseService
];
}
/**
* Validate all datas to execute the service.
*
* @param array $data
* @return bool
*/
public function validate(array $data): bool
{
parent::validate($data);
Activity::where('account_id', $data['account_id'])
->findOrFail($data['activity_id']);
return true;
}
/**
* Destroy an activity.
*
@@ -30,8 +46,7 @@ class DestroyActivity extends BaseService
{
$this->validate($data);
$activity = Activity::where('account_id', $data['account_id'])
->findOrFail($data['activity_id']);
$activity = Activity::find($data['activity_id']);
$activity->deleteJournalEntry();
@@ -3,8 +3,11 @@
namespace App\Services\Account\Activity\Activity;
use App\Services\BaseService;
use App\Models\Contact\Contact;
use App\Models\Account\Activity;
use App\Models\Account\ActivityType;
use App\Models\Journal\JournalEntry;
use App\Models\Instance\Emotion\Emotion;
class UpdateActivity extends BaseService
{
@@ -17,14 +20,48 @@ class UpdateActivity extends BaseService
{
return [
'account_id' => 'required|integer|exists:accounts,id',
'activity_type_id' => 'required|integer|exists:activity_types,id',
'activity_id' => 'required|integer|exists:activities,id',
'summary' => 'required|string:255',
'activity_type_id' => 'nullable|integer|exists:activity_types,id',
'summary' => 'required|string:100000',
'description' => 'nullable|string:400000000',
'date' => 'required|date|date_format:Y-m-d',
'happened_at' => 'required|date|date_format:Y-m-d',
'emotions' => 'nullable|array',
'contacts' => 'required|array',
];
}
/**
* Validate all datas to execute the service.
*
* @param array $data
* @return bool
*/
public function validate(array $data): bool
{
parent::validate($data);
Activity::where('account_id', $data['account_id'])
->findOrFail($data['activity_id']);
foreach ($data['contacts'] as $contactId) {
Contact::where('account_id', $data['account_id'])
->findOrFail($contactId);
}
if (! empty($data['activity_type_id']) && $data['activity_type_id'] != '') {
ActivityType::where('account_id', $data['account_id'])
->findOrFail($data['activity_type_id']);
}
if (! empty($data['emotions']) && $data['emotions'] != '') {
foreach ($data['emotions'] as $emotionId) {
Emotion::findOrFail($emotionId);
}
}
return true;
}
/**
* Update an activity.
*
@@ -35,19 +72,59 @@ class UpdateActivity extends BaseService
{
$this->validate($data);
$activity = Activity::where('account_id', $data['account_id'])
->findOrFail($data['activity_id']);
$activity = Activity::find($data['activity_id']);
ActivityType::where('account_id', $data['account_id'])
->findOrFail($data['activity_type_id']);
$this->update($data, $activity);
$activity->update([
'activity_type_id' => $data['activity_type_id'],
'summary' => $data['summary'],
'description' => $this->nullOrValue($data, 'description'),
'happened_at' => $data['date'],
// Log a journal entry but need to delete the previous one first
$activity->deleteJournalEntry();
JournalEntry::add($activity);
// Now we update the activity with each one of the attendees
app(AttachContactToActivity::class)->execute([
'account_id' => $data['account_id'],
'activity_id' => $data['activity_id'],
'contacts' => $data['contacts'],
]);
return $activity;
}
/**
* Update the activity.
*
* @param array $data
* @param Activity $activity
* @return void
*/
private function update(array $data, Activity $activity)
{
$activity->update([
'activity_type_id' => $this->nullOrValue($data, 'activity_type_id'),
'summary' => $data['summary'],
'description' => $this->nullOrValue($data, 'description'),
'happened_at' => $data['happened_at'],
]);
if (! empty($data['emotions']) && $data['emotions'] != '') {
$this->updateEmotions($data['emotions'], $activity);
}
}
/**
* Update activity's emotions.
*
* @param array $emotions
* @param Activity $activity
* @return void
*/
private function updateEmotions(array $emotions, Activity $activity)
{
$emotionsSync = [];
foreach ($emotions as $emotion) {
$emotionsSync[$emotion] = ['account_id' => $activity->account_id];
}
$activity->emotions()->sync($emotionsSync);
}
}
@@ -2,6 +2,7 @@
namespace App\Services\Contact\Contact;
use App\Helpers\DateHelper;
use Illuminate\Support\Arr;
use App\Services\BaseService;
use App\Models\Contact\Contact;
@@ -184,7 +185,7 @@ class UpdateBirthdayInformation extends BaseService
$reminder = app(CreateReminder::class)->execute([
'account_id' => $data['account_id'],
'contact_id' => $data['contact_id'],
'initial_date' => $specialDate->date->toDateString(),
'initial_date' => DateHelper::getDate($specialDate),
'frequency_type' => 'year',
'frequency_number' => 1,
'title' => trans(
@@ -2,6 +2,7 @@
namespace App\Services\Contact\Contact;
use App\Helpers\DateHelper;
use Illuminate\Support\Arr;
use App\Services\BaseService;
use App\Models\Contact\Contact;
@@ -192,7 +193,7 @@ class UpdateContactIntroductions extends BaseService
$reminder = app(CreateReminder::class)->execute([
'account_id' => $data['account_id'],
'contact_id' => $data['contact_id'],
'initial_date' => $specialDate->date->toDateString(),
'initial_date' => DateHelper::getDate($specialDate),
'frequency_type' => 'year',
'frequency_number' => 1,
'title' => trans(
@@ -2,6 +2,7 @@
namespace App\Services\Contact\Contact;
use App\Helpers\DateHelper;
use App\Services\BaseService;
use App\Models\Contact\Contact;
use App\Models\Instance\SpecialDate;
@@ -149,7 +150,7 @@ class UpdateDeceasedInformation extends BaseService
$reminder = app(CreateReminder::class)->execute([
'account_id' => $data['account_id'],
'contact_id' => $data['contact_id'],
'initial_date' => $specialDate->date->toDateString(),
'initial_date' => DateHelper::getDate($specialDate),
'frequency_type' => 'year',
'frequency_number' => 1,
'title' => trans(
+11
View File
@@ -23,6 +23,17 @@ return [
*/
'timestamp_format' => env('API_TIMESTAMP_FORMAT', 'Y-m-d\TH:i:s\Z'),
/*
|--------------------------------------------------------------------------
| Format of the date timestamp
|--------------------------------------------------------------------------
|
| This defines the format of the date that is returned on some API calls
| when it requires a date only.
|
*/
'date_timestamp_format' => env('API_DATE_TIMESTAMP_FORMAT', 'Y-m-d'),
/*
|--------------------------------------------------------------------------
| Error codes for the API
+2 -1
View File
@@ -2,6 +2,7 @@
use Carbon\Carbon;
use App\Models\User\User;
use App\Helpers\DateHelper;
use App\Models\Account\Account;
use Illuminate\Database\Seeder;
use App\Helpers\CountriesHelper;
@@ -277,7 +278,7 @@ class FakeContentTableSeeder extends Seeder
{
if (rand(1, 2) == 1) {
for ($j = 0; $j < rand(1, 13); $j++) {
$date = Carbon::instance($this->faker->dateTimeThisYear($max = 'now'))->format('Y-m-d');
$date = DateHelper::getDate(Carbon::instance($this->faker->dateTimeThisYear($max = 'now')));
$request = [
'account_id' => $this->contact->account_id,
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -1,7 +1,7 @@
{
"/js/manifest.js": "/js/manifest.js?id=7db827d654313dce4250",
"/js/vendor.js": "/js/vendor.js?id=add29da7fca54527d87a",
"/js/app.js": "/js/app.js?id=f8e3246e1aa973c9616e",
"/js/app.js": "/js/app.js?id=41ecbc54d1228fc3282d",
"/css/app-ltr.css": "/css/app-ltr.css?id=ff41d6c39e7fba5cf37e",
"/css/app-rtl.css": "/css/app-rtl.css?id=ee3f6d81111cdc99f35d",
"/css/stripe.css": "/css/stripe.css?id=76c70a7b11ae5f38a725",
-9
View File
@@ -105,15 +105,6 @@ Vue.component(
'form-specialdeceased',
require('./components/partials/SpecialDeceased.vue').default
);
Vue.component(
'emotion',
require('./components/people/Emotion.vue').default
);
Vue.component(
'participant-list',
require('./components/people/Participant.vue').default
);
// Dashboard
Vue.component(
+19 -12
View File
@@ -86,20 +86,15 @@ export default {
},
},
watch: {
value: function (newValue) {
this.updateExchange(newValue);
}
},
mounted() {
this.exchange = this.value;
if (this.exchange === '') {
this.exchange = this.defaultDate;
}
if (this.exchange !== '') {
var mdate = moment(this.exchange, this.exchangeFormat);
if (! mdate.isValid()) {
mdate = moment();
}
this.selectedDate = mdate.toDate();
}
this.updateExchange(this.value === '' ? this.defaultDate : this.value);
this.mondayFirst = moment.localeData().firstDayOfWeek() == 1;
this.update(this.selectedDate);
},
methods: {
@@ -138,6 +133,18 @@ export default {
this.$emit('input', this.exchange);
},
updateExchange(date) {
this.exchange = date;
if (this.exchange !== '') {
var mdate = moment(this.exchange, this.exchangeFormat);
if (! mdate.isValid()) {
mdate = moment();
}
this.selectedDate = mdate.toDate();
}
this.update(this.selectedDate);
},
/**
* Format the typed value with the locale specification.
* @param date string in locale format
+2 -2
View File
@@ -181,12 +181,12 @@ export default {
this.menu = false;
this.chosenEmotions.push(emotion);
this.emotionsMenu = 'primary';
this.$emit('updateEmotionsList', this.chosenEmotions);
this.$emit('update', this.chosenEmotions);
},
removeEmotion(emotion) {
this.chosenEmotions.splice(emotion, 1);
this.$emit('updateEmotionsList', this.chosenEmotions);
this.$emit('update', this.chosenEmotions);
}
}
};
+30 -34
View File
@@ -18,35 +18,33 @@ input[type=text]:focus {
</style>
<template>
<div>
<div class="relative">
<ul v-show="chosenParticipants.length != 0" class="mr2 mb3">
<li v-for="chosenParticipant in chosenParticipants"
:key="chosenParticipant.id"
class="dib participant br5 mr2"
<div class="relative">
<ul v-show="chosenParticipants.length != 0" class="mr2 mb3">
<li v-for="chosenParticipant in chosenParticipants"
:key="chosenParticipant.id"
class="dib participant br5 mr2"
>
<span class="ph2 pv1 dib">
{{ chosenParticipant.name }}
</span>
<span class="bl ph2 pv1 f6 pointer" @click.prevent="remove(chosenParticipant)">
</span>
</li>
</ul>
<div v-show="participants.length != 0" class="ba b--gray-monica">
<span class="db bb b--gray-monica pa2">
<input v-model="search" type="text" :placeholder="$t('app.filter')" class="br2 f5 w-100 ba b--black-20 pa2 outline-0" />
</span>
<ul class="overflow-auto participant-list">
<li v-for="fparticipant in filteredList"
:key="fparticipant.id"
class="bb b--gray-monica pa2 pointer potential-participant"
@click.prevent="select(fparticipant)"
>
<span class="ph2 pv1 dib">
{{ chosenParticipant.name }}
</span>
<span class="bl ph2 pv1 f6 pointer" @click.prevent="remove(chosenParticipant)">
</span>
{{ fparticipant.name }}
</li>
</ul>
<div v-show="participants.length != 0" class="ba b--gray-monica">
<span class="db bb b--gray-monica pa2">
<input v-model="search" type="text" :placeholder="$t('app.filter')" class="br2 f5 w-100 ba b--black-20 pa2 outline-0" />
</span>
<ul class="overflow-auto participant-list">
<li v-for="fparticipant in filteredList"
:key="fparticipant.id"
class="bb b--gray-monica pa2 pointer potential-participant"
@click.prevent="select(fparticipant)"
>
{{ fparticipant.name }}
</li>
</ul>
</div>
</div>
</div>
</template>
@@ -58,9 +56,7 @@ export default {
props: {
initialParticipants: {
type: Array,
default: function () {
return [];
}
default: () => [],
},
hash: {
type: String,
@@ -73,7 +69,6 @@ export default {
search: '',
participants: [],
chosenParticipants: [],
participant: null,
};
},
@@ -83,7 +78,8 @@ export default {
// also, sort the list by name
var list;
list = this.participants.filter(participant => {
return participant.name.toLowerCase().includes(this.search.toLowerCase());
return participant.name.toLowerCase().includes(this.search.toLowerCase())
&& this.chosenParticipants.find(p => p.id === participant.id) === undefined;
});
function compare(a, b) {
@@ -109,9 +105,9 @@ export default {
},
getParticipants: function () {
axios.get('/people/' + this.hash + '/activities/contacts')
axios.get('people/' + this.hash + '/activities/contacts')
.then(response => {
this.participants = response.data;
this.participants = _.toArray(response.data);
});
},
@@ -124,7 +120,7 @@ export default {
remove(participant) {
this.participants.push(participant);
this.chosenParticipants.splice(this.chosenParticipants.indexOf(participant), 1);
}
},
}
};
</script>
@@ -32,83 +32,96 @@
</div>
<!-- LOG AN ACTIVITY -->
<create-activity
:hash="hash"
:name="name"
:display-log-activity="displayLogActivity"
@update="updateList($event)"
@cancel="displayLogActivity = false"
/>
<template v-if="displayLogActivity">
<create-activity
:hash="hash"
:contact-id="contactId"
:name="name"
@update="updateList($event)"
@cancel="displayLogActivity = false"
/>
</template>
<!-- LIST OF ACTIVITIES -->
<div v-for="activity in activities" :key="activity.id" class="ba br2 b--black-10 br--top w-100 mb2">
<h2 class="pl2 pr2 pt3 f5">
{{ activity.summary }}
</h2>
<template v-if="!activity.edit">
<h2 class="pl2 pr2 pt3 f5">
{{ activity.summary }}
</h2>
<div v-if="activity.description" dir="auto" class="pl2 pr2 pb3" v-html="activity.description">
</div>
<!-- DETAILS -->
<div class="pa2 cf bt b--black-10 br--bottom f7">
<div class="w-70" :class="[ dirltr ? 'fl' : 'fr' ]">
<ul class="list">
<!-- HAPPENED AT -->
<li class="di" :class="[ dirltr ? 'mr3' : 'ml3' ]">
{{ activity.happened_at | moment }}
</li>
<!-- PARTICIPANT LIST -->
<li v-if="activity.attendees.total > 1" class="di">
<ul class="di list" :class="[ dirltr ? 'mr3' : 'ml3' ]">
<li class="di">
{{ $t('people.activities_list_participants') }}
</li>
<li v-for="attendee in activity.attendees.contacts" :key="attendee.id" class="di mr2">
<span v-show="attendee.id != contactId">{{ attendee.complete_name }}</span>
</li>
</ul>
</li>
<!-- EMOTIONS LIST -->
<li v-if="activity.emotions.length != 0" class="di">
<ul class="di list" :class="[ dirltr ? 'mr3' : 'ml3' ]">
<li class="di">
{{ $t('people.activities_list_emotions') }}
</li>
<li v-for="emotion in activity.emotions" :key="emotion.id" class="di">
{{ $t('app.emotion_' + emotion.name) }}
</li>
</ul>
</li>
<!-- ACTIVITY TYPE -->
<li v-if="activity.activity_type" class="di" :class="[ dirltr ? 'mr3' : 'ml3' ]">
{{ activity.activity_type.name }}
</li>
</ul>
<div v-if="activity.description" dir="auto" class="pl2 pr2 pb3" v-html="activity.description">
</div>
<div class="w-30" :class="[ dirltr ? 'fl tr' : 'fr tl' ]">
<!-- ACTIONS -->
<ul class="list">
<li class="di">
<a v-show="destroyActivityId != activity.id" class="pointer" @click.prevent="showDestroyActivity(activity)">{{ $t('app.delete') }}</a>
<ul v-show="destroyActivityId == activity.id" class="di">
<li class="di">
<a class="pointer red" @click.prevent="destroyActivity(activity)">
{{ $t('app.delete_confirm') }}
</a>
</li>
<li class="di">
<a class="pointer mr1" @click.prevent="destroyActivityId = 0">
{{ $t('app.cancel') }}
</a>
</li>
</ul>
</li>
</ul>
<!-- DETAILS -->
<div class="pa2 cf bt b--black-10 br--bottom f7">
<div class="w-70" :class="[ dirltr ? 'fl' : 'fr' ]">
<ul class="list">
<!-- HAPPENED AT -->
<li class="di" :class="[ dirltr ? 'mr3' : 'ml3' ]">
{{ activity.happened_at | moment }}
</li>
<!-- PARTICIPANT LIST -->
<li v-if="activity.attendees.total > 1" class="di">
<ul class="di list" :class="[ dirltr ? 'mr3' : 'ml3' ]">
<li class="di">
{{ $t('people.activities_list_participants') }}
</li>
<li v-for="attendee in activity.attendees.contacts.filter(c => c.id !== contactId)" :key="attendee.id" class="di mr2">
<span>{{ attendee.complete_name }}</span>
</li>
</ul>
</li>
<!-- EMOTIONS LIST -->
<li v-if="activity.emotions.length != 0" class="di">
<ul class="di list" :class="[ dirltr ? 'mr3' : 'ml3' ]">
<li class="di">
{{ $t('people.activities_list_emotions') }}
</li>
<li v-for="emotion in activity.emotions" :key="emotion.id" class="di">
{{ $t('app.emotion_' + emotion.name) }}
</li>
</ul>
</li>
<!-- ACTIVITY TYPE -->
<li v-if="activity.activity_type" class="di" :class="[ dirltr ? 'mr3' : 'ml3' ]">
{{ activity.activity_type.name }}
</li>
</ul>
</div>
<div class="w-30" :class="[ dirltr ? 'fl tr' : 'fr tl' ]">
<!-- ACTIONS -->
<ul class="list">
<li class="di">
<a href="" class="pointer" @click.prevent="$set(activity, 'edit', true)">{{ $t('app.edit') }}</a>
<a v-show="destroyActivityId != activity.id" href="" class="pointer" @click.prevent="showDestroyActivity(activity)">{{ $t('app.delete') }}</a>
<ul v-show="destroyActivityId == activity.id" class="di">
<li class="di">
<a class="pointer red" @click.prevent="destroyActivity(activity)">
{{ $t('app.delete_confirm') }}
</a>
</li>
<li class="di">
<a class="pointer mr1" @click.prevent="destroyActivityId = 0">
{{ $t('app.cancel') }}
</a>
</li>
</ul>
</li>
</ul>
</div>
</div>
</div>
</template>
<create-activity v-else
:hash="hash"
:name="name"
:activity="activity"
:contact-id="contactId"
@update="updateList($event)"
@cancel="$set(activity, 'edit', false); displayLogActivity = false"
/>
</div>
<p v-if="activities.length > 0" class="tc">
@@ -182,7 +195,7 @@ export default {
},
getActivities() {
axios.get('/people/' + this.hash + '/activities')
axios.get('api/contacts/' + this.contactId + '/activities')
.then(response => {
this.activities = response.data.data;
});
@@ -198,7 +211,7 @@ export default {
},
destroyActivity(activity) {
axios.delete('/people/' + this.hash + '/activities/' + activity.id)
axios.delete('api/activities/' + activity.id)
.then(response => {
this.activities.splice(this.activities.indexOf(activity), 1);
});
@@ -2,33 +2,27 @@
</style>
<template>
<div>
<select class="br2 f5 w-100 ba b--black-40 pa2 outline-0" @change="$emit('change', $event.target.value)">
<option value="" selected>
-
</option>
<optgroup v-for="activityTypeCategory in activityCategories" :key="activityTypeCategory.id" :label="activityTypeCategory.name">
<option v-for="type in activityTypeCategory.types" :key="type.id" :value="type.id">
{{ type.name }}
</option>
</optgroup>
</select>
</div>
<form-select
:title="title"
:options="activityCategories"
:iclass="'br2 f5 w-100 ba b--black-40 pa2 outline-0'"
@input="$emit('input', $event)"
/>
</template>
<script>
export default {
data() {
return {
activityCategories: [],
};
props: {
title: {
type: String,
default: '',
},
},
computed: {
dirltr() {
return this.$root.htmldir == 'ltr';
}
data() {
return {
activityCategories: null,
};
},
mounted() {
@@ -41,9 +35,14 @@ export default {
},
getActivities() {
axios.get('/activityCategories')
axios.get('activityCategories')
.then(response => {
this.activityCategories = response.data;
this.activityCategories = Object.assign({}, _.map(response.data, a => {
return {
name: a.name,
options: a.types,
};
}));
});
},
}
@@ -5,18 +5,16 @@
<div>
<!-- LOG AN ACTIVITY -->
<transition name="fade">
<div v-if="displayLogActivity" class="ba br3 mb3 pa3 b--black-40">
<div class="ba br3 mb3 pa3 b--black-40">
<div class="dt dt--fixed pb3 mb3 mb0-ns bb b--gray-monica">
<!-- SUMMARY -->
<div class="dtc pr2">
<p class="mb2 b">
{{ $t('people.activities_add_title', { name: name }) }}
</p>
<form-input
:id="'summary'"
v-model="newActivity.summary"
:input-type="'text'"
:required="false"
:title="$t('people.activities_add_title', { name: name })"
:required="true"
/>
</div>
@@ -28,10 +26,11 @@
<div class="di">
<div class="dib">
<form-date
ref="date"
v-model="newActivity.happened_at"
:show-calendar-on-focus="true"
:default-date="todayDate"
:locale="locale"
@selected="updateDate($event)"
/>
</div>
</div>
@@ -58,16 +57,13 @@
<!-- DESCRIPTION -->
<div v-if="displayDescription" class="bb b--gray-monica pv3 mb3">
<label>
{{ $t('people.activities_summary') }}
</label>
<form-textarea
v-model="newActivity.description"
:required="true"
:no-label="true"
:rows="4"
:title="$t('people.activities_summary')"
:placeholder="$t('people.conversation_add_content')"
@contentChange="updateDescription($event)"
/>
<p class="f6">
{{ $t('app.markdown_description') }} <a href="https://guides.github.com/features/mastering-markdown/" rel="noopener noreferrer" target="_blank">
@@ -81,15 +77,19 @@
<label>
{{ $t('people.activities_add_emotions_title') }}
</label>
<emotion class="pv2" @updateEmotionsList="updateEmotionsList" />
<emotion
class="pv2"
:initial-emotions="newActivity.emotions"
@update="updateEmotionsList"
/>
</div>
<!-- ACTIVITY CATEGORIES -->
<div v-if="displayCategory" class="bb b--gray-monica pb3 mb3">
<label>
{{ $t('people.activities_add_pick_activity') }}
</label>
<activity-type-list @change="updateCategory($event)" />
<activity-type-list
:title="$t('people.activities_add_pick_activity')"
@input="updateCategory($event)"
/>
</div>
<!-- PARTICPANTS -->
@@ -97,7 +97,11 @@
<label>
{{ $t('people.activities_add_participants', {name: name}) }}
</label>
<participant-list :hash="hash" @update="updateParticipant($event)" />
<participant
:hash="hash"
:initial-participants="participants"
@update="updateParticipant($event)"
/>
</div>
<error :errors="errors" />
@@ -106,13 +110,13 @@
<div class="pt3">
<div class="flex-ns justify-between">
<div class="">
<a class="btn btn-secondary tc w-auto-ns w-100 mb2 pb0-ns" @click.prevent="resetFields()">
<a class="btn btn-secondary tc w-auto-ns w-100 mb2 pb0-ns" @click.prevent="close()">
{{ $t('app.cancel') }}
</a>
</div>
<div class="">
<button class="btn btn-primary w-auto-ns w-100 mb2 pb0-ns" @click.prevent="store()">
{{ $t('app.add') }}
{{ activity ? $t('app.save') : $t('app.add') }}
</button>
</div>
</div>
@@ -124,13 +128,17 @@
<script>
import moment from 'moment';
import Error from '../../partials/Error.vue';
import ActivityTypeList from './ActivityTypeList.vue';
import Emotion from '../Emotion.vue';
import Error from '../../partials/Error.vue';
import Participant from '../Participant.vue';
export default {
components: {
ActivityTypeList,
Error
Emotion,
Error,
Participant,
},
filters: {
@@ -144,19 +152,22 @@ export default {
type: String,
default: '',
},
contactId: {
type: Number,
default: 0,
},
name: {
type: String,
default: '',
},
displayLogActivity: {
type: Boolean,
default: false,
activity: {
type: Object,
default: null,
},
},
data() {
return {
emotions: [],
displayDescription: false,
displayEmotions: false,
displayCategory: false,
@@ -167,8 +178,10 @@ export default {
happened_at: '',
emotions: [],
activity_type_id: null,
participants: [],
contacts: [],
},
todayDate: '',
participants: [],
errors: [],
};
},
@@ -183,6 +196,12 @@ export default {
}
},
watch: {
participants(value) {
this.newActivity.contacts = _.map(value, p => p.id);
}
},
mounted() {
this.prepareComponent();
},
@@ -190,38 +209,55 @@ export default {
methods: {
prepareComponent() {
this.todayDate = moment().format('YYYY-MM-DD');
this.newActivity.happened_at = this.todayDate;
this.resetFields();
},
resetFields() {
this.displayDescription = false;
this.displayEmotions = false;
this.displayCategory = false;
this.displayParticipants = false;
this.newActivity.summary = '';
this.newActivity.participants = [];
this.description = '';
this.happened_at = '';
this.emotions = [];
this.activity_type_id = 0;
if (this.activity) {
this.newActivity.summary = this.activity.summary;
this.newActivity.description = this.activity.description;
this.newActivity.happened_at = this.activity.happened_at;
this.newActivity.emotions = this.activity.emotions;
this.newActivity.activity_type_id = this.activity.activity_type ? this.activity.activity_type.id : null;
this.participants = this.activity.attendees.contacts.map(attendee => {
return {
id: attendee.id,
name: attendee.complete_name,
};
});
} else {
this.newActivity.summary = '';
this.newActivity.description = '';
this.newActivity.happened_at = this.todayDate;
this.newActivity.emotions = [];
this.newActivity.activity_type_id = null;
this.participants = [];
}
this.displayDescription = this.newActivity.description ? this.newActivity.description != '' : false;
this.displayEmotions = this.newActivity.emotions && this.newActivity.emotions.length > 0;
this.displayCategory = this.newActivity.activity_type_id !== null;
this.displayParticipants = this.participants.length > 0;
this.errors = [];
},
close() {
this.resetFields();
this.$emit('cancel');
},
updateDescription(description) {
this.newActivity.description = description;
},
updateDate(date) {
this.newActivity.happened_at = date;
},
updateCategory(id) {
this.newActivity.activity_type_id = parseInt(id);
},
store() {
axios.post('/people/' + this.hash + '/activities', this.newActivity)
let method = this.activity ? 'put' : 'post';
let url = this.activity ? 'api/activities/'+this.activity.id : 'api/activities';
if (! this.newActivity.contacts.includes(this.contactId)) {
this.newActivity.contacts.push(this.contactId);
}
axios[method](url, this.newActivity)
.then(response => {
this.resetFields();
this.$emit('update', response.data.data);
@@ -234,24 +270,27 @@ export default {
});
})
.catch(error => {
this.errors = _.flatten(_.toArray(error.response.data));
this._errorHandle(error);
});
},
updateParticipant: function (participants) {
this.newActivity.participants = participants;
this.participants = participants;
},
updateEmotionsList: function(emotions) {
this.emotions = emotions;
this.newActivity.emotions = [];
// filter the list of emotions to populate a new array
// containing only the emotion ids and not the entire objetcs
for (let i = 0; i < this.emotions.length; i++) {
this.newActivity.emotions.push(this.emotions[i].id);
this.newActivity.emotions = _.map(emotions, emotion => emotion.id);
},
_errorHandle(error) {
if (error.response && typeof error.response.data === 'object') {
this.errors = _.flatten(_.toArray(error.response.data));
} else {
this.errors = [this.$t('app.error_try_again'), error.message];
}
}
},
}
};
</script>
@@ -100,7 +100,7 @@
<label class="b">
{{ $t('people.modal_call_emotion') }}
</label>
<emotion class="pv2" @updateEmotionsList="updateEmotionsList" />
<emotion class="pv2" @update="updateEmotionsList" />
</div>
<!-- ACTIONS -->
@@ -174,7 +174,7 @@
<label class="b">
{{ $t('people.modal_call_emotion') }}
</label>
<emotion class="pv2" :initial-emotions="call.emotions" @updateEmotionsList="updateEmotionsList" />
<emotion class="pv2" :initial-emotions="call.emotions" @update="updateEmotionsList" />
</div>
<!-- ACTIONS -->
@@ -245,8 +245,12 @@
<script>
import moment from 'moment';
import Emotion from '../Emotion.vue';
export default {
components: {
Emotion,
},
filters: {
moment: function (date) {
+2 -2
View File
@@ -83,9 +83,9 @@ Route::group(['middleware' => ['auth:api']], function () {
Route::get('/contacts/{contact}/conversations', 'Contact\\ApiConversationController@conversations');
// Activities
Route::apiResource('activities', 'ApiActivityController')
Route::apiResource('activities', 'ApiActivitiesController')
->names(['index' => 'activities', 'show' => 'activity']);
Route::get('/contacts/{contact}/activities', 'ApiActivityController@activities');
Route::get('/contacts/{contact}/activities', 'ApiActivitiesController@activities');
// Reminders
Route::apiResource('reminders', 'ApiReminderController');
+1 -1
View File
@@ -176,7 +176,7 @@ Route::middleware(['auth', 'verified', 'mfa'])->group(function () {
// Activities
Route::get('/activityCategories', 'Contacts\\ActivitiesController@categories')->name('activities.categories');
Route::resource('people/{contact}/activities', 'Contacts\\ActivitiesController')->only(['index', 'store', 'destroy']);
Route::resource('people/{contact}/activities', 'Contacts\\ActivitiesController')->only(['index']);
Route::get('/people/{contact}/activities/contacts', 'Contacts\\ActivitiesController@contacts')->name('activities.contacts');
Route::get('/people/{contact}/activities/summary', 'Contacts\\ActivitiesController@summary')->name('activities.summary');
Route::get('/people/{contact}/activities/{year}', 'Contacts\\ActivitiesController@year')->name('activities.year');
+508
View File
@@ -0,0 +1,508 @@
<?php
namespace Tests\Api;
use Tests\ApiTestCase;
use App\Models\Contact\Contact;
use App\Models\Account\Activity;
use App\Models\Account\ActivityType;
use Illuminate\Foundation\Testing\DatabaseTransactions;
class ApiActivitiesTest extends ApiTestCase
{
use DatabaseTransactions;
protected $jsonActivity = [
'id',
'object',
'summary',
'description',
'happened_at',
'activity_type' => [
'id',
'object',
'name',
'location_type',
'activity_type_category' => [
'id',
'object',
'name',
'account' => [
'id',
],
'created_at',
'updated_at',
],
'account'=> [
'id',
],
'created_at',
'updated_at',
],
'attendees' => [
'total',
'contacts' => [
'*' => [
'id',
'object',
'first_name',
'last_name',
'complete_name',
],
],
],
'emotions' => [
'*' => [
'id',
'object',
'name',
],
],
'account' => [
'id',
],
'created_at',
'updated_at',
];
/** @test */
public function activities_get_all()
{
$user = $this->signin();
$activity1 = factory(Activity::class)->create([
'account_id' => $user->account_id,
]);
$activity2 = factory(Activity::class)->create([
'account_id' => $user->account_id,
]);
$response = $this->json('GET', '/api/activities');
$response->assertStatus(200);
$response->assertJsonStructure([
'data' => ['*' => $this->jsonActivity],
]);
$response->assertJsonFragment([
'object' => 'activity',
'id' => $activity1->id,
]);
$response->assertJsonFragment([
'object' => 'activity',
'id' => $activity2->id,
]);
}
/** @test */
public function activities_get_contact_all()
{
$user = $this->signin();
$contact1 = factory(Contact::class)->create([
'account_id' => $user->account_id,
]);
$activity1 = factory(Activity::class)->create([
'account_id' => $user->account_id,
]);
$activity1->contacts()->attach($contact1, ['account_id' => $user->account_id]);
$contact2 = factory(Contact::class)->create([
'account_id' => $user->account_id,
]);
$activity2 = factory(Activity::class)->create([
'account_id' => $user->account_id,
]);
$activity2->contacts()->attach($contact2, ['account_id' => $user->account_id]);
$response = $this->json('GET', '/api/contacts/'.$contact1->id.'/activities');
$response->assertStatus(200);
$response->assertJsonStructure([
'data' => ['*' => $this->jsonActivity],
]);
$response->assertJsonFragment([
'object' => 'activity',
'id' => $activity1->id,
]);
$response->assertJsonMissingExact([
'object' => 'activity',
'id' => $activity2->id,
]);
}
/** @test */
public function activities_get_contact_all_error()
{
$this->signin();
$response = $this->json('GET', '/api/contacts/0/activities');
$this->expectNotFound($response);
}
/** @test */
public function activities_get_contact_all_error_wrong_account()
{
$this->signin();
$contact = factory(Contact::class)->create();
$response = $this->json('GET', '/api/contacts/'.$contact->id.'/activities');
$this->expectNotFound($response);
}
/** @test */
public function activities_get_one()
{
$user = $this->signin();
$activity1 = factory(Activity::class)->create([
'account_id' => $user->account_id,
]);
$activity2 = factory(Activity::class)->create([
'account_id' => $user->account_id,
]);
$response = $this->json('GET', '/api/activities/'.$activity1->id);
$response->assertStatus(200);
$response->assertJsonStructure([
'data' => $this->jsonActivity,
]);
$response->assertJsonFragment([
'object' => 'activity',
'id' => $activity1->id,
]);
$response->assertJsonMissingExact([
'object' => 'activity',
'id' => $activity2->id,
]);
}
/** @test */
public function activities_get_one_error()
{
$this->signin();
$response = $this->json('GET', '/api/activities/0');
$this->expectNotFound($response);
}
/** @test */
public function activities_get_one_error_wrong_account()
{
$this->signin();
$activity = factory(Activity::class)->create();
$response = $this->json('GET', '/api/activities/'.$activity->id);
$this->expectNotFound($response);
}
/** @test */
public function activities_create()
{
$user = $this->signin();
$contact = factory(Contact::class)->create([
'account_id' => $user->account_id,
]);
$activityType = factory(ActivityType::class)->create([
'account_id' => $user->account_id,
]);
$response = $this->json('POST', '/api/activities', [
'contacts' => [$contact->id],
'description' => 'the description',
'summary' => 'the activity',
'happened_at' => '2018-05-01',
'activity_type_id' => $activityType->id,
]);
$response->assertStatus(201);
$response->assertJsonStructure([
'data' => $this->jsonActivity,
]);
$activity_id = $response->json('data.id');
$response->assertJsonFragment([
'object' => 'activity',
'id' => $activity_id,
]);
$this->assertGreaterThan(0, $activity_id);
$this->assertDatabaseHas('activities', [
'account_id' => $user->account_id,
'id' => $activity_id,
'summary' => 'the activity',
'description' => 'the description',
'happened_at' => '2018-05-01',
]);
$this->assertDatabaseHas('activity_contact', [
'account_id' => $user->account_id,
'contact_id' => $contact->id,
'activity_id' => $activity_id,
]);
}
/** @test */
public function activities_create_error_wrong_parameter()
{
$user = $this->signin();
$contact = factory(Contact::class)->create([
'account_id' => $user->account_id,
]);
$response = $this->json('POST', '/api/activities', [
'contact_id' => [$contact->id],
]);
$this->expectDataError($response, [
'The summary field is required.',
'The happened at field is required.',
]);
}
/** @test */
public function activities_create_error_bad_account()
{
$this->signin();
$contact = factory(Contact::class)->create();
$response = $this->json('POST', '/api/activities', [
'contacts' => [$contact->id],
'description' => 'the description',
'summary' => 'the activity',
'happened_at' => '2018-05-01',
]);
$this->expectNotFound($response);
}
/** @test */
public function activities_create_error_bad_account2()
{
$user = $this->signin();
$contact = factory(Contact::class)->create([
'account_id' => $user->account_id,
]);
$activityType = factory(ActivityType::class)->create();
$response = $this->json('POST', '/api/activities', [
'contacts' => [$contact->id],
'description' => 'the description',
'summary' => 'the activity',
'happened_at' => '2018-05-01',
'activity_type_id' => $activityType->id,
]);
$this->expectNotFound($response);
}
/** @test */
public function activities_update()
{
$user = $this->signin();
$contact = factory(Contact::class)->create([
'account_id' => $user->account_id,
]);
$activity = factory(Activity::class)->create([
'account_id' => $user->account_id,
]);
$response = $this->json('PUT', '/api/activities/'.$activity->id, [
'contacts' => [$contact->id],
'description' => 'the description',
'summary' => 'the activity',
'happened_at' => '2018-05-01',
]);
$response->assertStatus(200);
$response->assertJsonStructure([
'data' => $this->jsonActivity,
]);
$activity_id = $response->json('data.id');
$this->assertEquals($activity->id, $activity_id);
$response->assertJsonFragment([
'object' => 'activity',
'id' => $activity_id,
]);
$this->assertGreaterThan(0, $activity_id);
$this->assertDatabaseHas('activities', [
'account_id' => $user->account_id,
'id' => $activity_id,
'summary' => 'the activity',
'happened_at' => '2018-05-01',
]);
$this->assertDatabaseHas('activity_contact', [
'account_id' => $user->account_id,
'contact_id' => $contact->id,
'activity_id' => $activity_id,
]);
}
/** @test */
public function activities_update_existing()
{
$user = $this->signin();
$activity = factory(Activity::class)->create([
'account_id' => $user->account_id,
]);
$contact = factory(Contact::class)->create([
'account_id' => $user->account_id,
]);
$contact->activities()->attach($activity, [
'account_id' => $user->account_id,
]);
$contact2 = factory(Contact::class)->create([
'account_id' => $user->account_id,
]);
$contact2->activities()->attach($activity, [
'account_id' => $user->account_id,
]);
$this->assertDatabaseHas('activity_contact', [
'account_id' => $user->account_id,
'contact_id' => $contact->id,
'activity_id' => $activity->id,
]);
$this->assertDatabaseHas('activity_contact', [
'account_id' => $user->account_id,
'contact_id' => $contact2->id,
'activity_id' => $activity->id,
]);
$response = $this->json('PUT', '/api/activities/'.$activity->id, [
'contacts' => [$contact->id],
'description' => 'the description',
'summary' => 'the activity',
'happened_at' => '2018-05-01',
]);
$response->assertStatus(200);
$response->assertJsonStructure([
'data' => $this->jsonActivity,
]);
$activity_id = $response->json('data.id');
$this->assertEquals($activity->id, $activity_id);
$response->assertJsonFragment([
'object' => 'activity',
'id' => $activity_id,
]);
$this->assertGreaterThan(0, $activity_id);
$this->assertDatabaseHas('activities', [
'account_id' => $user->account_id,
'id' => $activity_id,
'summary' => 'the activity',
'happened_at' => '2018-05-01',
]);
$this->assertDatabaseHas('activity_contact', [
'account_id' => $user->account_id,
'contact_id' => $contact->id,
'activity_id' => $activity_id,
]);
$this->assertDatabaseMissing('activity_contact', [
'account_id' => $user->account_id,
'contact_id' => $contact2->id,
'activity_id' => $activity_id,
]);
}
/** @test */
public function activities_update_error_wrong_parameter()
{
$user = $this->signin();
$response = $this->json('PUT', '/api/activities/0', [
'description' => 'the description',
'summary' => 'the activity',
'happened_at' => '2018-05-01',
]);
$this->expectDataError($response, [
'The selected activity id is invalid.',
]);
}
/** @test */
public function activities_update_error_wrong_account_for_activity()
{
$user = $this->signin();
$contact = factory(Contact::class)->create([
'account_id' => $user->account_id,
]);
$activity = factory(Activity::class)->create();
$response = $this->json('PUT', '/api/activities/'.$activity->id, [
'contacts' => [$contact->id],
'description' => 'the description',
'summary' => 'the activity',
'happened_at' => '2018-05-01',
]);
$this->expectNotFound($response);
}
/** @test */
public function activities_update_error_wrong_account_for_contacts()
{
$user = $this->signin();
$contact = factory(Contact::class)->create();
$activity = factory(Activity::class)->create([
'account_id' => $user->account_id,
]);
$response = $this->json('PUT', '/api/activities/'.$activity->id, [
'contacts' => [$contact->id],
'description' => 'the description',
'summary' => 'the activity',
'happened_at' => '2018-05-01',
]);
$this->expectNotFound($response);
}
/** @test */
public function activities_delete()
{
$user = $this->signin();
$activity = factory(Activity::class)->create([
'account_id' => $user->account_id,
]);
$this->assertDatabaseHas('activities', [
'account_id' => $user->account_id,
]);
$response = $this->json('DELETE', '/api/activities/'.$activity->id);
$response->assertStatus(200);
$this->assertDatabaseMissing('activities', [
'account_id' => $user->account_id,
'id' => $activity->id,
]);
}
/** @test */
public function activities_delete_error()
{
$this->signin();
$response = $this->json('DELETE', '/api/activities/0');
$this->expectDataError($response, [
'The selected activity id is invalid.',
]);
}
/** @test */
public function activities_delete_with_wrong_account()
{
$this->signin();
$activity = factory(Activity::class)->create();
$response = $this->json('DELETE', '/api/activities/'.$activity->id);
$this->expectNotFound($response);
}
}
-46
View File
@@ -6,7 +6,6 @@ use App\Models\User\User;
use Tests\FeatureTestCase;
use App\Models\Contact\Contact;
use App\Models\Account\Activity;
use App\Models\Account\ActivityType;
use Illuminate\Foundation\Testing\DatabaseTransactions;
class ActivityTest extends FeatureTestCase
@@ -101,49 +100,4 @@ class ActivityTest extends FeatureTestCase
$response->decodeResponseJson()
);
}
public function test_it_stores_the_activity()
{
$user = $this->signin();
$contact = factory(Contact::class)->create([
'account_id' => $user->account_id,
]);
$activityType = factory(ActivityType::class)->create([
'account_id' => $user->account_id,
]);
$response = $this->post('/people/'.$contact->hashID().'/activities', [
'account_id' => $user->account_id,
'activity_type_id' => $activityType->id,
'summary' => 'summary',
'description' => 'description',
'happened_at' => '2010-10-10',
'participants' => [],
]);
$response->assertStatus(200);
$response->assertJsonStructure([
'data' => $this->jsonStructure,
]);
}
public function test_it_deletes_an_activity()
{
$user = $this->signin();
$contact = factory(Contact::class)->create([
'account_id' => $user->account_id,
]);
$activity = factory(Activity::class)->create([
'account_id' => $user->account_id,
]);
$response = $this->delete('/people/'.$contact->hashID().'/activities/'.$activity->id);
$response->assertStatus(200);
}
}
+2 -1
View File
@@ -3,6 +3,7 @@
namespace Tests\Feature;
use Tests\FeatureTestCase;
use App\Helpers\DateHelper;
use App\Models\Contact\Tag;
use App\Models\Contact\Gift;
use App\Helpers\StringHelper;
@@ -280,7 +281,7 @@ class ContactTest extends FeatureTestCase
$reminder = [
'title' => $this->faker->sentence('5'),
'initial_date' => $this->faker->dateTimeBetween('now', '+2 years')->format('Y-m-d'),
'initial_date' => DateHelper::getDate(DateHelper::parseDateTime($this->faker->dateTimeBetween('now', '+2 years'))),
'frequency_type' => 'one_time',
'description' => $this->faker->sentence(),
];
+3 -2
View File
@@ -5,6 +5,7 @@ namespace Tests\Unit\Models;
use Carbon\Carbon;
use App\Models\User\User;
use Tests\FeatureTestCase;
use App\Helpers\DateHelper;
use App\Models\Contact\Debt;
use App\Models\Account\Photo;
use App\Models\Contact\Gender;
@@ -388,7 +389,7 @@ class ContactTest extends FeatureTestCase
$this->assertEquals(
'2015-10-29',
$contact->getLastActivityDate()->format('Y-m-d')
DateHelper::getDate($contact->getLastActivityDate())
);
}
@@ -405,7 +406,7 @@ class ContactTest extends FeatureTestCase
$this->assertEquals(
'2015-10-29',
$contact->getLastActivityDate()->format('Y-m-d')
DateHelper::getDate($contact->getLastActivityDate())
);
}
@@ -18,9 +18,12 @@ class CreateActivityTest extends TestCase
use DatabaseTransactions;
/** @test */
public function it_stores_a_activity_and_creates_an_entry_in_the_journal()
public function it_stores_an_activity_and_creates_an_entry_in_the_journal()
{
$account = factory(Account::class)->create([]);
$account = factory(Account::class)->create();
$contacts = factory(Contact::class, 3)->create([
'account_id' => $account->id,
]);
$activityType = factory(ActivityType::class)->create([
'account_id' => $account->id,
]);
@@ -30,7 +33,10 @@ class CreateActivityTest extends TestCase
'activity_type_id' => $activityType->id,
'summary' => 'we went to central perk',
'description' => 'it was awesome',
'date' => '2009-09-09',
'happened_at' => '2009-09-09',
'contacts' => $contacts->map(function ($contact) {
return $contact->id;
})->toArray(),
];
$activity = app(CreateActivity::class)->execute($request);
@@ -43,6 +49,14 @@ class CreateActivityTest extends TestCase
'happened_at' => '2009-09-09',
]);
foreach ($contacts as $contact) {
$this->assertDatabaseHas('activity_contact', [
'account_id' => $account->id,
'activity_id' => $activity->id,
'contact_id' => $contact->id,
]);
}
$this->assertInstanceOf(
Activity::class,
$activity
@@ -58,7 +72,10 @@ class CreateActivityTest extends TestCase
/** @test */
public function it_adds_emotions()
{
$contact = factory(Contact::class)->create([]);
$account = factory(Account::class)->create();
$contact = factory(Contact::class)->create([
'account_id' => $account->id,
]);
$emotion = factory(Emotion::class)->create([]);
$emotion2 = factory(Emotion::class)->create([]);
@@ -67,28 +84,29 @@ class CreateActivityTest extends TestCase
array_push($emotionArray, $emotion2->id);
$activityType = factory(ActivityType::class)->create([
'account_id' => $contact->account_id,
'account_id' => $account->id,
]);
$request = [
'account_id' => $contact->account_id,
'account_id' => $account->id,
'activity_type_id' => $activityType->id,
'summary' => 'we went to central perk',
'description' => 'it was awesome',
'date' => '2009-09-09',
'happened_at' => '2009-09-09',
'emotions' => $emotionArray,
'contacts' => [$contact->id],
];
$activity = app(CreateActivity::class)->execute($request);
$this->assertDatabaseHas('emotion_activity', [
'account_id' => $contact->account_id,
'account_id' => $account->id,
'activity_id' => $activity->id,
'emotion_id' => $emotion->id,
]);
$this->assertDatabaseHas('emotion_activity', [
'account_id' => $contact->account_id,
'account_id' => $account->id,
'activity_id' => $activity->id,
'emotion_id' => $emotion2->id,
]);
@@ -112,13 +130,17 @@ class CreateActivityTest extends TestCase
{
$account = factory(Account::class)->create([]);
$activityType = factory(ActivityType::class)->create([]);
$contact = factory(Contact::class)->create([
'account_id' => $account->id,
]);
$request = [
'account_id' => $account->id,
'activity_type_id' => $activityType->id,
'summary' => 'we went to central perk',
'description' => 'it was awesome',
'date' => '2009-09-09',
'happened_at' => '2009-09-09',
'contacts' => [$contact->id],
];
$this->expectException(ModelNotFoundException::class);
@@ -4,6 +4,7 @@ namespace Tests\Unit\Services\Account\Activity;
use Tests\TestCase;
use App\Models\Account\Account;
use App\Models\Contact\Contact;
use App\Models\Account\Activity;
use App\Models\Account\ActivityType;
use Illuminate\Foundation\Testing\DatabaseTransactions;
@@ -42,17 +43,35 @@ class DestroyActivityTest extends TestCase
$activityType = factory(ActivityType::class)->create([
'account_id' => $account->id,
]);
$contact = factory(Contact::class)->create([
'account_id' => $account->id,
]);
$request = [
'account_id' => $account->id,
'activity_type_id' => $activityType->id,
'summary' => 'we went to central perk',
'description' => 'it was awesome',
'date' => '2009-09-09',
'happened_at' => '2009-09-09',
'contacts' => [$contact->id],
];
$activity = app(CreateActivity::class)->execute($request);
$this->assertDatabaseHas('activities', [
'id' => $activity->id,
]);
$this->assertDatabaseHas('activity_contact', [
'activity_id' => $activity->id,
'contact_id' => $contact->id,
]);
$this->assertDatabaseHas('journal_entries', [
'account_id' => $account->id,
'journalable_id' => $activity->id,
'journalable_type' => get_class($activity),
]);
$request = [
'account_id' => $activity->account_id,
'activity_id' => $activity->id,
@@ -62,6 +81,9 @@ class DestroyActivityTest extends TestCase
$this->assertDatabaseMissing('activities', [
'id' => $activity->id,
]);
$this->assertDatabaseMissing('activity_contact', [
'activity_id' => $activity->id,
]);
$this->assertDatabaseMissing('journal_entries', [
'account_id' => $account->id,
@@ -4,6 +4,7 @@ namespace Tests\Unit\Services\Account\Activity;
use Tests\TestCase;
use App\Models\Account\Account;
use App\Models\Contact\Contact;
use App\Models\Account\Activity;
use Illuminate\Validation\ValidationException;
use Illuminate\Foundation\Testing\DatabaseTransactions;
@@ -18,6 +19,9 @@ class UpdateActivityTest extends TestCase
public function it_updates_an_activity()
{
$activity = factory(Activity::class)->create([]);
$contact = factory(Contact::class)->create([
'account_id' => $activity->account_id,
]);
$request = [
'account_id' => $activity->account_id,
@@ -25,7 +29,8 @@ class UpdateActivityTest extends TestCase
'activity_type_id' => $activity->activity_type_id,
'summary' => 'we went to central perk',
'description' => 'it was awesome',
'date' => '2009-09-09',
'happened_at' => '2009-09-09',
'contacts' => [$contact->id],
];
app(UpdateActivity::class)->execute($request);
@@ -43,6 +48,107 @@ class UpdateActivityTest extends TestCase
);
}
/** @test */
public function it_removes_old_associated_contacts()
{
$activity = factory(Activity::class)->create();
$contacts = factory(Contact::class, 3)->create([
'account_id' => $activity->account_id,
]);
foreach ($contacts as $contact) {
$activity->contacts()->syncWithoutDetaching([$contact->id => [
'account_id' => $activity->account_id,
]]);
}
foreach ($contacts as $contact) {
$this->assertDatabaseHas('activity_contact', [
'account_id' => $activity->account_id,
'activity_id' => $activity->id,
'contact_id' => $contact->id,
]);
}
$newContact = factory(Contact::class)->create([
'account_id' => $activity->account_id,
]);
$request = [
'account_id' => $activity->account_id,
'activity_id' => $activity->id,
'activity_type_id' => $activity->activity_type_id,
'summary' => 'we went to central perk',
'description' => 'it was awesome',
'happened_at' => '2009-09-09',
'contacts' => [$newContact->id],
];
app(UpdateActivity::class)->execute($request);
$this->assertDatabaseHas('activity_contact', [
'account_id' => $activity->account_id,
'activity_id' => $activity->id,
'contact_id' => $newContact->id,
]);
foreach ($contacts as $contact) {
$this->assertDatabaseMissing('activity_contact', [
'account_id' => $activity->account_id,
'activity_id' => $activity->id,
'contact_id' => $contact->id,
]);
}
}
/** @test */
public function it_removes_old_associated_contacts_and_keep_previous_one()
{
$activity = factory(Activity::class)->create();
$contacts = factory(Contact::class, 3)->create([
'account_id' => $activity->account_id,
]);
foreach ($contacts as $contact) {
$activity->contacts()->syncWithoutDetaching([$contact->id => [
'account_id' => $activity->account_id,
]]);
}
foreach ($contacts as $contact) {
$this->assertDatabaseHas('activity_contact', [
'account_id' => $activity->account_id,
'activity_id' => $activity->id,
'contact_id' => $contact->id,
]);
}
$request = [
'account_id' => $activity->account_id,
'activity_id' => $activity->id,
'activity_type_id' => $activity->activity_type_id,
'summary' => 'we went to central perk',
'description' => 'it was awesome',
'happened_at' => '2009-09-09',
'contacts' => [$contacts[0]->id, $contacts[1]->id],
];
app(UpdateActivity::class)->execute($request);
$this->assertDatabaseHas('activity_contact', [
'account_id' => $activity->account_id,
'activity_id' => $activity->id,
'contact_id' => $contacts[0]->id,
]);
$this->assertDatabaseHas('activity_contact', [
'account_id' => $activity->account_id,
'activity_id' => $activity->id,
'contact_id' => $contacts[1]->id,
]);
$this->assertDatabaseMissing('activity_contact', [
'account_id' => $activity->account_id,
'activity_id' => $activity->id,
'contact_id' => $contacts[2]->id,
]);
}
/** @test */
public function it_fails_if_wrong_parameters_are_given()
{
@@ -53,7 +159,7 @@ class UpdateActivityTest extends TestCase
'activity_type_id' => $activity->activity_type_id,
'summary' => 'we went to central perk',
'description' => 'it was awesome',
'date' => '2009-09-09',
'happened_at' => '2009-09-09',
];
$this->expectException(ValidationException::class);
@@ -65,6 +171,9 @@ class UpdateActivityTest extends TestCase
{
$activity = factory(Activity::class)->create([]);
$account = factory(Account::class)->create([]);
$contact = factory(Contact::class)->create([
'account_id' => $activity->account_id,
]);
$request = [
'account_id' => $account->id,
@@ -72,7 +181,8 @@ class UpdateActivityTest extends TestCase
'activity_type_id' => $activity->activity_type_id,
'summary' => 'we went to central perk',
'description' => 'it was awesome',
'date' => '2009-09-09',
'happened_at' => '2009-09-09',
'contacts' => [$contact->id],
];
$this->expectException(ModelNotFoundException::class);
@@ -19,11 +19,8 @@ class ResetAccountTest extends TestCase
public function it_resets_an_account()
{
// populate the account with fake contacts and activities
$user = factory(User::class)->create([]);
factory(Contact::class, 3)->create([
'account_id' => $user->account_id,
]);
factory(Contact::class, 3)->create([
$user = factory(User::class)->create();
$contacts = factory(Contact::class, 3)->create([
'account_id' => $user->account_id,
]);
@@ -36,7 +33,10 @@ class ResetAccountTest extends TestCase
'activity_type_id' => $activityType->id,
'summary' => 'we went to central perk',
'description' => 'it was awesome',
'date' => '2009-09-09',
'happened_at' => '2009-09-09',
'contacts' => $contacts->map(function ($contact) {
return $contact->id;
})->toArray(),
];
app(CreateActivity::class)->execute($request);