order_info) $o->order_info = collect(); $o->order_info->put('cancel_cancel',Carbon::now()->format('Y-m-d H:i:s')); $o->order_status = 'ACTIVE'; return $o->save(); } private function action_cancel_pending_enter(Service $o): bool { $o->order_status = 'CANCEL-PENDING'; return $o->save(); } private function action_cancelled(Service $o): bool { $o->order_status = 'CANCELLED'; $o->active = FALSE; return $o->save(); } /** * Cancel a request to change a service * * @param Service $o * @return bool */ private function action_change_cancel(Service $o): bool { if (! $o->order_info) $o->order_info = collect(); // @todo add some validation if this doesnt return a result $np = $o->changes()->where('service__change.active',TRUE)->where('complete',FALSE)->get()->pop(); $np->pivot->active = FALSE; $np->pivot->save(); $o->order_status = 'ACTIVE'; return $o->save(); } /** * Action to change a service order_status to another stage * This is a generic function that can redirect the user to a page that is required to completed to enter * the new stage * * @param Service $o * @param string $stage * @return \Illuminate\Contracts\Foundation\Application|RedirectResponse|\Illuminate\Routing\Redirector */ private function action_request_enter_redirect(Service $o,string $stage) { return redirect(sprintf('u/service/%d/%s',$o->id,strtolower($stage))); } /* OTHER METHODS */ public function change_pending(ServiceChangeRequest $request,Service $o) { // @todo add some validation if this doesnt return a result $np = $o->changes()->where('service__change.active',TRUE)->where('complete',FALSE)->get()->pop(); if ($request->post()) { foreach ($this->service_change_charges($request,$o) as $co) $co->save(); $o->product_id = Arr::get($request->broadband,'product_id'); $o->price = Arr::get($request->broadband,'price'); $o->order_status = 'ACTIVE'; $o->save(); $np->pivot->complete = TRUE; $np->pivot->effective_at = Carbon::now(); $np->pivot->save(); return redirect()->to(url('u/service',[$o->id])); } return view('service.change_pending') ->with('breadcrumb',collect()->merge($o->account->breadcrumb)) ->with('o',$o) ->with('np',$np); } /** * Process a request to cancel a service * * @param Request $request * @param Service $o * @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View|RedirectResponse|\Illuminate\Routing\Redirector */ public function cancel_request(Request $request,Service $o) { if ($request->post()) { $request->validate([ 'stop_at'=>'required|date', ]); if (! $o->order_info) $o->order_info = collect(); $o->stop_at = $request->stop_at; $o->order_info->put('cancel_note',$request->notes); $o->order_status = 'CANCEL-REQUEST'; $o->save(); //@todo Get email from DB. Mail::to('help@graytech.net.au') ->queue((new CancelRequest($o,$request->notes))->onQueue('email')); return redirect('u/service/'.$o->id)->with('success','Cancellation lodged'); } return view('service.cancel_request') ->with('o',$o); } /** * Change the status of a service * @todo This needs to be optimized * * @note This route is protected by middleware @see ServicePolicy::progress() * It is assumed that the next stage is valid for the services current stage - validated in ServicePolicy::progress() * @param Service $o * @param string $stage * @return RedirectResponse */ public function change(Service $o,string $stage): RedirectResponse { // While stage has a string value, that indicates the next stage we want to go to // If stage is NULL, the current stage hasnt been completed // If stage is FALSE, then the current stage failed, and may optionally be directed to another stage. while ($stage) { // Check that stage is a valid next action for the user currently performing it //$current = $this->getStageParameters($this->order_status); $next = $o->getStageParameters($stage); // If valid, call the method to confirm that the current stage is complete if ($x=$next->get('enter_method')) { if (! method_exists($this,$x)) abort(500,sprintf('ENTER_METHOD [%s]defined doesnt exist',$x)); Log::debug(sprintf('Running ENTER_METHOD [%s] on Service [%d] to go to stage [%s]',$x,$o->id,$stage)); // @todo Should call exit_method of the current stage first, to be sure we can change try { $result = $this->{$x}($o,$stage); // If we have a form to complete, we need to return with a URL, so we can catch that with an Exception } catch (HttpException $e) { if ($e->getStatusCode() == 301) return ($e->getMessage()); } // An Error Condition if (is_null($result)) return redirect()->to('u/service/'.$o->id); elseif ($result instanceof RedirectResponse) return $result; // The service cannot enter the next stage elseif (! $result) abort(500,'Current Method FAILED: '.$result); } else { $o->order_status = $stage; if ($stage == 'ACTIVE') $o->active = TRUE; $o->save(); } // If valid, call the method to start the next stage $stage = ''; // @todo this is temporary, we havent written the code to automatically jump to the next stage if wecan } return redirect()->to('u/service/'.$o->id); } /** * Process a request to cancel a service * * @param Request $request * @param Service $o * @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View|RedirectResponse|\Illuminate\Routing\Redirector */ public function change_request(Request $request,Service $o) { if ($request->post()) { $request->validate([ 'product_id'=>'required|exists:products,id', 'change_date'=>'required|date', 'notes'=>'nullable|min:10', ]); $o->changes()->attach([$o->id=>[ 'site_id'=> $o->site_id, 'ordered_by' => Auth::id(), 'ordered_at' => Carbon::now(), 'effective_at' => $request->change_date, 'product_id' => $request->product_id, 'notes' => $request->notes, 'active' => TRUE, 'complete' => FALSE, ]]); $o->order_status = 'CHANGE-REQUEST'; $o->save(); //@todo Get email from DB. Mail::to('help@graytech.net.au') ->queue((new ChangeRequest($o,$request->notes))->onQueue('email')); return redirect('u/service/'.$o->id)->with('success','Upgrade requested'); } switch (get_class($o->type)) { default: return view('service.change_request') ->with('breadcrumb',collect()->merge($o->account->breadcrumb)) ->with('o',$o); } } /** * List all the domains managed by the user * * @return View * @todo revalidate */ public function domain_list(): View { $o = Service\Domain::serviceActive() ->serviceUserAuthorised(Auth::user()) ->select('service_domain.*') ->join('services',['services.id'=>'service_domain.service_id']) ->with(['service.account','registrar']) ->get(); return view('service.domain.list') ->with('o',$o); } public function email_list(): View { // @todo Need to add the with path when calculating next_billed and price $o = Service\Email::serviceActive() ->serviceUserAuthorised(Auth::user()) ->select('service_email.*') ->join('services',['services.id'=>'service_email.service_id']) ->with(['service.account','service.product.type.supplied.supplier_detail.supplier','tld']) ->get(); return view('service.email.list') ->with('o',$o); } /** * Return details on the users service * * @param Service $o * @return View */ public function home(Service $o): View { return View('service.home') ->with('breadcrumb',collect()->merge($o->account->breadcrumb)) ->with('o',$o); } public function hosting_list(): View { // @todo Need to add the with path when calculating next_billed and price $o = Service\Host::serviceActive() ->serviceUserAuthorised(Auth::user()) ->select('service_host.*') ->join('services',['services.id'=>'service_host.service_id']) ->with(['service.account','service.product.type.supplied.supplier_detail.supplier','tld']) ->get(); return view('service.host.list') ->with('o',$o); } private function service_change_charges(Request $request,Service $o): Collection { $charges = collect(); $po = Product::findOrFail(Arr::get($request->broadband,'product_id')); $start_at = Carbon::create(Arr::get($request->broadband,'start_at')); // Get the invoiced items covering the start_at date foreach ($o->invoice_items->filter(function($item) use ($start_at) { return ($item->start_at < $start_at) && ($item->stop_at > $start_at) && ($item->item_type === 0); }) as $iio) { // Reverse the original charge $co = new Charge; $co->active = TRUE; $co->service_id = $o->id; $co->account_id = $o->account_id; $co->sweep_type = 6; $co->product_id = $iio->product_id; $co->description = 'Plan Upgrade Adjustment'; $co->user_id = Auth::id(); $co->type = $iio->item_type; $co->start_at = $start_at; $co->stop_at = $iio->stop_at; $co->amount = $iio->price_base; $co->taxable = TRUE; // @todo this should be determined $co->quantity = -1*$start_at->diff($iio->stop_at)->days/$iio->start_at->diff($iio->stop_at)->days; $charges->push($co); // Add the new charge $co = new Charge; $co->active = TRUE; $co->service_id = $o->id; $co->account_id = $o->account_id; $co->sweep_type = 6; $co->product_id = $po->id; $co->description = 'Plan Upgrade Adjustment'; $co->user_id = Auth::id(); $co->type = $iio->item_type; $co->start_at = $start_at; $co->stop_at = $iio->stop_at; $co->amount = Arr::get($request->broadband,'price') ?: $po->base_charge; $co->taxable = TRUE; // @todo this should be determined $co->quantity = $start_at->diff($iio->stop_at)->days/$iio->start_at->diff($iio->stop_at)->days; $charges->push($co); } // Add any fee if (Arr::get($request->broadband,'change_fee')) { $co = new Charge; $co->active = TRUE; $co->service_id = $o->id; $co->account_id = $o->account_id; $co->sweep_type = 6; $co->product_id = $po->id; $co->description = 'Plan Upgrade Fee'; $co->user_id = Auth::id(); $co->type = 3; $co->start_at = $start_at; $co->stop_at = $start_at; $co->amount = Arr::get($request->broadband,'change_fee'); $co->taxable = TRUE; // @todo this should be determined $co->quantity = 1; $charges->push($co); } return $charges; } /** * This is an API method, that works with service change - to return the new charges as a result of changing a service * * @param Request $request * @param Service $o * @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View */ public function service_change_charges_display(Request $request,Service $o) { return view('a.charge.service_change') ->with('charges',$this->service_change_charges($request,$o)); } /** * Update details about a service * * @param Request $request * @param Service $o * @return RedirectResponse * @throws ValidationException */ public function update(Request $request,Service $o) { if ($o->type->validation()) { Session::put('service_update',true); $validator = Validator::make($x=$request->post($o->category),$o->type->validation()); if ($validator->fails()) { return redirect() ->back() ->withErrors($validator) ->withInput(); } $o->type->forceFill($validator->validated()); } elseif ($request->post($o->product->category)) { $o->type->forceFill($request->post($o->product->category)); } $o->type->save(); if ($request->post('invoice_next_at')) $o->invoice_next_at = $request->invoice_next_at; if ($request->post('recur_schedule')) $o->recur_schedule = $request->recur_schedule; $o->suspend_billing = ($request->suspend_billing == 'on'); $o->external_billing = ($request->external_billing == 'on'); // @todo Cant have both price and price_override - show an exception. (price_override shows a discount, whereas price shows the price) $o->price = $request->price ?: NULL; $o->price_override = $request->price_override ?: NULL; // Also update our service start_at date. // @todo We may want to make start_at/stop_at dynamic values calculated by the type records if ($request->post('start_at')) $o->start_at = $request->start_at; else { // For broadband, start_at is connect_at in the type record switch ($o->category) { case 'broadband': $o->start_at = $o->type->connect_at; break; } } $o->save(); return redirect()->back()->with('success','Record Updated'); } }