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('theme.backend.adminlte.service.change_pending') ->with('breadcrumb',collect()->merge($o->account->breadcrumb)) ->with('o',$o) ->with('np',$np); } /** * Process a request to cancel a service * * @param ServiceCancel $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(ServiceCancel $request,Service $o) { if (! $o->order_info) $o->order_info = collect(); $o->stop_at = $request->stop_at; $o->order_info->put('cancel_note',$request->validated('notes')); if ($request->validated('extra_charges')) $o->order_info->put('cancel_extra_charges_accepted',$request->extra_charges_amount); $o->order_status = 'CANCEL-REQUEST'; $o->save(); Mail::to(config('osb.ticket_admin')) ->queue((new CancelRequest($o,$request->notes))->onQueue('email')); return redirect('u/service/'.$o->id) ->with('success','Cancellation lodged'); } /** * 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(); Mail::to(config('osb.ticket_admin')) ->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('theme.backend.adminlte.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() ->AccountUserAuthorised('services') ->select('service_domain.*') ->join('services',['services.id'=>'service_domain.service_id']) ->with(['service.account','registrar']) ->get(); return view('theme.backend.adminlte.service.domain.list') ->with('o',$o); } public function email_list(): View { $o = Service\Email::ServiceActive() ->AccountUserAuthorised('services') ->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('theme.backend.adminlte.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('theme.backend.adminlte.service.home') ->with('breadcrumb',collect()->merge($o->account->breadcrumb)) ->with('o',$o); } public function hosting_list(): View { $o = Service\Host::ServiceActive() ->AccountUserAuthorised('services') ->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('theme.backend.adminlte.service.hosting.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->invoiced_items ->filter(fn($item)=>($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->diffInDays($iio->stop_at)/$iio->start_at->diffInDays($iio->stop_at); $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->diffInDays($iio->stop_at)/$iio->start_at->diffInDays($iio->stop_at); $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('theme.backend.adminlte.service.change_charge') ->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) { Session::put('service_update',true); // We dynamically create our validation $validator = Validator::make( $request->post(), $x=collect($o->type->validation()) ->keys() ->transform(fn($item)=>sprintf('%s.%s',$o->product->category,$item)) ->combine(array_values($o->type->validation())) ->transform(fn($item)=>is_string($item) ? preg_replace('/^exclude_without:/',sprintf('exclude_without:%s.',$o->product->category),$item) : $item) ->merge( [ 'external_billing' => 'nullable|in:on', 'suspend_billing' => 'nullable|in:on', 'recur_schedule' => ['required',Rule::in(collect(Invoice::billing_periods)->keys())], 'invoice_next_at' => 'nullable|date', 'price' => 'nullable|numeric', $o->product->category => 'array|min:1', ] ) ->toArray() ); if ($validator->fails()) { return redirect() ->back() ->withErrors($validator) ->withInput(); } $validated = collect($validator->validated()); // Store our service type values $o->type->forceFill($validated->get($o->product->category)); // Some special handling switch ($o->product->category) { case 'broadband': // If pppoe is not set, then we dont need username/password $o->type->pppoe = ($x=data_get($validated,$o->product->category.'.pppoe',FALSE)); if (! $x) { $o->type->service_username = NULL; $o->type->service_password = NULL; } break; } $o->type->save(); if ($validated->has('invoice_next_at')) $o->invoice_next_at = $validated?->get('invoice_next_at'); if ($validated->has('recur_schedule')) $o->recur_schedule = $validated->get('recur_schedule'); $o->suspend_billing = ($validated->get('suspend_billing') == 'on'); $o->external_billing = ($validated->get('external_billing') == 'on'); $o->price = $validated->get('price'); // 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 ($validated->has('start_at')) $o->start_at = $validated->get('start_at'); else { // For broadband, start_at is connect_at in the type record switch ($o->product->category) { case 'broadband': $o->start_at = $o->type->connect_at; break; } } $o->save(); return redirect() ->back() ->with('success','Record Updated'); } }