File: D:/HostingSpaces/netwerkbrabant/netwerkbrabant.nl/app/KommaApp/Courses/CourseService.php
<?php
namespace App\KommaApp\Courses;
use App\KommaApp\Base\Service;
use App\KommaApp\Courses\Models\Course;
use App\KommaApp\CourseSignUps\CourseSignUpService;
use App\KommaApp\CourseSignUps\Models\CourseSignUp;
use App\KommaApp\CourseTypes\Models\CourseType;
use App\KommaApp\Orders\OrderService;
use App\KommaApp\Regions\Models\Region;
use App\KommaApp\WeFact\WeFactAPI;
use App\KommaApp\WeFact\WeFactService;
use App\Mail\CourseSignUpMail;
use Carbon\Carbon;
use Illuminate\Support\Facades\Log;
use Spatie\CalendarLinks\Link;
class CourseService extends Service
{
const PER_PAGE = 6;
const HIGHLIGHTED_EVENTS = 3;
private $today;
public function __construct()
{
$this->today = Carbon::now()->startOfDay();
$this->today = $this->today->format('Y-m-d H:i:s');
parent::__construct();
}
/**
* The main query for getting courses
*
* @return \Illuminate\Database\Eloquent\Relations\HasMany|\Illuminate\Database\Query\Builder
*/
private function courseQuery()
{
return $this->site
->courses()
->where('active', 1)
->where('date', '>=', $this->today)
->orderBy('date', 'asc')
->orderBy('created_at', 'asc');
}
/**
* Load the relations after or during query building
*
* @param $courses
* @param bool $queryBuilder
*/
private function loadRelations(&$courses, $queryBuilder = false)
{
if ($queryBuilder) $courses->with('translation', 'images', 'type', 'type.translation');
else $courses->load('translation', 'images', 'type', 'type.translation');
}
/**
* Get a specific course by id
*
* @param $courseId
* @return \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Relations\HasMany|\Illuminate\Database\Query\Builder|null|object
*/
public function getCourse($courseId)
{
$course = $this->courseQuery();
$course = $course->where('id', $courseId);
$this->loadRelations($course, true);
return $course->first();
}
/**
* Get the courses which are active and bound to a region and are newer then today ordered descending by date
* with optional parameter to paginate, load the relations, exclude given ids and get a given amount
*
* @param array|null $excludeIds
* @param array|null $filters
* @return \Illuminate\Contracts\Pagination\LengthAwarePaginator|\Illuminate\Database\Eloquent\Collection|\Illuminate\Support\Collection
*/
public function getPaginatedCourses(array $excludeIds = null, array $filters = null)
{
$courses = $this->courseQuery();
$this->loadRelations($courses, true);
if ($excludeIds) $courses = $courses->whereNotIn('id', $excludeIds);
if($filters) {
foreach ($filters as $key => $value) {
$courses = $courses->where($key, '=', $value);
}
}
return $courses->paginate(self::PER_PAGE);
}
/**
* Get the courses which are active and bound to a region and are newer then today ordered descending by date
* with optional parameter to paginate, load the relations, exclude given ids and get a given amount
*
* @param string $courseType
* @param bool $pagination
* @param bool $loadRelations
* @param array|null $excludeIds
* @param int|null $amount
* @return \Illuminate\Contracts\Pagination\LengthAwarePaginator|\Illuminate\Database\Eloquent\Collection|\Illuminate\Support\Collection
*/
public function getCoursesByType(string $courseType, bool $pagination = false, bool $loadRelations = false, array $excludeIds = null, int $amount = null)
{
$courses = $this->courseQuery();
if ($loadRelations) $this->loadRelations($courses, true);
if ($excludeIds) $courses = $courses->whereNotIn('id', $excludeIds);
// Get the courses paginated or by get with optional a given amount
if ($pagination) {
$courses = $courses->where('course_type_id', $courseType)->paginate(self::PER_PAGE);
} else {
if ($amount) $courses = $courses->where('course_type_id', $courseType)->take($amount);
$courses = $courses->get();
}
return $courses;
}
/**
* Get the highlighted courses
* This can be based upon the bind company region of the user / or as guest the most upcoming courses
*
* @return \Illuminate\Database\Eloquent\Collection|\Illuminate\Support\Collection
*/
public function getHighlightedCoursesByType(CourseType $courseType)
{
$courses = $this->courseQuery()
->where('course_type_id', $courseType->id);
$this->loadRelations($courses, true);
return $courses->take(3)->get();
}
/**
* Get the highlighted events
* This can be based upon the bind company region of the user / or as guest the most upcoming events
*
* @return \Illuminate\Database\Eloquent\Collection|\Illuminate\Support\Collection
*/
public function getHighlightedCourses()
{
$courses = $this->courseQuery();
$this->loadRelations($courses, true);
return $courses->take(3)->get();
}
/**
* Get the next course after the given one
* first try to get the next course in region else just the next one
* and if there are non a course that hasn't happend yet...
*
* @param Course $course
* @return \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Relations\HasMany|\Illuminate\Database\Query\Builder|null|object
*/
public function getNextCourses(Course $course)
{
$nextOfType = $this->courseQuery()
->where('date', '>=', $course->date)
->where('id', '!=', $course->id)
->where('course_type_id', $course->course_type_id)
->with('translation', 'images', 'type', 'type.translation')
->first();
$nextCoursesQuery = $this->courseQuery()
->where('date', '>=', $course->date)
->where('id', '!=', $course->id)
->with('translation', 'images', 'type', 'type.translation');
// When no of type, return the take of query
if(!$nextOfType) return $nextCoursesQuery->take(3)->get();
$nextCourses = $nextCoursesQuery->where('id', '!=', $nextOfType->id)
->take(2)
->get();
return collect([$nextOfType])->merge($nextCourses);
}
/**
* Create a calendar link
*
* @param Course $course
* @return Link
* @throws \Spatie\CalendarLinks\Exceptions\InvalidLink
*/
public function createCalendarEvent(Course $course)
{
$addressString = '';
foreach(['location_name', 'location_address', 'location_city'] as $locationPart)
{
if(empty($course->{$locationPart})) continue;
$addressString .= $course->{$locationPart}. ' ';
}
// Create a calendar link
if (isset($course->calendar_start_time) && $course->calendar_start_time != '' && isset($course->calendar_end_time) && $course->calendar_end_time != '') {
$startTime = $course->date->setTimeFromTimeString($course->calendar_start_time);
$endTime = $course->date->setTimeFromTimeString($course->calendar_end_time);
$calenderCourse = Link::create( $course->translation->name, $startTime, $endTime)
->description($course->translation->sub_title);
}
else {
$calenderCourse = Link::create( $course->translation->name, $course->date->startOfDay(), $course->date->endOfDay())
->description($course->translation->sub_title);
}
if($addressString != '') $calenderCourse->address($addressString);
return $calenderCourse;
}
public function convertRequest(array $formValues)
{
/** @var CourseSignUpService $courseSignUpService */
$courseSignUpService = \App::make(CourseSignUpService::class);
$course = Course::find(array_pull($formValues, 'course'))
->load('translation')
->load('type');
$courseProduct = (object)[
'productable_type' => Course::class,
'productable_id' => $course->id,
'name' => $course->translation->name,
// Calculated the price without vat by the Course type Vat and ceil to whole integer
'price' => round($course->price_amount / (($course->type->vat / 100) + 1)),
'vat' => $course->type->vat,
'price_including_vat' => $course->price_amount,
];
// Create signee array and add the main signee
$signees = [
'main' => $courseSignUpService->createSignee($formValues),
];
// Check if there is an other invoice mail address
if (isset($formValues['other_invoice']) && $formValues['other_invoice'] == 'on'){
$signees['main']->invoice_email = array_pull($formValues, 'other_invoice_email');
}
// Else populate it from the email value
else{
$signees['main']->invoice_email = $signees['main']->email;
}
array_forget($formValues, ['other_invoice', 'other_invoice_email']);
// Check if the (main) signee has filled a reference number
if(!empty($formValues['invoice_reference'])) {
$signees['main']->reference = array_pull($formValues, 'invoice_reference');
}
array_forget($formValues, ['invoice_reference']);
// Check if we need to get the plus one else remove the keys
if (isset($formValues['plus_one']) && $formValues['plus_one'] == 'on') {
$signees['plusOne'] = $courseSignUpService->createSignee($formValues, 'plus_one_');
$signees['plusOne']->invoice_email = $signees['plusOne']->email;
}
else {
foreach ($courseSignUpService::SigneeFields as $signeeField)
array_forget($formValues, 'plus_one_' . $signeeField);
}
array_forget($formValues, 'plus_one');
return [$signees, $courseProduct, $course];
}
/**
* Save signee as course sign ups through the course model
*
* @param $signee
* @param $mainSignee
* @param Course $course
* @param int|null $invoiceId
* @return CourseSignUp
*/
public function addSigneeToCourseSignUps($signee, $mainSignee, Course $course, int $invoiceId = null): CourseSignUp
{
/** @var CourseSignUpService $courseSignUpService */
$courseSignUpService = \App::make(CourseSignUpService::class);
return $courseSignUpService->addSigneeToCourseSignUps($signee, $mainSignee, $course, $invoiceId);
}
/**
* Get the order status and belonging course through order id
*
* @param $orderId
* @return array
*/
public function getCourseOrderStatus($orderId)
{
// Get the order
$orderService = \App::make(OrderService::class);
if(!$order = $orderService->getOrder($orderId)) \App::abort(404);
// Get the belonging course product
$orderProduct = $order->products()->first();
// Get the course from the course product
$course = $orderProduct->productable;
$course = $course->load('translation');
return [$order, $course];
}
/**
* Sync a course with WeFact
*
* @param Course $course
*/
public function syncCourse(Course $course)
{
if(empty($course->wefact_code)) {
throw new \InvalidArgumentException('Course (id:' . $course->id .') did not have a wefact_code, and therefor could not sync.');
}
/** @var WeFactService $weFactService */
$weFactService = app()->make(WeFactService::class);
$weFactProduct = $weFactService->getProduct($course->wefact_code);
$weFactProductModifiedDate = Carbon::createFromFormat(Carbon::DEFAULT_TO_STRING_FORMAT, $weFactProduct->Modified);
// WeFact product and Course are up-to-date
if($weFactProductModifiedDate == $course->wefact_modified_at) {
Log::info(self::class.': Sync - WeFact product ' . $course->wefact_code . ' and course ' . $course->id . ' are up-to-date.');
return;
}
// WeFact is newer, therefor sync WeFact to Product
elseif($weFactProductModifiedDate > $course->wefact_modified_at){
$this->syncWeFactProductToCourse($course, $weFactProduct, $weFactProductModifiedDate);
return;
}
}
/**
* Sync all relevant courses from WeFact.
* Note; Rather then loading each course and the belonging WeFact Product individual, we load the product list and then compare it with found courses in our database
*
* @return int
*/
public function syncAllRelevantCourses()
{
/** @var WeFactService $weFactService */
$weFactService = app()->make(WeFactService::class);
$weFactProducts = collect($weFactService->getCourseProducts());
$courses = Course::where('date', '>=', $this->today)
->whereIn('wefact_code', $weFactProducts->pluck('ProductCode')->toArray())
->get();
// Track for logging how many course are synced
$courseSynced = 0;
foreach ($courses as $course) {
$weFactProduct = (object)$weFactProducts->where('ProductCode', '=', $course->wefact_code)->first();
$weFactProductModifiedDate = Carbon::createFromFormat(Carbon::DEFAULT_TO_STRING_FORMAT, $weFactProduct->Modified);
// If the WeFact date is the same continue
if($weFactProductModifiedDate == $course->wefact_modified_at) continue;
$courseSynced++;
$this->syncWeFactProductToCourse($course, $weFactProduct, $weFactProductModifiedDate);
}
return $courseSynced;
}
/**
* Save WeFact product into our belonging course
*
* @param Course $course
* @param $weFactProduct
* @param Carbon $weFactProductModifiedDate
*/
private function syncWeFactProductToCourse(Course $course, $weFactProduct, Carbon $weFactProductModifiedDate)
{
$course->price_amount = WeFactAPI::convertPriceExclToIncl($weFactProduct->PriceExcl, $weFactProduct->TaxPercentage);;
$course->price = '€ ' . number_format(($course->price_amount / 100), 2, ',', '.');
$course->wefact_name = $weFactProduct->ProductName;
$course->wefact_description = $weFactProduct->ProductKeyPhrase;
$course->wefact_modified_at = $weFactProductModifiedDate;
$course->save();
Log::info(self::class.': Sync - Course ' . $course->id . ' has been synced with WeFact product ' . $course->wefact_code . '.');
}
}