HEX
Server: Microsoft-IIS/8.5
System: Windows NT YDAWBH120 6.3 build 9600 (Windows Server 2012 R2 Standard Edition) AMD64
User: tentjecom_web (0)
PHP: 7.4.14
Disabled: NONE
Upload Files
File: D:/HostingSpaces/SBogers10/netwerkbrabant.komma.pro/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 . '.');
    }

}