diff --git a/app/Http/Controllers/ServiceController.php b/app/Http/Controllers/ServiceController.php index 48448ee..5db2907 100644 --- a/app/Http/Controllers/ServiceController.php +++ b/app/Http/Controllers/ServiceController.php @@ -2,21 +2,211 @@ namespace App\Http\Controllers; +use Carbon\Carbon; +use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; use Illuminate\Support\Arr; use Illuminate\Support\Facades\Auth; +use Illuminate\Support\Facades\Log; +use Illuminate\Support\Facades\Mail; use Illuminate\View\View; +use Symfony\Component\HttpKernel\Exception\HttpException; +use App\Mail\{CancelRequest,ChangeRequest}; use App\Models\Service; class ServiceController extends Controller { + /* SERVICE WORKFLOW METHODS */ + + /** + * Cancel a request to cancel a service + * + * @param Service $o + * @return bool + */ + private function action_cancel_cancel(Service $o): bool + { + if (! $o->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(); + } + + /** + * 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(); + + $o->order_info->put('change_cancel',Carbon::now()->format('Y-m-d H:i:s')); + $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 */ + + /** + * 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([ + 'date_end'=>'required|date', + ]); + + if (! $o->order_info) + $o->order_info = collect(); + + $o->date_end = $request->date_end; + $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('u.service.cancel_request') + ->with('o',$o); + } + + /** + * Change the status of a service + * + * @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; + $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([ + 'change_date'=>'required|date', + 'notes'=>'required|min:10', + ]); + + if (! $o->order_info) + $o->order_info = collect(); + + $o->order_info->put('change_note',$request->notes); + $o->order_info->put('change_date',$request->change_date); + $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('u.service.change_request') + ->with('o',$o); + } + } + /** * Edit a domain service details * * @param Request $request * @param Service $o - * @return \Illuminate\Http\RedirectResponse + * @return RedirectResponse + * @todo revalidate */ public function domain_edit(Request $request,Service $o) { @@ -49,6 +239,7 @@ class ServiceController extends Controller * List all the domains managed by the user * * @return View + * @todo revalidate */ public function domain_list(): View { @@ -62,95 +253,4 @@ class ServiceController extends Controller return view('r.service.domain.list') ->with('o',$o); } - - /** - * Update a service - * - * @note: Route Middleware protects this path - * @param Request $request - * @param Service $o - * @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View|\Illuminate\Http\RedirectResponse - */ - public function update(Request $request,Service $o) - { - switch ($o->order_status) { - case 'CANCEL-REQUEST': - if ($request->post()) { - if (! $request->post('date_end')) - return redirect()->back()->withErrors('Cancellation Date not provided'); - - $o->date_end = $request->post('date_end'); - - foreach (['cancel_notes'] as $key) { - if ($request->post($key)) - $o->setOrderInfo($key,$request->post($key)); - } - - $o->order_status='CANCEL-PENDING'; - $o->save(); - - return redirect()->to(url('u/service',$o->id))->with('updated','Service cancellation submitted.'); - } - - return $this->update_request_cancel($o); - - case 'ORDER-SENT': - if ($request->post()) { - foreach (['reference','notes'] as $key) { - $o->setOrderInfo($key,$request->post($key)); - } - - $o->save(); - - foreach ($request->post($o->stype) as $k=>$v) { - $o->type->{$k} = $v; - } - - $o->type->save(); - - return redirect()->to(url('u/service',$o->id))->with('updated','Order sent notes updated.'); - } - - return $this->update_order_status($o); - - case 'PROVISION-PLANNED': - if ($request->post()) { - foreach (['provision_notes'] as $key) { - $o->setOrderInfo($key,$request->post($key)); - } - - $o->date_start = $request->post('date_start'); - - $o->save(); - - foreach ($request->post($o->stype) as $k=>$v) { - $o->type->{$k} = $v; - } - - $o->type->save(); - - return redirect()->to(url('u/service',$o->id))->with('updated','Order sent notes updated.'); - } - - return $this->update_provision_planned($o); - - default: - abort(499,'Not yet implemented'); - } - } - - private function update_order_status(Service $o) - { - return View('r.service.order.sent',['o'=>$o]); - } - - private function update_request_cancel(Service $o) - { - return View('u.service.order.cancel',['o'=>$o]); - } - - private function update_provision_planned(Service $o) - { - return View('r.service.order.provision_plan',['o'=>$o]); - } } \ No newline at end of file diff --git a/app/Mail/CancelRequest.php b/app/Mail/CancelRequest.php new file mode 100644 index 0000000..a0f63a2 --- /dev/null +++ b/app/Mail/CancelRequest.php @@ -0,0 +1,56 @@ +service = $o; + $this->notes = $notes; + } + + /** + * Build the message. + * + * @return $this + */ + public function build() + { + switch (get_class($this->service->type)) { + case 'App\Models\Service\Adsl': + $subject = sprintf('CANCEL NBN: %s',$this->service->type->service_address); + break; + + case 'App\Models\Service\Voip': + $subject = sprintf('CANCEL VOIP: %s',$this->service->type->service_number); + break; + + default: + $subject = 'Cancel Service Request'; + } + + return $this + ->markdown('email.admin.service.cancel') + ->subject($subject) + ->with(['site'=>$this->service->site]); + } +} \ No newline at end of file diff --git a/app/Mail/ChangeRequest.php b/app/Mail/ChangeRequest.php new file mode 100644 index 0000000..25cf2c5 --- /dev/null +++ b/app/Mail/ChangeRequest.php @@ -0,0 +1,56 @@ +service = $o; + $this->notes = $notes; + } + + /** + * Build the message. + * + * @return $this + */ + public function build() + { + switch (get_class($this->service->type)) { + case 'App\Models\Service\Adsl': + $subject = sprintf('Change NBN: %s',$this->service->type->service_address); + break; + + case 'App\Models\Service\Voip': + $subject = sprintf('Change VOIP: %s',$this->service->type->service_number); + break; + + default: + $subject = 'Change Service Request'; + } + + return $this + ->markdown('email.admin.service.change') + ->subject($subject) + ->with(['site'=>$this->service->site]); + } +} \ No newline at end of file diff --git a/app/Mail/OrderRequest.php b/app/Mail/OrderRequest.php index 13dcd54..29ac5ac 100644 --- a/app/Mail/OrderRequest.php +++ b/app/Mail/OrderRequest.php @@ -3,9 +3,9 @@ namespace App\Mail; use Illuminate\Bus\Queueable; +use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Mail\Mailable; use Illuminate\Queue\SerializesModels; -use Illuminate\Contracts\Queue\ShouldQueue; use App\Models\Service; @@ -13,8 +13,8 @@ class OrderRequest extends Mailable { use Queueable, SerializesModels; - public $service; - public $notes; + public Service $service; + public string $notes; /** * Create a new message instance. @@ -22,7 +22,7 @@ class OrderRequest extends Mailable * @param Service $o * @param string $notes */ - public function __construct(Service $o,$notes='') + public function __construct(Service $o,string $notes='') { $this->service = $o; $this->notes = $notes; @@ -35,8 +35,7 @@ class OrderRequest extends Mailable */ public function build() { - switch (get_class($this->service->type)) - { + switch (get_class($this->service->type)) { case 'App\Models\Service\Adsl': $subject = sprintf('NBN: %s',$this->service->type->service_address); break; diff --git a/app/Models/Service.php b/app/Models/Service.php index ed0a042..563c75c 100644 --- a/app/Models/Service.php +++ b/app/Models/Service.php @@ -3,6 +3,7 @@ namespace App\Models; use Exception; +use Illuminate\Database\Eloquent\Casts\AsCollection; use Illuminate\Database\Eloquent\Collection as DatabaseCollection; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; @@ -62,7 +63,7 @@ class Service extends Model implements IDs ]; protected $casts = [ - 'order_info'=>'collection', + 'order_info'=>AsCollection::class, ]; public $dateFormat = 'U'; @@ -106,16 +107,30 @@ class Service extends Model implements IDs /** * Valid status shows the applicable next status for an action on a service * Each status can be - * + Approved, to proceed to the next valid status' - * + Held, to a holding pattern status - * + Rejected, reverted to an different status - * + Cancel, to progress down a decomission route - * + Updated, stay on the current status with new information + * + * The structure of each item is: + * => [ + * 'next' == next possible levels, initiated by who (array). + * who = 'role','system' + * 'enter_method' == the method run when we enter this stage - could be used to send email for example, if this method doesnt complete successfully, we dont enter this stage + * + If it returns NULL, An error condition + * + If it returns FALSE, the service cannot enter this stage + * + If it returns TRUE, the service has successfully changed to this stage + * + If it returns a VIEW/REDIRECT, that resulting page will handle the status change + * 'exit_method' == the method that determines that the order can leave this status (true = can, false = cant) + * + If it returns NULL, An error condition + * + If it returns FALSE, the service CANNOT leave this stage + * + If it returns TRUE, the service CAN leave this stage + * it will receive the next method as an argument + * 'title' == title shown on the menu, for the user to choose + * ] + * So when an order goes to the next state, the exit method will be tested immediately (if there is only 1 exit method for the user) + * to see if it can proceed further if not, it'll wait here for user/admin intervention * * @var array */ - public static $action_progress = [ - // Order Submitted + private const ACTION_PROGRESS = [ + // Order Submitted @todo redo 'ORDER-SUBMIT' => [ 'fail'=>FALSE, // Progress to next stages by who @@ -128,7 +143,7 @@ class Service extends Model implements IDs 'method'=>'action_order_submit', 'title'=>'Order Submit', ], - // Client accepts order, if performed by RW + // Client accepts order, if performed by RW @todo redo 'ORDER-ACCEPT' => [ 'fail'=>FALSE, 'next'=>[ @@ -138,7 +153,7 @@ class Service extends Model implements IDs 'method'=>'action_order_accept', 'title'=>'Client Accept Order', ], - // If the product has a setup, collect payment information + // If the product has a setup, collect payment information @todo redo 'SETUP-PAYMENT-WAIT' => [ 'fail'=>FALSE, 'next'=>[ @@ -148,6 +163,7 @@ class Service extends Model implements IDs 'method'=>'action_setup_payment_wait', 'title'=>'Setup Payment', ], + // @todo redo 'PAYMENT-WAIT' => [ 'fail'=>FALSE, 'next'=>[ @@ -157,6 +173,7 @@ class Service extends Model implements IDs 'method'=>'action_payment_wait', 'title'=>'Service Payment', ], + // @todo redo 'PAYMENT-CHECK' => [ 'fail'=>'ORDER-HOLD', 'next'=>[ @@ -166,13 +183,13 @@ class Service extends Model implements IDs 'method'=>'action_payment_check', 'title'=>'Validate Payment Method', ], - // Order On Hold (Reason) + // Order On Hold (Reason) @todo redo 'ORDER-HOLD' => ['release'=>'ORDER-SUBMIT','update_reference'=>'ORDER-SENT'], - // Order Rejected (Reason) + // Order Rejected (Reason) @todo redo 'ORDER-REJECTED' => [], - // Order Cancelled + // Order Cancelled @todo redo 'ORDER-CANCELLED' => [], - // Order Sent to Supplier + // Order Sent to Supplier @todo redo 'ORDER-SENT' => [ 'fail'=>'ORDER-HOLD', 'next'=>[ @@ -182,7 +199,7 @@ class Service extends Model implements IDs 'method'=>'action_order_sent', 'title'=>'Send Order', ], - // Order Confirmed by Supplier + // Order Confirmed by Supplier @todo redo 'ORDERED' => [ 'fail'=>false, 'next'=>[ @@ -194,7 +211,7 @@ class Service extends Model implements IDs 'method'=>'action_ordered', 'title'=>'Service Ordered', ], - // Service confirmed by supplier, optional connection date + // Service confirmed by supplier, optional connection date @todo redo 'PROVISION-PLANNED' => [ 'fail'=>false, 'next'=>[ @@ -204,7 +221,7 @@ class Service extends Model implements IDs 'method'=>'action_provision_planned', 'title'=>'Provision Planned', ], - // Service has been provisioned by supplier + // Service has been provisioned by supplier @todo redo 'PROVISIONED' => [ 'fail'=>false, 'next'=>[ @@ -216,34 +233,56 @@ class Service extends Model implements IDs ], // Service is Active 'ACTIVE' => [ - 'fail'=>FALSE, 'next'=>[ - 'UPGRADE-REQUEST'=>['customer'], 'CANCEL-REQUEST'=>['customer'], + 'CHANGE-REQUEST'=>['customer'], ], - 'system'=>FALSE, - 'method'=>'action_active', + 'exit'=>'action_active_exit', 'title'=>'Service Active', ], // Service to be Upgraded - 'UPGRADE-REQUEST' => [ - 'fail'=>FALSE, + 'CANCEL-CANCEL' => [ 'next'=>[ - 'UPGRADE-PENDING'=>[], + 'ACTIVE'=>['wholesaler'], ], - 'system'=>FALSE, - 'method'=>FALSE, - 'title'=>'Upgrade Service', + 'enter_method'=>'action_cancel_cancel', + 'title'=>'Cancel Cancellation Request', ], // Service to be Cancelled 'CANCEL-REQUEST' => [ - 'fail'=>FALSE, 'next'=>[ - 'CANCEL-PENDING'=>[], + 'CANCEL-CANCEL'=>['wholesaler'], + 'CANCEL-PENDING'=>['wholesaler'], ], - 'system'=>FALSE, - 'method'=>'action_cancel_request', - 'title'=>'Cancel Service', + 'enter_method'=>'action_request_enter_redirect', + 'exit_method'=>'action_cancel_request_exit', + 'title'=>'Cancel Request', + ], + // Service Cancellation being processed + 'CANCEL-PENDING' => [ + 'next'=>[ + 'CANCELLED'=>['wholesaler'], + ], + 'enter_method'=>'action_cancel_pending_enter', + 'exit_method'=>'action_cancel_pending_exit', + 'title'=>'Cancel Pending', + ], + // Service to be Upgraded + 'CHANGE-CANCEL' => [ + 'next'=>[ + 'ACTIVE'=>['wholesaler'], + ], + 'enter_method'=>'action_change_cancel', + 'title'=>'Cancel Change Request', + ], + // Service to be Upgraded + 'CHANGE-REQUEST' => [ + 'next'=>[ + 'CHANGE-PENDING'=>['wholesaler'], + 'CHANGE-CANCEL'=>['wholesaler'], + ], + 'enter_method'=>'action_request_enter_redirect', + 'title'=>'Change Service', ], ]; @@ -906,36 +945,13 @@ class Service extends Model implements IDs $this->attributes['date_last'] = $value->timestamp; } - /* GENERAL METHODS */ - - // The action methods will return: NULL for no progress|FALSE for a failed status|next stage name. - - /** - * Action required before order can leave the ACTIVE status. - * - * @return bool - */ - private function action_active(): ?bool - { - // N/A - return TRUE; - } - - /** - * Request cancellation for an order when status ACTIVE stage. - * This method should have the client confirm/accept the cancellation, if it was placed by a reseller/wholesaler. - * - * @return bool - */ - private function action_cancel_request(): ?bool - { - throw new HttpException(301,url('u/service/cancel',$this->id)); - } + /* METHODS */ /** * Processing when service has been ordered. * * @return bool|null + * @todo Check */ private function action_ordered(): ?bool { @@ -948,6 +964,7 @@ class Service extends Model implements IDs * This method should have the client confirm/accept the order, if it was placed by a reseller/wholesaler. * * @return bool + * @todo Check */ private function action_order_accept(): ?bool { @@ -958,6 +975,8 @@ class Service extends Model implements IDs /** * Action method when status ORDER_SENT * This method redirects to a form, where updating the form will progress to the next stage. + * + * @todo Check */ private function action_order_sent(string $next) { @@ -972,6 +991,7 @@ class Service extends Model implements IDs * Action method when status ORDER_SUBMIT * * @return bool + * @todo Check */ private function action_order_submit(): ?bool { @@ -984,6 +1004,7 @@ class Service extends Model implements IDs * * @param string $next * @return bool + * @todo Check */ private function action_provision_planned(string $next) { @@ -995,6 +1016,7 @@ class Service extends Model implements IDs * This method should collect any setup fees payment. * * @return bool + * @todo Check */ private function action_setup_payment_wait(): ?bool { @@ -1007,6 +1029,7 @@ class Service extends Model implements IDs * This method should validate any payment details. * * @return bool + * @todo Check */ private function action_payment_check(): ?bool { @@ -1019,6 +1042,7 @@ class Service extends Model implements IDs * This method should collect any service payment details. * * @return bool + * @todo Check */ private function action_payment_wait(): ?bool { @@ -1026,145 +1050,78 @@ class Service extends Model implements IDs return TRUE; } + /** + * Work out the next applicable actions for this service status, taking into account the user's role + * + * @notes + * + Clients can only progress 1 step, if they are in the next step. + * + Resellers/Wholesales can progress to the next Reseller/Wholesaler and any steps in between. + * @return Collection + */ + public function actions(): Collection + { + $next = $this->getStageParameters($this->order_status)->get('next'); + return $next + ? $next->map(function($item,$key) { + $authorized = FALSE; + + if ($x=Arr::get(self::ACTION_PROGRESS,$key)) + foreach ($item as $role) { + if ($this->isAuthorised($role)) { + $authorized = TRUE; + break; + } + } + + return $authorized ? $x['title'] : NULL; + })->filter()->sort() + : collect(); + } + private function getOrderInfoValue(string $key): ?string { return $this->order_info ? $this->order_info->get($key) : NULL; } /** - * Get the current stage parameters + * Get the stage parameters * * @param string $stage - * @return array + * @return Collection */ - private function getStageParameters(string $stage): Collection + public function getStageParameters(string $stage): Collection { - $result = collect(Arr::get(self::$action_progress,$stage)); + $result = Arr::get(self::ACTION_PROGRESS,$stage); $myrole = array_search(Auth::user()->role(),User::$role_order); // If we have no valid next stage, return an empty collection. - if (! $result->count() OR $myrole===FALSE) - return $result; + if (($myrole === FALSE) || (! $result)) + return collect(); // Filter the result based on who we are $next = collect(); - foreach ($result->get('next') as $action=>$roles) { + if (array_key_exists('next',$result) && count($result['next'])) { + foreach ($result['next'] as $action => $roles) { + // Can the current user do this role? + $cando = FALSE; - // Can the current user do this role? - $cando = FALSE; - foreach ($roles as $role) { - if ($myrole <= array_search($role,User::$role_order)) { - $cando = TRUE; - break; - } - } + foreach ($roles as $role) { + if ($myrole <= array_search($role,User::$role_order)) { + $cando = TRUE; - if ($cando OR $result->get('system')) { - $next->put($action,$roles); - } - } - - $result->put('next',$next); - - return $result; - } - - /** - * @notes - * + When progressing stages, we know who the user is that initiated the stage, - * + If no user, then we perform stages SYSTEM=TRUE - * + We need to validate that the current stage is complete, before progressing to the next stage - * + The current stage may require input from a user, or automation process to progress - * + Before leaving this method, we update the service with the stage that it is currently on. - * - * @param string $stage - * @return bool|int|string|null - */ - public function action(string $stage) - { - // 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 = $this->getStageParameters($stage); - - // If valid, call the method to confirm that the current stage is complete - if (method_exists($this,$current['method'])) { - try { - $result = $this->{$current['method']}($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()); - } - - // @todo Implement a status message - if (is_null($result)) { - $stage = NULL; - abort(500,'Current Method Cannot Proceed: '.$result); - - // @todo Implement a status message - } elseif (! $result) { - $stage = NULL; - abort(500,'Current Method FAILED: '.$result); - - } else { - $this->order_status = $stage; - $this->save(); - - // If we have more than 1 next step for the next stage, we'll have to end. - if ($this->actions()->count() > 1) { - $stage = NULL; - - } else { - $stage = $this->actions()->keys()->first(); + break; } } - // @todo Implement a status message - } else { - // Cant do anything, dont have a method to check if we can leave - $stage = NULL; - abort(500,'NO Method Cannot Proceed to leave this stage: '.$current['method']); + if ($cando) + $next->put($action,$roles); } - // If valid, call the method to start the next stage - } - } - - /** - * Work out the next applicable actions for this service status - * - * @notes - * + Clients can only progress 1 step, if they are in the next step. - * + Resellers/Wholesales can progress to the next Reseller/Wholesaler and any steps in between. - * - * @param bool $next Only show next actions - * @return Collection - */ - public function actions(): Collection - { - $result = collect(); - $action = $this->getStageParameters($this->order_status); - - if (! $action->count()) - return $result; - - // Next Action - foreach ($this->getStageParameters($this->order_status)->get('next') as $k=>$v) { - $result->put($k,Arr::get(self::$action_progress,$k.'.title')); + $result['next'] = $next; } - // No next actions, that will mean the current action hasnt completed. - if (! $result->count()) - $result->put($this->order_status,Arr::get($action,'title')); - - return $result; + return collect($result); } /** @@ -1209,6 +1166,54 @@ class Service extends Model implements IDs return $this->active OR ($this->order_status AND ! in_array($this->order_status,$this->inactive_status)); } + /** + * Determine if the current user has the role for this service + * + * @param string $role + * @return bool + */ + public function isAuthorised(string $role): bool + { + switch(Auth::user()->role()) { + // Wholesalers are site admins, they can see everything + case 'wholesaler': + return TRUE; + + case 'reseller': + switch ($role) { + case 'wholesaler': + return FALSE; + + // Check service is in the resellers/customers list + case 'reseller': + case 'customer': + dd(['m'=>__METHOD__,'not written']); + + default: + abort(500,'Unknown role for reseller: '.$role); + } + + case 'customer': + switch ($role) { + case 'reseller': + case 'wholesaler': + return FALSE; + + // Check service is in the customers list + case 'customer': + dd(['m'=>__METHOD__,'not written']); + + default: + abort(500,'Unknown role for customer: '.$role); + } + + default: + abort(500,'Unknown user role: ',Auth::user()->role()); + } + + return FALSE; + } + /** * Do we bill for this service * diff --git a/resources/views/email/admin/service/cancel.blade.php b/resources/views/email/admin/service/cancel.blade.php new file mode 100644 index 0000000..9aefe3f --- /dev/null +++ b/resources/views/email/admin/service/cancel.blade.php @@ -0,0 +1,26 @@ +@component('mail::message',['site'=>$site,'heading'=>'Cancel Service Request']) +Please cancel the following... + +@component('mail::table') +| Service | Details | +| :---------- | :---------------- | +| Account | {{ $service->account_name }} ({!! $service->account->account_id_url !!}) | +| Service ID | {!! $service->service_id_url !!} | +| Product | {{ $service->product_name }} | +@switch($service->product_category) +@case('ADSL') +| Address | {{ $service->type->service_address }} | +@break; +@case('VOIP') +| Number | {{ $service->type->service_number }} | +| Supplier Details | {{ $service->order_info->join(':') }} | +@break; +@endswitch +@endcomponent + + +**NOTES:** {{ $notes }} + +Thanks,
+{{ config('app.name') }} +@endcomponent \ No newline at end of file diff --git a/resources/views/email/admin/service/change.blade.php b/resources/views/email/admin/service/change.blade.php new file mode 100644 index 0000000..bbfc5d2 --- /dev/null +++ b/resources/views/email/admin/service/change.blade.php @@ -0,0 +1,26 @@ +@component('mail::message',['site'=>$site,'heading'=>'Cancel Service Request']) +Please change the following... + +@component('mail::table') +| Service | Details | +| :---------- | :---------------- | +| Account | {{ $service->account_name }} ({!! $service->account->account_id_url !!}) | +| Service ID | {!! $service->service_id_url !!} | +| Product | {{ $service->product_name }} | +@switch($service->product_category) +@case('ADSL') +| Address | {{ $service->type->service_address }} | +@break; +@case('VOIP') +| Number | {{ $service->type->service_number }} | +| Supplier Details | {{ $service->order_info->join(':') }} | +@break; +@endswitch +@endcomponent + + +**NOTES:** {{ $notes }} + +Thanks,
+{{ config('app.name') }} +@endcomponent \ No newline at end of file diff --git a/resources/views/theme/backend/adminlte/u/service/order/cancel.blade.php b/resources/views/theme/backend/adminlte/u/service/cancel_request.blade.php similarity index 73% rename from resources/views/theme/backend/adminlte/u/service/order/cancel.blade.php rename to resources/views/theme/backend/adminlte/u/service/cancel_request.blade.php index 3d2b9a3..4988653 100644 --- a/resources/views/theme/backend/adminlte/u/service/order/cancel.blade.php +++ b/resources/views/theme/backend/adminlte/u/service/cancel_request.blade.php @@ -27,7 +27,7 @@ @include('common.service.widget.error') @endif -
+ {{ csrf_field() }}
@@ -48,9 +48,9 @@ --}}
- -
- + +
+
@@ -67,8 +67,6 @@ @endsection @section('page-scripts') - @css('//cdnjs.cloudflare.com/ajax/libs/summernote/0.8.12/summernote-bs4.css','summernote-css') - @js('//cdnjs.cloudflare.com/ajax/libs/summernote/0.8.12/summernote-bs4.js','summernote-js') @js('//cdn.jsdelivr.net/momentjs/latest/moment.min.js','moment-js') @js('//cdn.jsdelivr.net/npm/daterangepicker/daterangepicker.min.js','daterange-js') @css('//cdn.jsdelivr.net/npm/daterangepicker/daterangepicker.css','daterange-css') @@ -83,18 +81,6 @@ format: 'YYYY-MM-DD' } }); - - $('.textarea').summernote({ - minHeight: 350, - toolbar: [ - ['style', ['style']], - ['font', ['bold', 'underline', 'clear']], - ['color', ['color']], - ['para', ['ul', 'ol', 'paragraph']], - ['table', ['table']], - ['view', ['codeview', 'help']], - ], - }); }); @append \ No newline at end of file diff --git a/resources/views/theme/backend/adminlte/u/service/change_request.blade.php b/resources/views/theme/backend/adminlte/u/service/change_request.blade.php new file mode 100644 index 0000000..e6eb816 --- /dev/null +++ b/resources/views/theme/backend/adminlte/u/service/change_request.blade.php @@ -0,0 +1,86 @@ +@extends('adminlte::layouts.app') + +@section('htmlheader_title') + {{ $o->sid }} +@endsection +@section('page_title') + {{ $o->sid }} +@endsection + +@section('contentheader_title') + Service: {{ $o->sid }} {{ $o->product->name }} +@endsection +@section('contentheader_description') + {{ $o->sname }}: {{ $o->sdesc }} +@endsection + +@section('main-content') +
+
+
+
+
Change Service
+
+ + + @if ($errors->any()) + @include('common.service.widget.error') + @endif + + + {{ csrf_field() }} + +
+
+ +
+
+
+ +
+ +
+
+
+ + {{-- + @includeIf('u.service.widgets.'.$o->stype.'.order',['o'=>$o->type]) + --}} + +
+ +
+ +
+
+
+ + + + +
+
+
+@endsection + +@section('page-scripts') + @js('//cdn.jsdelivr.net/momentjs/latest/moment.min.js','moment-js') + @js('//cdn.jsdelivr.net/npm/daterangepicker/daterangepicker.min.js','daterange-js') + @css('//cdn.jsdelivr.net/npm/daterangepicker/daterangepicker.css','daterange-css') + + +@append \ No newline at end of file diff --git a/resources/views/theme/backend/adminlte/u/service/home.blade.php b/resources/views/theme/backend/adminlte/u/service/home.blade.php index c0b4ee0..d8794b1 100644 --- a/resources/views/theme/backend/adminlte/u/service/home.blade.php +++ b/resources/views/theme/backend/adminlte/u/service/home.blade.php @@ -49,14 +49,12 @@ @can('update',$o)