546 lines
15 KiB
PHP
546 lines
15 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers;
|
|
|
|
use Carbon\Carbon;
|
|
use Illuminate\Http\RedirectResponse;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Support\Arr;
|
|
use Illuminate\Support\Collection;
|
|
use Illuminate\Support\Facades\Auth;
|
|
use Illuminate\Support\Facades\Gate;
|
|
use Illuminate\Support\Facades\Log;
|
|
use Illuminate\Support\Facades\Mail;
|
|
use Illuminate\Support\Facades\Session;
|
|
use Illuminate\Support\Facades\Validator;
|
|
use Illuminate\Validation\Rule;
|
|
use Illuminate\Validation\ValidationException;
|
|
use Illuminate\View\View;
|
|
use Symfony\Component\HttpKernel\Exception\HttpException;
|
|
|
|
use App\Http\Requests\{ChargeAdd,ServiceChangeRequest};
|
|
use App\Mail\{CancelRequest,ChangeRequest};
|
|
use App\Models\{Charge,Invoice,Product,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();
|
|
}
|
|
|
|
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 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('theme.backend.adminlte.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('theme.backend.adminlte.service.change_request')
|
|
->with('breadcrumb',collect()->merge($o->account->breadcrumb))
|
|
->with('o',$o);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Add a charge to a service/account
|
|
*
|
|
* @param ChargeAdd $request
|
|
* @return RedirectResponse
|
|
*/
|
|
public function charge_addedit(ChargeAdd $request): RedirectResponse
|
|
{
|
|
$o = Charge::findOrNew(Arr::get($request->validated(),'id'));
|
|
|
|
// Dont update processed charges
|
|
if ($o->processed)
|
|
abort(403);
|
|
|
|
$o->forceFill(array_merge(Arr::except($request->validated(),['id']),['active'=>TRUE]));
|
|
$o->save();
|
|
|
|
return redirect()
|
|
->back()
|
|
->with('success',sprintf('Charge %s #%d',$o->wasRecentlyCreated ? 'Created' : 'Updated',$o->id));
|
|
}
|
|
|
|
/**
|
|
* Add a charge to a service/account
|
|
*
|
|
* @param Request $request
|
|
* @return View
|
|
*/
|
|
public function charge_edit(Request $request): View
|
|
{
|
|
$o = Charge::where('processed',FALSE)
|
|
->where('id',$request->id)
|
|
->firstOrFail();
|
|
|
|
if (Gate::allows('update',$o)) {
|
|
return view('theme.backend.adminlte.charge.widget.addedit')
|
|
->with('o',$o)
|
|
->with('so',$o->service);
|
|
}
|
|
|
|
abort(403);
|
|
}
|
|
|
|
/**
|
|
* 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('theme.backend.adminlte.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('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
|
|
{
|
|
// @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('theme.backend.adminlte.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('theme.backend.adminlte.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)
|
|
{
|
|
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->category,$item))
|
|
->combine(array_values($o->type->validation()))
|
|
->transform(fn($item)=>is_string($item)
|
|
? preg_replace('/^exclude_without:/',sprintf('exclude_without:%s.',$o->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->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->category));
|
|
|
|
// Some special handling
|
|
switch ($o->category) {
|
|
case 'broadband':
|
|
// If pppoe is not set, then we dont need username/password
|
|
$o->type->pppoe = ($x=data_get($validated,$o->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->category) {
|
|
case 'broadband':
|
|
$o->start_at = $o->type->connect_at;
|
|
break;
|
|
}
|
|
}
|
|
|
|
$o->save();
|
|
|
|
return redirect()
|
|
->back()
|
|
->with('success','Record Updated');
|
|
}
|
|
} |