Compare commits
7 Commits
3fc676fa26
...
64acc93c0d
Author | SHA1 | Date | |
---|---|---|---|
64acc93c0d | |||
b33c052995 | |||
8d72c1ceb5 | |||
256e4c6260 | |||
fc0e75e1fb | |||
4c24f09195 | |||
48d7968d0c |
@ -10,32 +10,39 @@ use App\Models\{Site,Supplier,User};
|
||||
|
||||
class SupplierAccountSync extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'supplier:account:sync'
|
||||
.' {siteid : Site ID}'
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'supplier:account:sync'
|
||||
.' {supplier : Supplier Name}'
|
||||
.' {--f|forceprod : Force Prod API on dev environment}';
|
||||
.' {--f|forceprod : Force Prod API on dev environment}'
|
||||
.' {--s|site : Site ID}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Sync accounts with a supplier';
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Sync accounts with a supplier';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
Config::set('site',Site::findOrFail($this->argument('siteid')));
|
||||
$so = Supplier::where('name',$this->argument('supplier'))->singleOrFail();
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return int
|
||||
* @note Suppliers are now associated with accounts, so this needs to be updated
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
Config::set(
|
||||
'site',
|
||||
$this->option('site')
|
||||
? Site::findOrFail($this->option('site'))
|
||||
: Site::where('url',config('app.url'))->sole()
|
||||
);
|
||||
|
||||
$so = Supplier::where('name','ilike',strtolower($this->argument('supplier')))->sole();
|
||||
|
||||
foreach ($so->API($this->option('forceprod'))->getCustomers(['fetchall'=>true]) as $customer) {
|
||||
// Check if we have this customer already (by ID)
|
||||
@ -43,7 +50,6 @@ class SupplierAccountSync extends Command
|
||||
$this->info(sprintf('User already linked (%s:%s)',$customer->id,$customer->email));
|
||||
|
||||
} elseif ($x=User::where('email',$customer->email)->single()) {
|
||||
//dump($x->suppliers->first());
|
||||
if ($x->suppliers->count()) {
|
||||
$this->alert(sprintf('User [%d:%s] already linked to this supplier with ID (%s)',$customer->id,$customer->email,$x->suppliers->first()->pivot->id));
|
||||
|
||||
@ -63,5 +69,7 @@ class SupplierAccountSync extends Command
|
||||
$this->error(sprintf('User doesnt exist with email (%s:%s)',$customer->id,$customer->email));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
}
|
@ -3,9 +3,10 @@
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\Config;
|
||||
|
||||
use App\Models\{Site,Supplier};
|
||||
use App\Jobs\SupplierDomainSync as Job;
|
||||
use App\Models\{Site,Supplier};
|
||||
|
||||
class SupplierDomainSync extends Command
|
||||
{
|
||||
@ -15,9 +16,9 @@ class SupplierDomainSync extends Command
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'supplier:domain:sync'
|
||||
.' {siteid : Site ID}'
|
||||
.' {supplier : Supplier Name}'
|
||||
.' {--f|forceprod : Force Prod API on dev environment}';
|
||||
.' {supplier : Supplier Name}'
|
||||
.' {--f|forceprod : Force Prod API on dev environment}'
|
||||
.' {--s|site : Site ID}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
@ -33,8 +34,15 @@ class SupplierDomainSync extends Command
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
Config::set(
|
||||
'site',
|
||||
($x=$this->option('site')
|
||||
? Site::findOrFail($this->option('site'))
|
||||
: Site::where('url',config('app.url'))->sole())
|
||||
);
|
||||
|
||||
$so = Supplier::where('name',$this->argument('supplier'))->singleOrFail();
|
||||
|
||||
Job::dispatchSync(Site::findOrFail($this->argument('siteid')),$so,$this->option('forceprod'));
|
||||
return Job::dispatchSync($x,$so,$this->option('forceprod'));
|
||||
}
|
||||
}
|
@ -27,7 +27,7 @@ class SocialLoginController extends Controller
|
||||
$openiduser = Socialite::with($provider)->user();
|
||||
|
||||
if (! $openiduser)
|
||||
return redirect('/home')
|
||||
return redirect('home')
|
||||
->with('error','No user details obtained.');
|
||||
|
||||
$oo = ProviderOauth::firstOrCreate(['name'=>$provider,'active'=>TRUE]);
|
||||
@ -43,7 +43,7 @@ class SocialLoginController extends Controller
|
||||
$user = User::where('email',$openiduser->email)->first();
|
||||
|
||||
if ((! $user) || (! $user->active))
|
||||
return redirect('/login')
|
||||
return redirect('login')
|
||||
->with('error','Invalid account, or account inactive, please contact an admin.');
|
||||
|
||||
return $this->link($provider,$aoo,$user);
|
||||
@ -54,7 +54,7 @@ class SocialLoginController extends Controller
|
||||
|
||||
// If there are too many users, then we have a problem
|
||||
} elseif ($aoo->count() > 1) {
|
||||
return redirect('/login')
|
||||
return redirect('login')
|
||||
->with('error','Seems you have multiple oauth IDs, please contact an admin.');
|
||||
|
||||
// User is using OAUTH for the first time.
|
||||
@ -73,11 +73,11 @@ class SocialLoginController extends Controller
|
||||
|
||||
// If there are too many users, then we have a problem
|
||||
} elseif ($uo->count() > 1) {
|
||||
return redirect('/login')
|
||||
return redirect('login')
|
||||
->with('error','Seems you have multiple accounts, please contact an admin.');
|
||||
|
||||
} else {
|
||||
return redirect('/login')
|
||||
return redirect('login')
|
||||
->with('error','Seems you dont have an account with that email, please contact an admin.');
|
||||
}
|
||||
}
|
||||
@ -91,7 +91,7 @@ class SocialLoginController extends Controller
|
||||
$openiduser = Socialite::with($provider)->user();
|
||||
|
||||
if (! $openiduser)
|
||||
return redirect('/home')
|
||||
return redirect('home')
|
||||
->with('error','No user details obtained.');
|
||||
|
||||
$po = ProviderOauth::where('name',$provider)->singleOrFail();
|
||||
@ -137,12 +137,12 @@ class SocialLoginController extends Controller
|
||||
|
||||
// Check our email matches
|
||||
if (Arr::get($aoo->oauth_data,'email','invalid') !== $request->post('email'))
|
||||
return redirect('/login')
|
||||
return redirect('login')
|
||||
->with('error','Account details didnt match to make link.');
|
||||
|
||||
// Check our token matches
|
||||
if ($aoo->link_token !== $request->post('token'))
|
||||
return redirect('/login')
|
||||
return redirect('login')
|
||||
->with('error','Token details didnt match to make link.');
|
||||
|
||||
// Load our email.
|
||||
|
@ -34,8 +34,7 @@ class CheckoutController extends Controller
|
||||
}
|
||||
|
||||
return $o->wasRecentlyCreated
|
||||
? redirect()
|
||||
->to('a/checkout/'.$o->id)
|
||||
? redirect('a/checkout/'.$o->id)
|
||||
->with('success','Checkout added')
|
||||
: redirect()
|
||||
->back()
|
||||
|
@ -50,7 +50,7 @@ class HomeController extends Controller
|
||||
} catch (ExpiredInviteCode $e) {
|
||||
Log::alert(sprintf('User is using an expired token for invoice [%s] using [%s]',$o->id,$code));
|
||||
|
||||
return redirect()->to('/login');
|
||||
return redirect('login');
|
||||
|
||||
} catch (DoormanException $e) {
|
||||
Log::alert(sprintf('An attempt to read invoice id [%s] using [%s]',$o->id,$code));
|
||||
@ -72,6 +72,6 @@ class HomeController extends Controller
|
||||
*/
|
||||
public function service_progress(Service $o,string $status)
|
||||
{
|
||||
return redirect()->to($o->action($status) ?: url('u/service',$o->id));
|
||||
return redirect($o->action($status) ?: url('u/service',$o->id));
|
||||
}
|
||||
}
|
@ -63,8 +63,7 @@ class PaymentController extends Controller
|
||||
}
|
||||
|
||||
return $o->wasRecentlyCreated
|
||||
? redirect()
|
||||
->to('r/payment/'.$o->id)
|
||||
? redirect('r/payment/'.$o->id)
|
||||
->with('success','Payment added')
|
||||
: redirect()
|
||||
->back()
|
||||
|
@ -34,8 +34,7 @@ class PaypalController extends Controller
|
||||
|
||||
public function cancel()
|
||||
{
|
||||
return redirect()
|
||||
->to(self::cart_url);
|
||||
return redirect(self::cart_url);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -52,8 +51,7 @@ class PaypalController extends Controller
|
||||
$cart = request()->session()->get('invoice.cart');
|
||||
|
||||
if (! $cart)
|
||||
return redirect()
|
||||
->to('u/home');
|
||||
return redirect('home');
|
||||
|
||||
$invoices = Invoice::find($cart);
|
||||
|
||||
@ -113,15 +111,13 @@ class PaypalController extends Controller
|
||||
} catch (HttpException $e) {
|
||||
Log::error('Paypal Exception',['request'=>$paypal,'response'=>$e->getMessage()]);
|
||||
|
||||
return redirect()
|
||||
->to(self::cart_url)
|
||||
return redirect(self::cart_url)
|
||||
->withErrors('Paypal Exception: '.$e->getCode());
|
||||
|
||||
} catch (\HttpException $e) {
|
||||
Log::error('HTTP Exception',['request'=>$this->client,'response'=>$e->getMessage()]);
|
||||
|
||||
return redirect()
|
||||
->to(self::cart_url)
|
||||
return redirect(self::cart_url)
|
||||
->withErrors('HTTP Exception: '.$e->getCode());
|
||||
}
|
||||
|
||||
@ -138,8 +134,7 @@ class PaypalController extends Controller
|
||||
return redirect()
|
||||
->away($redirect_url);
|
||||
|
||||
return redirect()
|
||||
->to(self::cart_url)
|
||||
return redirect(self::cart_url)
|
||||
->withErrors('An error occurred with Paypal?');
|
||||
}
|
||||
|
||||
@ -192,23 +187,20 @@ class PaypalController extends Controller
|
||||
->away($redirect_url);
|
||||
}
|
||||
|
||||
return redirect()
|
||||
->to(self::cart_url)
|
||||
return redirect(self::cart_url)
|
||||
->withErrors('An error occurred with Paypal?');
|
||||
|
||||
} catch (\HttpException $e) {
|
||||
Log::error('HTTP Exception',['request'=>$paypal,'response'=>$e->getMessage()]);
|
||||
|
||||
return redirect()
|
||||
->to(self::cart_url)
|
||||
return redirect(self::cart_url)
|
||||
->withErrors('HTTP Exception: '.$e->getCode());
|
||||
}
|
||||
|
||||
if ((! $response) || (! $response->result->purchase_units)) {
|
||||
Log::error('Paypal Capture: No Purchase Units?');
|
||||
|
||||
return redirect()
|
||||
->to(self::cart_url)
|
||||
return redirect(self::cart_url)
|
||||
->withErrors('Paypal Exception: NPU');
|
||||
}
|
||||
|
||||
@ -267,8 +259,7 @@ class PaypalController extends Controller
|
||||
|
||||
Log::info('Paypal Payment Recorded',['po'=>$po->id]);
|
||||
|
||||
return redirect()
|
||||
->to('u/home')
|
||||
return redirect('home')
|
||||
->with('success','Payment recorded thank you.');
|
||||
}
|
||||
}
|
@ -113,7 +113,7 @@ class ServiceController extends Controller
|
||||
$np->pivot->effective_at = Carbon::now();
|
||||
$np->pivot->save();
|
||||
|
||||
return redirect()->to(url('u/service',[$o->id]));
|
||||
return redirect(url('u/service',[$o->id]));
|
||||
}
|
||||
|
||||
return view('theme.backend.adminlte.service.change_pending')
|
||||
@ -191,7 +191,7 @@ class ServiceController extends Controller
|
||||
|
||||
// An Error Condition
|
||||
if (is_null($result))
|
||||
return redirect()->to('u/service/'.$o->id);
|
||||
return redirect('u/service/'.$o->id);
|
||||
|
||||
elseif ($result instanceof RedirectResponse)
|
||||
return $result;
|
||||
@ -213,7 +213,7 @@ class ServiceController extends Controller
|
||||
$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);
|
||||
return redirect('u/service/'.$o->id);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -412,7 +412,10 @@ class ServiceController extends Controller
|
||||
'suspend_billing' => 'nullable|in:on',
|
||||
'recur_schedule' => ['required',Rule::in(collect(Invoice::billing_periods)->keys())],
|
||||
'invoice_next_at' => 'nullable|date',
|
||||
'price' => 'nullable|numeric',
|
||||
'product_id'=>'required|exists:products,id',
|
||||
'price' => 'nullable|numeric|min:0', // Price we charge the client, if we dont charge supplied/price
|
||||
'cost' => 'nullable|numeric|min:0', // Price we are charged by supplier, if we arent charged supplier/price
|
||||
'supplierid' => 'nullable|string|min:1', // As used on invoices
|
||||
$o->product->category => 'array|min:1',
|
||||
]
|
||||
)
|
||||
@ -456,6 +459,9 @@ class ServiceController extends Controller
|
||||
$o->suspend_billing = ($validated->get('suspend_billing') == 'on');
|
||||
$o->external_billing = ($validated->get('external_billing') == 'on');
|
||||
$o->price = $validated->get('price');
|
||||
$o->cost = $validated->get('cost');
|
||||
$o->supplierid = $validated->get('supplierid');
|
||||
$o->product_id = $validated->get('product_id');
|
||||
|
||||
// Also update our service start_at date.
|
||||
// @todo We may want to make start_at/stop_at dynamic values calculated by the type records
|
||||
|
@ -30,7 +30,9 @@ class SupplierController extends Controller
|
||||
$o->save();
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return redirect()->back()->withErrors($e->getMessage())->withInput();
|
||||
return redirect()
|
||||
->back()
|
||||
->withErrors($e->getMessage())->withInput();
|
||||
}
|
||||
|
||||
$o->load(['detail']);
|
||||
|
@ -45,6 +45,7 @@ use App\Traits\{ScopeAccountUserAuthorised,ScopeServiceActive,SiteID};
|
||||
*
|
||||
* Methods:
|
||||
* + isChargeOverridden : Has the price been overridden?
|
||||
* + isCostOverridden : Has the cost been overridden?
|
||||
* + isPending : Is this a pending active service
|
||||
*
|
||||
* @package App\Models
|
||||
@ -535,6 +536,11 @@ class Service extends Model implements IDs
|
||||
return $this->account->taxed($this->billing_charge());
|
||||
}
|
||||
|
||||
public function getBillingCostAttribute(): float
|
||||
{
|
||||
return $this->account->taxed($this->billing_cost());
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine a monthly price for a service, even if it is billed at a different frequency
|
||||
*
|
||||
@ -543,7 +549,16 @@ class Service extends Model implements IDs
|
||||
*/
|
||||
public function getBillingChargeNormalisedAttribute(): float
|
||||
{
|
||||
return number_format($this->getBillingChargeAttribute()*Invoice::billing_change($this->getBillingIntervalAttribute(),$this->offering->billing_interval),2);
|
||||
return number_format(
|
||||
$this->getBillingChargeAttribute()*Invoice::billing_change($this->getBillingIntervalAttribute(),$this->offering->billing_interval),
|
||||
2);
|
||||
}
|
||||
|
||||
public function getBillingCostNormalisedAttribute(): float
|
||||
{
|
||||
return number_format(
|
||||
$this->getBillingCostAttribute()*Invoice::billing_change($this->getBillingIntervalAttribute(),$this->offering->billing_interval),
|
||||
2);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -567,7 +582,7 @@ class Service extends Model implements IDs
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the earliest date that the service can be cancelled
|
||||
* Return the earliest date that the service can be canceled as per contract/billing intervals
|
||||
*
|
||||
* @return Carbon
|
||||
*/
|
||||
@ -916,17 +931,21 @@ class Service extends Model implements IDs
|
||||
/**
|
||||
* Provide billing charge to a future date
|
||||
*
|
||||
* If $date is earlier than our contract end date, then our charge is to the contract end date.
|
||||
* If $date is after our contract end date:
|
||||
* + and we are still in contract, then our charge is to contract end date plus additional time, else
|
||||
* + then our charge is the months to the $date
|
||||
*
|
||||
* @param Carbon $date
|
||||
* @return float
|
||||
* @throws Exception
|
||||
*/
|
||||
public function billing_charge_to(Carbon $date): float
|
||||
{
|
||||
// if the date is less than the paid to, but less than the cancel date to, return cancel-paid to charge
|
||||
// If the date is greater than the paid to, and less than the cancel date to, return cancel-paid to charge
|
||||
if ($this->getPaidToAttribute()->lessThan($this->getCancelDateAttribute())) {
|
||||
$max = max($date,$this->getPaidToAttribute())->clone();
|
||||
$d = $max->diffInDays($this->getCancelDateAttribute());
|
||||
$max = max($date,$this->getCancelDateAttribute())->clone();
|
||||
|
||||
if ($this->getPaidToAttribute()->lessThan($max)) {
|
||||
$d = $this->getPaidToAttribute()->diffInDays($max);
|
||||
|
||||
return $this->account->taxed($d/30*$this->getBillingChargeNormalisedAttribute());
|
||||
}
|
||||
@ -934,6 +953,43 @@ class Service extends Model implements IDs
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* The amount we are charged for the client to have this service
|
||||
*
|
||||
* @return float
|
||||
*/
|
||||
public function billing_cost(): float
|
||||
{
|
||||
return is_null($this->cost)
|
||||
? $this->product->getBaseCostAttribute()
|
||||
: $this->cost*Invoice::billing_change($this->product->type->billing_interval,$this->product->billing_interval);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate our costs to keep this service to a future date
|
||||
*
|
||||
* If $date is earlier than the contract end date, then our cost is to the contract end date.
|
||||
* If $date is after the contract end date:
|
||||
* + and we are still in contract, then our cost is to contract end date plus additional time, else
|
||||
* + then our cost is the months to the $date
|
||||
*
|
||||
* @param Carbon $date
|
||||
* @return float
|
||||
* @throws Exception
|
||||
*/
|
||||
public function billing_cost_to(Carbon $date): float
|
||||
{
|
||||
$max = max($date,$this->getCancelDateAttribute())->clone();
|
||||
|
||||
if ($this->getInvoicedToAttribute()->lessThan($max)) {
|
||||
$d = $this->getInvoicedToAttribute()->diffInDays($max);
|
||||
|
||||
return $this->account->taxed($d/30*$this->getBillingCostNormalisedAttribute());
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the stage parameters
|
||||
*
|
||||
@ -1065,9 +1121,14 @@ class Service extends Model implements IDs
|
||||
return ! is_null($this->price);
|
||||
}
|
||||
|
||||
public function isCostOverridden(): bool
|
||||
{
|
||||
return ! is_null($this->cost);
|
||||
}
|
||||
|
||||
public function isContract(): bool
|
||||
{
|
||||
return $this->getContractEndAttribute()->greaterThan(Carbon::now());
|
||||
return $this->getContractEndAttribute() && $this->getContractEndAttribute()->greaterThan(Carbon::now());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -57,16 +57,6 @@ class Broadband extends Type implements ServiceUsage
|
||||
|
||||
/* ATTRIBUTES */
|
||||
|
||||
/**
|
||||
* @deprecated use $o->service_name;
|
||||
* @return mixed|string
|
||||
*/
|
||||
public function getNameAttribute()
|
||||
{
|
||||
abort(500,'deprecated - use $o->service_name');
|
||||
return $this->service_number ?: $this->service_address;
|
||||
}
|
||||
|
||||
/**
|
||||
* The type of technology used to provide this Internet Service
|
||||
*
|
||||
@ -104,15 +94,14 @@ class Broadband extends Type implements ServiceUsage
|
||||
/* METHODS */
|
||||
|
||||
/**
|
||||
* Return the suppliers offering that this service is providing
|
||||
* Return the supplier's offering that this service is providing
|
||||
*
|
||||
* @return SupplierType
|
||||
* @todo This column provided_adsl_plan_id should either be deprecated or renamed.
|
||||
*/
|
||||
public function supplied(): SupplierType
|
||||
{
|
||||
return $this->provided_adsl_plan_id
|
||||
? SupplierBroadband::findOrFail($this->provided_adsl_plan_id)
|
||||
return $this->provided_supplier_broadband_id
|
||||
? SupplierBroadband::findOrFail($this->provided_supplier_broadband_id)
|
||||
: $this->service->offering->supplied;
|
||||
}
|
||||
|
||||
|
30
database/migrations/2025_05_16_135847_add_cost.php
Normal file
30
database/migrations/2025_05_16_135847_add_cost.php
Normal file
@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('services', function (Blueprint $table) {
|
||||
$table->float('cost')->nullable();
|
||||
$table->string('supplierid')->nullable();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('service_broadband', function (Blueprint $table) {
|
||||
$table->dropColumn('cost');
|
||||
$table->dropColumn('supplierid');
|
||||
});
|
||||
}
|
||||
};
|
@ -19,7 +19,7 @@
|
||||
$ao->load(['services:id,active,account_id']);
|
||||
@endphp
|
||||
<tr>
|
||||
<td><a href="{{ url('r/switch/start',$ao->user_id) }}"><i class="fas fa-external-link-alt"></i></a></td>
|
||||
<td><a href="{{ url('switch/start',$ao->user_id) }}"><i class="fas fa-external-link-alt"></i></a></td>
|
||||
<td><a href="{{ url('u/home',$ao->user_id) }}">{{ $ao->name }}</a></td>
|
||||
<td class="text-right">{{ $ao->services->where('active',TRUE)->count() }} <small>/{{ $ao->services->count() }}</small></td>
|
||||
</tr>
|
||||
|
@ -1,4 +1,5 @@
|
||||
<!-- $co=Checkout::class -->
|
||||
|
||||
@extends('adminlte::layouts.app')
|
||||
|
||||
@section('htmlheader_title')
|
||||
|
@ -1,4 +1,5 @@
|
||||
<!-- $o=Invoice::class -->
|
||||
|
||||
@use(App\Models\Checkout)
|
||||
@use(App\Models\Service)
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
<!-- po=Payment::class -->
|
||||
<!-- $po=Payment::class -->
|
||||
|
||||
@use(Carbon\Carbon)
|
||||
@use(App\Models\Account)
|
||||
@use(App\Models\Checkout)
|
||||
|
@ -1,3 +1,5 @@
|
||||
<!-- $o=Service::class,$np=Product::class -->
|
||||
|
||||
@use(App\Models\Product)
|
||||
|
||||
@extends('adminlte::layouts.app')
|
||||
@ -16,7 +18,6 @@
|
||||
{{ $o->sid }}
|
||||
@endsection
|
||||
|
||||
<!-- $o=Service::class, $np=Product::class -->
|
||||
@section('main-content')
|
||||
<div class="row">
|
||||
<div class="col-12 col-lg-4">
|
||||
|
@ -25,6 +25,7 @@
|
||||
<th>ID</th>
|
||||
<th>Service</th>
|
||||
<th>Product</th>
|
||||
<th>Supplier ID</th>
|
||||
<th class="text-right">Monthly</th>
|
||||
<th class="text-right">Cost</th>
|
||||
<th class="text-right">Usage</th>
|
||||
@ -38,13 +39,26 @@
|
||||
<td><a href="{{ url('u/service',[$o->id]) }}">{{ $o->id }}</a></td>
|
||||
<td>{{ $o->name }}</td>
|
||||
<td>{{ $o->product->name }}</td>
|
||||
<td>{{ $o->supplierid }}</td>
|
||||
<td class="text-right">{{ number_format($o->billing_charge_normalised,2) }}</td>
|
||||
<td class="text-right">{{ number_format($o->product->cost_normalized(),2) }}</td>
|
||||
<td class="text-right">{{ number_format($o->billing_cost_normalised,2) }}</td>
|
||||
<td class="text-right">{{ $o->product->hasUsage() ? number_format($o->type->usage_summary(0)->sum()/1000,1) : '-' }}</td>
|
||||
<td>{{ $o->product->supplier->name }}</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr>
|
||||
<td></td>
|
||||
<th>TOTAL:</th>
|
||||
<td></td>
|
||||
<td class="text-right"></td>
|
||||
<td class="text-right"></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
@ -77,6 +91,41 @@
|
||||
],
|
||||
rowGroup: {
|
||||
dataSrc: [2],
|
||||
endRender: function (rows, group) {
|
||||
// Remove the formatting to get integer data for summation
|
||||
let intVal = function (i) {
|
||||
return typeof i === 'string'
|
||||
? i.replace(/[\$,]/g, '') * 1
|
||||
: typeof i === 'number'
|
||||
? i
|
||||
: 0;
|
||||
};
|
||||
|
||||
// Total over all pages
|
||||
let month = rows
|
||||
.data()
|
||||
.pluck(4)
|
||||
.reduce((a, b) => intVal(a) + intVal(b), 0);
|
||||
|
||||
let cost = rows
|
||||
.data()
|
||||
.pluck(5)
|
||||
.reduce((a, b) => intVal(a) + intVal(b), 0);
|
||||
|
||||
let usage = rows
|
||||
.data()
|
||||
.pluck(6)
|
||||
.reduce((a, b) => intVal(a) + intVal(b), 0);
|
||||
|
||||
return $('<tr/>')
|
||||
.append('<td/>')
|
||||
.append('<td><strong>SUB-TOTAL:</strong></td>')
|
||||
.append('<td/>')
|
||||
.append('<td class="text-right"><strong>'+month.toFixed(2)+'</strong></td>')
|
||||
.append('<td class="text-right"><strong>'+cost.toFixed(2)+'</strong></td>')
|
||||
.append('<td class="text-right"><strong>'+usage.toFixed(2)+'</strong></td>')
|
||||
.append('<td/>');
|
||||
}
|
||||
},
|
||||
columnDefs: [
|
||||
{
|
||||
@ -84,7 +133,7 @@
|
||||
visible: false,
|
||||
},
|
||||
{
|
||||
targets: [0,1,3,4,5],
|
||||
targets: [0,1,4,5,6],
|
||||
searchPanes: {
|
||||
show: false,
|
||||
}
|
||||
@ -104,6 +153,46 @@
|
||||
controls: false,
|
||||
},
|
||||
dom: '<"dtsp-verticalContainer"<"dtsp-verticalPanes"P><"dtsp-dataTable"Bfrtip>>',
|
||||
footerCallback: function (row, data, start, end, display) {
|
||||
let api = this.api();
|
||||
|
||||
// Remove the formatting to get integer data for summation
|
||||
let intVal = function (i) {
|
||||
return typeof i === 'string'
|
||||
? i.replace(/[\$,]/g, '') * 1
|
||||
: typeof i === 'number'
|
||||
? i
|
||||
: 0;
|
||||
};
|
||||
|
||||
// Total over all pages
|
||||
month = api
|
||||
.column(4,{ search: 'applied' })
|
||||
.data()
|
||||
.reduce((a, b) => intVal(a) + intVal(b), 0);
|
||||
|
||||
// Total over this page
|
||||
monthTotal = api
|
||||
.column(4, { page: 'current' })
|
||||
.data()
|
||||
.reduce((a, b) => intVal(a) + intVal(b), 0);
|
||||
|
||||
// Total over all pages
|
||||
cost = api
|
||||
.column(5,{ search: 'applied' })
|
||||
.data()
|
||||
.reduce((a, b) => intVal(a) + intVal(b), 0);
|
||||
|
||||
// Total over this page
|
||||
costTotal = api
|
||||
.column(5, { page: 'current' })
|
||||
.data()
|
||||
.reduce((a, b) => intVal(a) + intVal(b), 0);
|
||||
|
||||
// Update footer
|
||||
api.column(4).footer().innerHTML = monthTotal.toFixed(2)+'<br><small>('+month.toFixed(2)+')</small>';
|
||||
api.column(5).footer().innerHTML = costTotal.toFixed(2)+'<br><small>('+cost.toFixed(2)+')</small>';
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
@ -1,4 +1,5 @@
|
||||
<!-- $o = App\Models\Service\Broadband::class -->
|
||||
<!-- $o=Service\Broadband::class -->
|
||||
|
||||
<div class="card">
|
||||
@if($o->service->isPending())
|
||||
<div class="ribbon-wrapper ribbon-lg">
|
||||
|
@ -10,7 +10,7 @@
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="col-12">
|
||||
Connection Type
|
||||
<x-leenooks::form.toggle id="pppoe" name="broadband[pppoe]" label="PPPoE" old="broadband.pppoe" :value="$o->pppoe"/>
|
||||
</div>
|
||||
|
@ -1,4 +1,5 @@
|
||||
<!-- $o = App\Models\Service\Domain::class -->
|
||||
<!-- $o=Service\Domain::class -->
|
||||
|
||||
<div class="card">
|
||||
@if($o->service->isPending())
|
||||
<div class="ribbon-wrapper ribbon-lg">
|
||||
|
@ -1,4 +1,5 @@
|
||||
<!-- $o = App\Models\Service\Email::class -->
|
||||
<!-- $o=Service\Email::class -->
|
||||
|
||||
<div class="card">
|
||||
@if($o->service->isPending())
|
||||
<div class="ribbon-wrapper ribbon-lg">
|
||||
|
@ -1,4 +1,5 @@
|
||||
<!-- $o = App\Models\Service\Host::class -->
|
||||
<!-- $o=Service\Host::class -->
|
||||
|
||||
<div class="card">
|
||||
@if($o->service->isPending())
|
||||
<div class="ribbon-wrapper ribbon-lg">
|
||||
|
@ -1,7 +1,10 @@
|
||||
<!-- $o=Service::class,$p=Product::class -->
|
||||
|
||||
@use(Carbon\CarbonInterface)
|
||||
@use(App\Models\Invoice)
|
||||
|
||||
@php($c=$o->product)
|
||||
|
||||
<!-- $o=Service::class, $p=Product::class -->
|
||||
<table class="table table-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
@ -29,7 +32,8 @@
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>Product</th>
|
||||
<td class="text-center" colspan="2"><a href="{{ url('a/product/details',$c->id) }}">#{{ $c->supplied->id }}: {{ $c->supplied->name_long }}</a></td>
|
||||
<td><a href="{{ url('a/product/details',$o->product_id) }}">#{{ $o->product_id }}: {{ $o->product->name }}</a></td>
|
||||
<td><a href="{{ url('a/product/details',$c->id) }}">#{{ $c->supplied->id }}: {{ $c->supplied->name_long }}</a></td>
|
||||
@if($p->exists)
|
||||
<th> </th>
|
||||
<td class="text-center" colspan="2">#{{ $p->supplied->id }}: {{ $p->supplied->name_long }}</td>
|
||||
@ -64,7 +68,7 @@
|
||||
<tr>
|
||||
<th>Billing Price</th>
|
||||
<td @if($o->isChargeOverridden())class="text-danger"@endif>${{ number_format($b=$o->billing_charge,2) }}</td>
|
||||
<td>${{ number_format($a=$o->account->taxed($c->base_cost),2) }}</td>
|
||||
<td @if($o->isCostOverridden())class="text-danger"@endif>${{ number_format($a=$o->billing_cost,2) }}</td>
|
||||
<td>{!! markup($a,$b) !!}</td>
|
||||
@if($p->exists)
|
||||
<td @if($o->isChargeOverridden())class="text-danger"@endif>${{ number_format($b=$o->account->taxed($p->base_charge),2) }}</td>
|
||||
@ -79,10 +83,16 @@
|
||||
@if($x)
|
||||
<abbr title="${{ number_format($b=$o->account->taxed($c->base_charge)*Invoice::billing_change($o->billing_interval,Invoice::BILL_MONTHLY),2) }}">${{ number_format($b=$o->billing_charge_normalised,2) }}
|
||||
@else
|
||||
{{ number_format($b=$o->billing_charge_normalised,2) }}
|
||||
${{ number_format($b=$o->billing_charge_normalised,2) }}
|
||||
@endif
|
||||
</td>
|
||||
<td @if($x=$o->isCostOverridden()) class="text-danger" @endif>
|
||||
@if($x)
|
||||
<abbr title="${{ number_format($a=$o->account->taxed($c->base_cost)*Invoice::billing_change($o->billing_interval,Invoice::BILL_MONTHLY),2) }}">${{ number_format($b=$o->billing_cost_normalised,2) }}
|
||||
@else
|
||||
${{ number_format($a=$o->billing_cost_normalised,2) }}
|
||||
@endif
|
||||
</td>
|
||||
<td>${{ number_format($a=$o->account->taxed($c->base_cost)*Invoice::billing_change($o->billing_interval,Invoice::BILL_MONTHLY),2) }}</td>
|
||||
<td>{!! markup($a,$b) !!}</td>
|
||||
@if($p->exists)
|
||||
<td @if($x=$o->isChargeOverridden()) class="text-danger" @endif>${{ number_format($b=$o->account->taxed($p->base_charge)*Invoice::billing_change($o->billing_interval,Invoice::BILL_MONTHLY),2) }}</td>
|
||||
@ -109,11 +119,25 @@
|
||||
<td>${{ number_format($b=$o->account->taxed($o->product->min_charge),2) }}</td>
|
||||
<td>${{ number_format($a=$o->account->taxed($c->supplied->min_cost),2) }}</td>
|
||||
<td>{!! markup($a,$b) !!}</td>
|
||||
|
||||
@if($p->exists)
|
||||
<td>${{ number_format($a=$o->account->taxed($p->min_charge),2) }}</td>
|
||||
<td>${{ number_format($a=$o->account->taxed($p->supplied->min_cost ?? 0),2) }}</td>
|
||||
<td>{!! markup($a,$b) !!}</td>
|
||||
@endif
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<th>Contract Left</th>
|
||||
@if($o->isContract())
|
||||
<td>${{ number_format($o->billing_charge_to($o->contract_end),2) }} (<small>{{ $o->paid_to->format('Y-m-d') }}</small>)</td>
|
||||
<td>${{ number_format($o->billing_cost_to($o->contract_end),2) }} (<small>{{ $o->invoiced_to->format('Y-m-d') }}</small>)</td>
|
||||
<td>{{ $o->contract_end->format('Y-m-d') }}<br><small>({{ $o->contract_end->diffForHumans(now(),CarbonInterface::DIFF_RELATIVE_TO_OTHER,FALSE,2) }} today)</small></td>
|
||||
|
||||
@else
|
||||
<td colspan="2" class="text-center">Not on contract</td>
|
||||
<td> </td>
|
||||
@endif
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
@ -1,4 +1,5 @@
|
||||
<!-- $o = App\Models\Service\Phone::class -->
|
||||
<!-- $o=Service\Phone::class -->
|
||||
|
||||
<div class="card">
|
||||
@if($o->service->isPending())
|
||||
<div class="ribbon-wrapper ribbon-lg">
|
||||
|
@ -1,9 +1,10 @@
|
||||
<!-- $o=Service::class -->
|
||||
@use(App\Models\Invoice)
|
||||
@use(App\Models\Product)
|
||||
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<h4>Update Service details <x-leenooks::button.success class="float-right"/></h4>
|
||||
<h4>Update Service Details <x-leenooks::button.success class="float-right"/></h4>
|
||||
<hr>
|
||||
|
||||
<form method="POST" action="{{ url('a/service/update',[$o->id]) }}">
|
||||
@ -29,13 +30,36 @@
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-3"></div>
|
||||
<div class="col-12 col-sm-3">
|
||||
<x-leenooks::form.text name="supplierid" icon="fa-hashtag" label="Supplier ID" :value="$o->supplierid"/>
|
||||
</div>
|
||||
|
||||
<div class="col-12 col-sm-9 col-md-6 col-xl-5">
|
||||
<x-leenooks::form.select id="recur_schedule" name="recur_schedule" icon="fa-redo" label="Renew Term" :value="$o->recur_schedule" :options="collect(Invoice::billing_periods)->map(fn($item,$key)=>['id'=>$key,'value'=>$item['name']])"/>
|
||||
</div>
|
||||
|
||||
<!-- Cost -->
|
||||
<div class="col-12 col-sm-4 col-xl-3">
|
||||
<x-leenooks::form.text name="cost" icon="fa-dollar-sign" label="Cost (Monthly)" :value="$o->cost"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<!-- PRODUCT -->
|
||||
<x-leenooks::form.select name="product_id" icon="fa-list" label="Product" :helper="$o->product->category_name"
|
||||
:value="$o->product_id"
|
||||
:options="Product::get()
|
||||
->filter(fn($item)=>$item->active && ($item->category === $o->product->category))
|
||||
->sortBy('name')
|
||||
->map(fn($item)=>[
|
||||
'id'=>$item->id,
|
||||
'value'=>sprintf('%s (%3.2f/%3.2f)',
|
||||
$item->name,
|
||||
$o->account->taxed($item->base_charge)*Invoice::billing_change($item->billing_interval,Invoice::BILL_MONTHLY),
|
||||
$o->account->taxed($item->base_cost)*Invoice::billing_change($item->billing_interval,Invoice::BILL_MONTHLY))])" :required="true"/>
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
|
||||
@includeIf('theme.backend.adminlte.service.widget.'.$o->product->category.'.update',['o'=>$o->type])
|
||||
|
@ -1,6 +1,7 @@
|
||||
<!-- $spo=Supplier::class -->
|
||||
|
||||
@use(Carbon\Carbon)
|
||||
|
||||
<!-- $spo=Supplier::class -->
|
||||
@extends('adminlte::layouts.app')
|
||||
|
||||
@section('htmlheader_title')
|
||||
|
@ -1,6 +1,8 @@
|
||||
<!-- $spo=Supplier::class -->
|
||||
<!-- $oo=Supplier/{type}::class -->
|
||||
|
||||
@use(App\Models\Supplier)
|
||||
|
||||
@php
|
||||
if(isset($spo)) {
|
||||
$oo = $spo->detail?->find(request()->route()->parameter('type'),request()->route()->parameter('id'));
|
||||
|
@ -1,4 +1,5 @@
|
||||
<!-- $o = Supplier{type}::class -->
|
||||
<!-- $o=Supplier/Broadband::class -->
|
||||
|
||||
<div class="row">
|
||||
<!-- Supplier Name -->
|
||||
<div class="col-4">
|
||||
|
@ -1,4 +1,5 @@
|
||||
<!-- ($o??$user)=User::class -->
|
||||
|
||||
@use(App\Models\Country)
|
||||
|
||||
@extends('adminlte::layouts.app')
|
||||
|
@ -46,7 +46,7 @@
|
||||
|
||||
<div class="dropdown-divider"></div>
|
||||
@if($user->switched)
|
||||
<a href="{{ url('/admin/switch/stop') }}" class="dropdown-item">
|
||||
<a href="{{ url('switch/stop') }}" class="dropdown-item">
|
||||
<i class="fas fa-sign-out-alt mr-2"></i> {{ trans('adminlte_lang::message.switchoff') }}
|
||||
</a>
|
||||
@else
|
||||
|
@ -18,8 +18,7 @@ use App\Http\Controllers\{AdminController,
|
||||
SearchController,
|
||||
ServiceController,
|
||||
SupplierController,
|
||||
UserController,
|
||||
Wholesale\ReportController};
|
||||
UserController};
|
||||
use App\Models\Supplier;
|
||||
|
||||
/*
|
||||
@ -42,8 +41,8 @@ Auth::routes([
|
||||
'confirm' => false, // for additional password confirmations
|
||||
'verify' => false, // for email verification
|
||||
]);
|
||||
Route::get('logout',[LoginController::class,'logout'])
|
||||
->name('logout-get');
|
||||
// Auth::routes doesnt provide a get /logout path, so we'll specify it here
|
||||
Route::get('logout',[LoginController::class,'logout']);
|
||||
|
||||
// Frontend Routes (Non-Authed Users)
|
||||
Route::view('/','theme.frontend.metronic.welcome.home');
|
||||
@ -54,21 +53,35 @@ Route::redirect('passkey/loggedin','/u/home');
|
||||
|
||||
Route::get('search',[SearchController::class,'search']);
|
||||
|
||||
Route::get('pay/paypal/authorise',[PaypalController::class,'authorise']);
|
||||
Route::get('pay/paypal/cancel',[PaypalController::class,'cancel']);
|
||||
Route::get('pay/paypal/capture',[PaypalController::class,'capture']);
|
||||
// Paypal paths
|
||||
Route::controller(PaypalController::class)
|
||||
->prefix('pay/paypal')
|
||||
->group(function() {
|
||||
Route::get('authorise','authorise');
|
||||
Route::get('cancel','cancel');
|
||||
Route::get('capture','capture');
|
||||
});
|
||||
|
||||
// Account linking to OPENID host
|
||||
Route::get('auth/{socialProvider}',[SocialLoginController::class,'redirectToProvider']);
|
||||
Route::get('auth/{socialProvider}/callback',[SocialLoginController::class,'handleProviderCallback']);
|
||||
Route::get('auth/{socialProvider}/token',[SocialLoginController::class,'handleBearerTokenCallback']);
|
||||
Route::get('auth/{socialProvider}/link',[SocialLoginController::class,'link']);
|
||||
Route::post('auth/{socialProvider}/linkcomplete',[SocialLoginController::class,'linkcomplete']);
|
||||
Route::controller(PaypalController::class)
|
||||
->prefix('auth')
|
||||
->group(function() {
|
||||
Route::get('{socialProvider}','redirectToProvider');
|
||||
Route::get('{socialProvider}/callback','handleProviderCallback');
|
||||
Route::get('{socialProvider}/token','handleBearerTokenCallback');
|
||||
Route::get('{socialProvider}/link','link');
|
||||
Route::post('{socialProvider}/linkcomplete','linkcomplete');
|
||||
});
|
||||
|
||||
// Return from user switch
|
||||
Route::get('admin/switch/stop',[SwitchUserController::class,'switch_stop'])
|
||||
// User Switch
|
||||
Route::controller(SwitchUserController::class)
|
||||
->prefix('switch')
|
||||
->middleware('auth')
|
||||
->name('switch.stop');
|
||||
->group(function() {
|
||||
Route::get('stop','switch_stop');
|
||||
Route::get('start/{user}','switch_start')
|
||||
->middleware(['role:reseller','can:assume,user']);
|
||||
});
|
||||
|
||||
// Our Admin Routes - for wholesalers
|
||||
Route::group(['middleware'=>['auth','role:wholesaler'],'prefix'=>'a'],function() {
|
||||
@ -137,11 +150,6 @@ Route::group(['middleware'=>['auth','role:wholesaler'],'prefix'=>'a'],function()
|
||||
|
||||
// Our Reseller Routes
|
||||
Route::group(['middleware'=>['auth','role:reseller'],'prefix'=>'r'],function() {
|
||||
// Enable user switch
|
||||
Route::get('switch/start/{user}',[SwitchUserController::class,'switch_start'])
|
||||
->middleware('can:assume,user')
|
||||
->name('switch.start');
|
||||
|
||||
// Reseller Reports
|
||||
Route::group(['prefix'=>'report'],function() {
|
||||
Route::view('charge/pending','theme.backend.adminlte.charge.pending');
|
||||
|
Loading…
x
Reference in New Issue
Block a user