Work on products, first completed broadband
This commit is contained in:
parent
8f5293662e
commit
1e9f15b40f
@ -3,10 +3,9 @@
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Config;
|
||||
|
||||
use App\Models\Service;
|
||||
use App\Models\{Service,Site};
|
||||
|
||||
class ServiceList extends Command
|
||||
{
|
||||
@ -15,10 +14,10 @@ class ServiceList extends Command
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'service:list '.
|
||||
'{--a|active : Active Only}'.
|
||||
'{--category= : Category}'.
|
||||
'{--f|fix : Fix start_date}';
|
||||
protected $signature = 'service:list'.
|
||||
' {--i|inactive : Include Inactive}'.
|
||||
' {--t|type= : Type}'.
|
||||
' {--f|fix : Fix start_date}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
@ -27,16 +26,6 @@ class ServiceList extends Command
|
||||
*/
|
||||
protected $description = 'List all services';
|
||||
|
||||
/**
|
||||
* Create a new command instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
@ -44,13 +33,11 @@ class ServiceList extends Command
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
DB::listen(function($query) {
|
||||
Log::debug('- SQL',['sql'=>$query->sql,'binding'=>$query->bindings]);
|
||||
});
|
||||
$header = '|%13s|%-10s|%-35s|%-40s|%8s|%10s|%15s|%10s|%10s|%12s|%14s|';
|
||||
|
||||
$this->warn(sprintf('|%10s|%-6s|%-20s|%-50s|%8s|%14s|%10s|%10s|%10s|%10s|%10s|',
|
||||
$this->warn(sprintf($header,
|
||||
'ID',
|
||||
'CAT',
|
||||
'Type',
|
||||
'Product',
|
||||
'Name',
|
||||
'active',
|
||||
@ -60,13 +47,15 @@ class ServiceList extends Command
|
||||
'stop date',
|
||||
'connect date',
|
||||
'first invoice'
|
||||
));
|
||||
));
|
||||
|
||||
foreach (Service::all() as $o) {
|
||||
if ($this->option('active') AND ! $o->isActive())
|
||||
foreach (Service::withoutGlobalScope(\App\Models\Scopes\SiteScope::class)->with(['site'])->cursor() as $o) {
|
||||
if ((! $this->option('inactive')) AND ! $o->isActive())
|
||||
continue;
|
||||
|
||||
if ($this->option('category') AND $o->product->category !== $this->option('category'))
|
||||
Config::set('site',$o->site);
|
||||
|
||||
if ($this->option('type') AND ($o->product->getProductTypeAttribute() !== $this->option('type')))
|
||||
continue;
|
||||
|
||||
$c = $o->invoice_items->filter(function($item) {return $item->item_type === 0; })->sortby('date_start')->first();
|
||||
@ -76,10 +65,10 @@ class ServiceList extends Command
|
||||
$o->save();
|
||||
}
|
||||
|
||||
$this->info(sprintf('|%10s|%-6s|%-20s|%-50s|%8s|%14s|%10s|%10s|%10s|%10s|%10s|',
|
||||
$this->info(sprintf($header,
|
||||
$o->sid,
|
||||
$o->product->category,
|
||||
$o->product_name,
|
||||
$o->product->getProductTypeAttribute(),
|
||||
$o->product->getNameAttribute(),
|
||||
$o->name_short,
|
||||
$o->active ? 'active' : 'inactive',
|
||||
$o->status,
|
||||
@ -87,7 +76,7 @@ class ServiceList extends Command
|
||||
$o->date_start ? $o->date_start->format('Y-m-d') : NULL,
|
||||
$o->date_end ? $o->date_end->format('Y-m-d') : NULL,
|
||||
($o->type AND $o->type->service_connect_date) ? $o->type->service_connect_date->format('Y-m-d') : NULL,
|
||||
$c ? $c->date_start->format('Y-m-d') : NULL,
|
||||
($c && $c->date_start) ? $c->date_start->format('Y-m-d') : NULL,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
@ -170,7 +170,7 @@ class AdminController extends Controller
|
||||
*/
|
||||
public function supplier()
|
||||
{
|
||||
return view('a.supplier');
|
||||
return view('a.supplier.find');
|
||||
}
|
||||
|
||||
/**
|
||||
@ -223,7 +223,7 @@ class AdminController extends Controller
|
||||
if (! $o->exists && $request->name)
|
||||
$o = Supplier::where('name',$request->name)->with(['details'])->firstOrNew();
|
||||
|
||||
return view('a.supplierdetails')
|
||||
return view('a.supplier.details')
|
||||
->with('o',$o);
|
||||
}
|
||||
|
||||
|
@ -14,16 +14,19 @@ use App\Models\{Account,Product,Service,User};
|
||||
|
||||
class OrderController extends Controller
|
||||
{
|
||||
// @todo To check
|
||||
public function __construct()
|
||||
{
|
||||
$this->middleware('auth');
|
||||
}
|
||||
|
||||
// @todo To check
|
||||
public function index()
|
||||
{
|
||||
return view('order.home');
|
||||
}
|
||||
|
||||
// @todo To check
|
||||
public function product_order(Product $o)
|
||||
{
|
||||
Theme::set('metronic-fe');
|
||||
@ -31,6 +34,7 @@ class OrderController extends Controller
|
||||
return view('order.widget.order',['o'=>$o]);
|
||||
}
|
||||
|
||||
// @todo To check
|
||||
public function product_info(Product $o)
|
||||
{
|
||||
Theme::set('metronic-fe');
|
||||
@ -38,9 +42,10 @@ class OrderController extends Controller
|
||||
return view('order.widget.info',['o'=>$o]);
|
||||
}
|
||||
|
||||
// @todo To check
|
||||
public function submit(Request $request)
|
||||
{
|
||||
Validator::make($request->all(),['product_id'=>'required|exists:ab_product,id'])
|
||||
Validator::make($request->all(),['product_id'=>'required|exists:products,id'])
|
||||
// Reseller
|
||||
->sometimes('account_id','required|email',function($input) use ($request) {
|
||||
return is_null($input->account_id) AND is_null($input->order_email_manual);
|
||||
@ -59,7 +64,7 @@ class OrderController extends Controller
|
||||
$po = Product::findOrFail($request->input('product_id'));
|
||||
|
||||
// Check we have the custom attributes for the product
|
||||
$options = $po->orderValidation($request);
|
||||
$order = $po->orderValidation($request);
|
||||
|
||||
if ($request->input('order_email_manual')) {
|
||||
$uo = User::firstOrNew(['email'=>$request->input('order_email_manual')]);
|
||||
@ -101,24 +106,24 @@ class OrderController extends Controller
|
||||
$so->product_id = $request->input('product_id');
|
||||
$so->order_status = 'ORDER-SUBMIT';
|
||||
$so->orderby_id = Auth::id();
|
||||
$so->model = get_class($options);
|
||||
$so->model = get_class($order);
|
||||
|
||||
if ($options->order_info) {
|
||||
$so->order_info = $options->order_info;
|
||||
if ($order->order_info) {
|
||||
$so->order_info = $order->order_info;
|
||||
|
||||
unset($options->order_info);
|
||||
unset($order->order_info);
|
||||
}
|
||||
|
||||
$so = $ao->services()->save($so);
|
||||
|
||||
if ($options instanceOf Model) {
|
||||
$options->service_id = $so->id;
|
||||
$options->save();
|
||||
if ($order instanceOf Model) {
|
||||
$order->service_id = $so->id;
|
||||
$order->save();
|
||||
}
|
||||
|
||||
Mail::to('help@graytech.net.au')
|
||||
->queue((new OrderRequest($so,$request->input('options.notes','')))->onQueue('email')); //@todo Get email from DB.
|
||||
->queue((new OrderRequest($so,$request->input('options.notes') ?: ''))->onQueue('email')); //@todo Get email from DB.
|
||||
|
||||
return view('order_received',['o'=>$so]);
|
||||
}
|
||||
}
|
||||
}
|
@ -66,7 +66,7 @@ class SearchController extends Controller
|
||||
}
|
||||
|
||||
# Look for an ADSL/NBN Service
|
||||
foreach (Adsl::Search($request->input('term'))
|
||||
foreach (Service\Broadband::Search($request->input('term'))
|
||||
->whereIN('account_id',$accounts)
|
||||
->orderBy('service_number')
|
||||
->limit(10)->get() as $o)
|
||||
|
@ -9,12 +9,12 @@ interface IDs
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getLIDattribute(): string;
|
||||
public function getLIDAttribute(): string;
|
||||
|
||||
/**
|
||||
* Return the system ID of the item
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getSIDattribute(): string;
|
||||
public function getSIDAttribute(): string;
|
||||
}
|
76
app/Interfaces/ProductItem.php
Normal file
76
app/Interfaces/ProductItem.php
Normal file
@ -0,0 +1,76 @@
|
||||
<?php
|
||||
|
||||
namespace App\Interfaces;
|
||||
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
|
||||
interface ProductItem
|
||||
{
|
||||
/* RELATIONS */
|
||||
|
||||
/**
|
||||
* Supplier that provides this offering
|
||||
*
|
||||
* @return BelongsTo
|
||||
*/
|
||||
//public function supplier_detail(): BelongsTo;
|
||||
|
||||
/**
|
||||
* Available products created from this supplier offering
|
||||
*
|
||||
* @return BelongsToMany
|
||||
*/
|
||||
//public function types(): BelongsToMany;
|
||||
|
||||
/* ATTRIBUTES */
|
||||
|
||||
/**
|
||||
* Return the billing interval base cost including tax
|
||||
*
|
||||
* @return float
|
||||
*/
|
||||
public function getBaseCostTaxableAttribute(): float;
|
||||
|
||||
/**
|
||||
* Return the billing interval that the supplier charges
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getBillingIntervalAttribute(): int;
|
||||
|
||||
/**
|
||||
* The term that the supplier imposes on this service being connected
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getContractTermAttribute(): int;
|
||||
|
||||
/**
|
||||
* Suppliers offering name (short)
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getNameAttribute(): string;
|
||||
|
||||
/**
|
||||
* Suppliers offering name (long)
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getNameLongAttribute(): string;
|
||||
|
||||
/**
|
||||
* Return the setup cost including tax
|
||||
*
|
||||
* @return float
|
||||
*/
|
||||
public function getSetupCostTaxableAttribute(): float;
|
||||
|
||||
/**
|
||||
* Return the type of offering this is.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getTypeAttribute();
|
||||
}
|
@ -4,6 +4,9 @@ namespace App\Interfaces;
|
||||
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
/**
|
||||
* @deprecated - rename to productItem if still required
|
||||
*/
|
||||
interface ProductSupplier
|
||||
{
|
||||
/**
|
||||
@ -20,6 +23,13 @@ interface ProductSupplier
|
||||
*/
|
||||
public function allowance_string(): string;
|
||||
|
||||
/**
|
||||
* Return the contract term for this product when sold as a service
|
||||
*
|
||||
* @return int Months
|
||||
*/
|
||||
public function getContractTermAttribute(): int;
|
||||
|
||||
/**
|
||||
* Return the product cost
|
||||
*
|
||||
@ -36,4 +46,11 @@ interface ProductSupplier
|
||||
* @return mixed
|
||||
*/
|
||||
public function getSupplierAttribute();
|
||||
|
||||
/**
|
||||
* Does this offering capture usage information
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasUsage(): bool;
|
||||
}
|
90
app/Interfaces/SupplierItem.php
Normal file
90
app/Interfaces/SupplierItem.php
Normal file
@ -0,0 +1,90 @@
|
||||
<?php
|
||||
|
||||
namespace App\Interfaces;
|
||||
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
|
||||
interface SupplierItem
|
||||
{
|
||||
/* RELATIONS */
|
||||
|
||||
/**
|
||||
* Supplier that provides this offering
|
||||
*
|
||||
* @return BelongsTo
|
||||
*/
|
||||
public function supplier_detail(): BelongsTo;
|
||||
|
||||
/**
|
||||
* Available products created from this supplier offering
|
||||
*
|
||||
* @return BelongsToMany
|
||||
*/
|
||||
public function types(): BelongsToMany;
|
||||
|
||||
/* ATTRIBUTES */
|
||||
|
||||
/**
|
||||
* Return the billing interval base cost including tax
|
||||
*
|
||||
* @return float
|
||||
*/
|
||||
public function getBaseCostTaxableAttribute(): float;
|
||||
|
||||
/**
|
||||
* Return the billing interval that the supplier charges
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getBillingIntervalAttribute(): int;
|
||||
|
||||
/**
|
||||
* The term that the supplier imposes on this service being connected
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getContractTermAttribute(): int;
|
||||
|
||||
/**
|
||||
* The minimum cost of ordering this offering
|
||||
*
|
||||
* @return float
|
||||
*/
|
||||
public function getMinCostAttribute(): float;
|
||||
|
||||
/**
|
||||
* The minimum cost of ordering this offering including taxes
|
||||
*
|
||||
* @return float
|
||||
*/
|
||||
public function getMinCostTaxableAttribute(): float;
|
||||
|
||||
/**
|
||||
* Suppliers offering name (short)
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getNameAttribute(): string;
|
||||
|
||||
/**
|
||||
* Suppliers offering name (long)
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getNameLongAttribute(): string;
|
||||
|
||||
/**
|
||||
* Return the setup cost including tax
|
||||
*
|
||||
* @return float
|
||||
*/
|
||||
public function getSetupCostTaxableAttribute(): float;
|
||||
|
||||
/**
|
||||
* Return the type of offering this is.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getTypeAttribute();
|
||||
}
|
@ -7,25 +7,22 @@ use Illuminate\Database\Eloquent\Model;
|
||||
use Leenooks\Traits\ScopeActive;
|
||||
|
||||
use App\Interfaces\IDs;
|
||||
use App\Traits\NextKey;
|
||||
|
||||
/**
|
||||
* Class Account
|
||||
* Service Accounts
|
||||
*
|
||||
* Attributes for accounts:
|
||||
* + lid: : Local ID for account
|
||||
* + sid: : System ID for account
|
||||
* + name: : Account Name
|
||||
* + lid : Local ID for account
|
||||
* + sid : System ID for account
|
||||
* + name : Account Name
|
||||
* + taxes : Taxes Applicable to this account
|
||||
*
|
||||
* @package App\Models
|
||||
*/
|
||||
class Account extends Model implements IDs
|
||||
{
|
||||
use HasFactory,NextKey,ScopeActive;
|
||||
|
||||
const RECORD_ID = 'account';
|
||||
public $incrementing = FALSE;
|
||||
use HasFactory,ScopeActive;
|
||||
|
||||
const CREATED_AT = 'date_orig';
|
||||
const UPDATED_AT = 'date_last';
|
||||
@ -67,6 +64,11 @@ class Account extends Model implements IDs
|
||||
return $this->belongsToMany(External\Integrations::class,'external_account',NULL,'external_integration_id');
|
||||
}
|
||||
|
||||
public function group()
|
||||
{
|
||||
return $this->hasOneThrough(Group::class,AccountGroup::class,'account_id','id','id','group_id');
|
||||
}
|
||||
|
||||
public function invoices()
|
||||
{
|
||||
return $this->hasMany(Invoice::class);
|
||||
@ -89,6 +91,11 @@ class Account extends Model implements IDs
|
||||
return $active ? $query->active() : $query;
|
||||
}
|
||||
|
||||
public function taxes()
|
||||
{
|
||||
return $this->hasMany(Tax::class,'country_id','country_id');
|
||||
}
|
||||
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
|
11
app/Models/AccountGroup.php
Normal file
11
app/Models/AccountGroup.php
Normal file
@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class AccountGroup extends Model
|
||||
{
|
||||
protected $table = 'ab_account_group';
|
||||
public $timestamps = FALSE;
|
||||
}
|
@ -1,36 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
use App\Traits\OrderServiceOptions;
|
||||
|
||||
class AdslPlan extends Model
|
||||
{
|
||||
use OrderServiceOptions;
|
||||
|
||||
protected $table = 'ab_adsl_plan';
|
||||
|
||||
protected $order_attributes = [
|
||||
'options.address'=>[
|
||||
'request'=>'options.address',
|
||||
'key'=>'service_address',
|
||||
'validation'=>'required|string:10|unique:ab_service__adsl,service_address',
|
||||
'validation_message'=>'Address is a required field.',
|
||||
],
|
||||
'options.notes'=>[
|
||||
'request'=>'options.notes',
|
||||
'key'=>'order_info.notes',
|
||||
'validation'=>'present',
|
||||
'validation_message'=>'Special Instructions here.',
|
||||
],
|
||||
];
|
||||
|
||||
protected $order_model = Service\Adsl::class;
|
||||
|
||||
public function product()
|
||||
{
|
||||
return $this->hasOne(AdslSupplierPlan::class,'id','adsl_supplier_plan_id');
|
||||
}
|
||||
}
|
@ -7,6 +7,9 @@ use Carbon\Carbon;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
class AdslSupplier extends Model
|
||||
{
|
||||
protected $table = 'ab_adsl_supplier';
|
||||
|
@ -4,11 +4,9 @@ namespace App\Models\Base;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
use App\Models\Product;
|
||||
|
||||
//@todo column prod_plugin_file should no longer be required
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
abstract class ProductType extends Model
|
||||
{
|
||||
public $timestamps = FALSE;
|
||||
public $dateFormat = 'U';
|
||||
}
|
@ -45,6 +45,46 @@ class Invoice extends Model implements IDs
|
||||
protected $dates = ['date_orig','due_date'];
|
||||
public $dateFormat = 'U';
|
||||
|
||||
/* Our available billing periods */
|
||||
public const billing_periods = [
|
||||
0 => [
|
||||
'name' => 'Weekly',
|
||||
'interval' => 0.25,
|
||||
],
|
||||
1 => [
|
||||
'name' => 'Monthly',
|
||||
'interval' => 1,
|
||||
],
|
||||
2 => [
|
||||
'name' => 'Quarterly',
|
||||
'interval' => 3,
|
||||
],
|
||||
3 => [
|
||||
'name' => 'Semi-Annually',
|
||||
'interval' => 6,
|
||||
],
|
||||
4 => [
|
||||
'name' => 'Annually',
|
||||
'interval' => 12,
|
||||
],
|
||||
5 => [
|
||||
'name' => 'Two years',
|
||||
'interval' => 24,
|
||||
],
|
||||
6 => [
|
||||
'name' => 'Three Years',
|
||||
'interval' => 36,
|
||||
],
|
||||
7 => [
|
||||
'name' => 'Four Years',
|
||||
'interval' => 48,
|
||||
],
|
||||
8 => [
|
||||
'name' => 'Five Years',
|
||||
'interval' => 60,
|
||||
],
|
||||
];
|
||||
|
||||
// Array of items that can be updated with PushNew
|
||||
protected $pushable = ['items'];
|
||||
|
||||
@ -61,6 +101,58 @@ class Invoice extends Model implements IDs
|
||||
private int $_total = 0;
|
||||
private int $_total_tax = 0;
|
||||
|
||||
/* STATIC */
|
||||
|
||||
/**
|
||||
* This works out what multiplier to use to change billing periods
|
||||
*
|
||||
* @param int $source
|
||||
* @param int $target
|
||||
* @return float
|
||||
*/
|
||||
public static function billing_change(int $source,int $target): float
|
||||
{
|
||||
return Arr::get(self::billing_periods,$target.'.interval')/Arr::get(self::billing_periods,$source.'.interval');
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the name for the billing interval
|
||||
*
|
||||
* @param int $interval
|
||||
* @return string
|
||||
*/
|
||||
public static function billing_name(int $interval): string
|
||||
{
|
||||
$interval = collect(self::billing_periods)->get($interval);
|
||||
|
||||
return Arr::get($interval,'name','Unknown');
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the number of months in the billing interval
|
||||
*
|
||||
* @param int $interval
|
||||
* @return int
|
||||
*/
|
||||
public static function billing_period(int $interval): int
|
||||
{
|
||||
$interval = collect(self::billing_periods)->get($interval);
|
||||
|
||||
return Arr::get($interval,'interval',0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a contract in months, this will calculate the number of billing intervals required
|
||||
*
|
||||
* @param int $contract_term
|
||||
* @param int $source
|
||||
* @return int
|
||||
*/
|
||||
public static function billing_term(int $contract_term,int $source): int
|
||||
{
|
||||
return ceil(($contract_term ?: 1)/(Arr::get(self::billing_periods,$source.'.interval') ?: 1));
|
||||
}
|
||||
|
||||
/* RELATIONS */
|
||||
|
||||
public function account()
|
||||
@ -312,7 +404,7 @@ class Invoice extends Model implements IDs
|
||||
|
||||
$lo = $this->account->user->language;
|
||||
return $return->sortBy(function ($item) use ($lo) {
|
||||
return $item->name($lo);
|
||||
return $item->name;
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -1,41 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
use App\Traits\OrderServiceOptions;
|
||||
|
||||
class PlanVoip extends Model
|
||||
{
|
||||
use OrderServiceOptions;
|
||||
|
||||
protected $order_attributes = [
|
||||
'options.phonenumber'=>[
|
||||
'request'=>'options.phonenumber',
|
||||
'key'=>'service_number',
|
||||
'validation'=>'nullable|size:10|unique:ab_service__voip,service_number',
|
||||
'validation_message'=>'Phone Number is a required field.',
|
||||
],
|
||||
'options.supplier'=>[
|
||||
'request'=>'options.supplier',
|
||||
'key'=>'order_info.supplier',
|
||||
'validation'=>'required_with:options.phonenumber',
|
||||
'validation_message'=>'Phone Supplier is a required field.',
|
||||
],
|
||||
'options.supplieraccnum'=>[
|
||||
'request'=>'options.supplieraccnum',
|
||||
'key'=>'order_info.supplieraccnum',
|
||||
'validation'=>'required_with:options.phonenumber',
|
||||
'validation_message'=>'Phone Supplier Account Number is a required field.',
|
||||
],
|
||||
'options.notes'=>[
|
||||
'request'=>'options.notes',
|
||||
'key'=>'order_info.notes',
|
||||
'validation'=>'required_if:options.phonenumber,null',
|
||||
'validation_message'=>'Special Instructions here.',
|
||||
],
|
||||
];
|
||||
|
||||
protected $order_model = Service\Voip::class;
|
||||
}
|
@ -5,48 +5,84 @@ namespace App\Models;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Http\Request;
|
||||
use Leenooks\Traits\ScopeActive;
|
||||
|
||||
use App\Interfaces\IDs;
|
||||
use App\Traits\NextKey;
|
||||
use App\Traits\{ProductDetails,SiteID};
|
||||
|
||||
/**
|
||||
* Class Product
|
||||
* Products that are available to sale, and appear on invoices
|
||||
* Products that are available to sale, and appear on invoices.
|
||||
*
|
||||
* Products have one Type (Product/*), made of an Offering (Supplier/*) from a Supplier.
|
||||
* Conversely, Suppliers provide Offerings (Supplier/*) which belong to a Type (Product/*) of a Product.
|
||||
*
|
||||
* Attributes for products:
|
||||
* + lid : Local ID for product (part number)
|
||||
* + supplied : Supplier product provided for this offering
|
||||
* + supplier : Supplier for this offering
|
||||
* + name : Brief Name for our product
|
||||
* + name_short : Product ID for our Product
|
||||
* + name_long : Long Name for our product
|
||||
* + billing_interval : Default Billing Interval
|
||||
* + billing_interval_string: Default Billing Interval in human-readable form
|
||||
* + setup_charge : Charge to setup this product
|
||||
* + setup_charge_taxable : Charge to setup this product including taxes
|
||||
* + base_charge : Default billing amount
|
||||
* + base_charge_taxable : Default billing amount including taxes
|
||||
* + min_charge : Minimum cost taking into account billing interval and setup costs
|
||||
* + min_charge_taxable : Minimum cost taking into account billing interval and setup costs including taxes
|
||||
*
|
||||
* Attributes for product types (type - Product/*)
|
||||
* + name : Short Name for our Product
|
||||
* + name_long : Long Name for our Product
|
||||
* + description : Description of offering (Broadband=speed)
|
||||
*
|
||||
* Attributes for supplier's offerings (type->supplied - Supplier/*)
|
||||
* + name : Short Name for suppliers offering
|
||||
* + name_long : Long Name for suppliers offering
|
||||
* + description : Description of offering (Broadband=speed)
|
||||
*
|
||||
* Product Pricing self::pricing is an array of:
|
||||
* [
|
||||
* timeperiod => [
|
||||
* show => true|false (show this time period to the user for ordering)
|
||||
* group => [ pricing/setup ]
|
||||
* ]
|
||||
* ]
|
||||
*
|
||||
* @todo doesnt appear that price_type is used - but could be used to have different offering types billed differently
|
||||
* @package App\Models
|
||||
*/
|
||||
class Product extends Model implements IDs
|
||||
{
|
||||
use HasFactory,NextKey;
|
||||
|
||||
const RECORD_ID = 'product';
|
||||
public $incrementing = FALSE;
|
||||
|
||||
const CREATED_AT = 'date_orig';
|
||||
const UPDATED_AT = 'date_last';
|
||||
|
||||
public $dateFormat = 'U';
|
||||
protected $table = 'ab_product';
|
||||
use HasFactory,SiteID,ProductDetails,ScopeActive;
|
||||
|
||||
protected $casts = [
|
||||
// @todo convert existing data to a json array
|
||||
// 'price_group'=>'array',
|
||||
'pricing'=>'collection',
|
||||
];
|
||||
|
||||
protected $with = ['descriptions'];
|
||||
|
||||
/* RELATIONS */
|
||||
|
||||
public function descriptions()
|
||||
/**
|
||||
* Get the product name in the users language, and if the user isnt logged in, the sites language
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Relations\HasOne
|
||||
*/
|
||||
public function description()
|
||||
{
|
||||
return $this->hasMany(ProductTranslate::class);
|
||||
return $this->hasOne(ProductTranslate::class)
|
||||
->where('language_id',(Auth::user() && Auth::user()->language_id) ? Auth::user()->language_id : config('site')->language_id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Which services are configured with this product
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Relations\HasMany
|
||||
*/
|
||||
public function services()
|
||||
{
|
||||
return $this->hasMany(Service::class);
|
||||
@ -59,144 +95,203 @@ class Product extends Model implements IDs
|
||||
*/
|
||||
public function type()
|
||||
{
|
||||
return $this->morphTo(null,'model','prod_plugin_data');
|
||||
return $this->morphTo(null,'model','model_id');
|
||||
}
|
||||
|
||||
/* INTERFACES */
|
||||
|
||||
public function getLIDAttribute(): string
|
||||
{
|
||||
return sprintf('%04s',$this->id);
|
||||
}
|
||||
|
||||
public function getSIDAttribute(): string
|
||||
{
|
||||
return sprintf('%02s-%s',$this->site_id,$this->getLIDattribute());
|
||||
}
|
||||
|
||||
/* ATTRIBUTES */
|
||||
|
||||
/**
|
||||
* Get the service category (from the product)
|
||||
* The amount we invoice each time period for this service
|
||||
*
|
||||
* @return string
|
||||
* @param int|NULL $timeperiod
|
||||
* @param Group|NULL $go
|
||||
* @return float
|
||||
*/
|
||||
public function getCategoryAttribute()
|
||||
public function getBaseChargeAttribute(int $timeperiod=NULL,Group $go=NULL): float
|
||||
{
|
||||
return $this->prod_plugin_file ?: 'Other';
|
||||
}
|
||||
|
||||
public function getContractTermAttribute()
|
||||
{
|
||||
switch ($this->prod_plugin_file) {
|
||||
case 'ADSL': return $this->plugin()->contract_term;
|
||||
// @todo Incorporate into DB
|
||||
case 'VOIP': return 12;
|
||||
|
||||
// @todo Change this after contracts implemented.
|
||||
default:
|
||||
return 'TBA';
|
||||
}
|
||||
}
|
||||
|
||||
public function getDefaultBillingAttribute()
|
||||
{
|
||||
return Arr::get($this->PricePeriods(),$this->price_recurr_default);
|
||||
}
|
||||
|
||||
public function getDefaultCostAttribute()
|
||||
{
|
||||
// @todo Integrate this into a Tax::class
|
||||
return Arr::get($this->price_array,sprintf('%s.1.price_base',$this->price_recurr_default))*1.1;
|
||||
}
|
||||
|
||||
private function getDefaultLanguage()
|
||||
{
|
||||
return config('site')->language;
|
||||
}
|
||||
|
||||
public function getDescriptionAttribute()
|
||||
{
|
||||
// @todo If the user has selected a specific language.
|
||||
return $this->description($this->getDefaultLanguage());
|
||||
return $this->getCharge('base',$timeperiod,$go);
|
||||
}
|
||||
|
||||
/**
|
||||
* Product Local ID
|
||||
* The amount we invoice each time period for this service, including taxes
|
||||
*
|
||||
* @return string
|
||||
* @param int|null $timeperiod
|
||||
* @param Group|null $go
|
||||
* @param Collection|NULL $taxes
|
||||
* @return float
|
||||
*/
|
||||
public function getLIDattribute(): string
|
||||
public function getBaseChargeTaxableAttribute(int $timeperiod=NULL,Group $go=NULL,Collection $taxes=NULL): float
|
||||
{
|
||||
return sprintf('%04s',$this->id);
|
||||
}
|
||||
|
||||
public function getMinimumCostAttribute()
|
||||
{
|
||||
$table = [
|
||||
0=>4,
|
||||
1=>1,
|
||||
2=>1/3,
|
||||
3=>1/6,
|
||||
4=>1/12,
|
||||
5=>1/24,
|
||||
6=>1/36,
|
||||
7=>1/48,
|
||||
8=>1/60,
|
||||
];
|
||||
|
||||
return $this->setup_cost + ( $this->default_cost * Arr::get($table,$this->price_recurr_default) * $this->contract_term);
|
||||
}
|
||||
|
||||
public function getNameAttribute(Language $lo=NULL)
|
||||
{
|
||||
if (is_null($lo))
|
||||
$lo = $this->getDefaultLanguage();
|
||||
|
||||
return $this->descriptions->where('language_id',$lo->id)->first()->description_short;
|
||||
}
|
||||
|
||||
public function getNameShortAttribute(Language $lo=NULL)
|
||||
{
|
||||
if (is_null($lo))
|
||||
$lo = $this->getDefaultLanguage();
|
||||
|
||||
return $this->descriptions->where('language_id',$lo->id)->first()->name;
|
||||
}
|
||||
|
||||
public function getProductTypeAttribute()
|
||||
{
|
||||
return $this->plugin()->product->name;
|
||||
}
|
||||
|
||||
public function getPriceArrayAttribute()
|
||||
{
|
||||
try {
|
||||
return unserialize($this->attributes['price_group']);
|
||||
} catch (\Exception $e) {
|
||||
Log::debug('Problem with Price array in product ',['pid'=>$this->id]);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
public function getPriceTypeAttribute()
|
||||
{
|
||||
$table = [
|
||||
0=>_('One-time Charge'),
|
||||
1=>_('Recurring Membership/Subscription'),
|
||||
2=>_('Trial for Membership/Subscription'),
|
||||
];
|
||||
}
|
||||
|
||||
public function getProductIdAttribute()
|
||||
{
|
||||
return sprintf('#%04s',$this->id);
|
||||
}
|
||||
|
||||
public function getSetupCostAttribute()
|
||||
{
|
||||
// @todo Integrate this into a Tax::class
|
||||
return Arr::get($this->price_array,sprintf('%s.1.price_setup',$this->price_recurr_default))*1.1;
|
||||
return Tax::tax_calc($this->getBaseChargeAttribute($timeperiod,$go),$taxes ?: config('site')->taxes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Product System ID
|
||||
* The base cost of this product at the appropriate billing interval
|
||||
*
|
||||
* @return float
|
||||
*/
|
||||
public function getBaseCostAttribute(): float
|
||||
{
|
||||
return round($this->type->supplied->base_cost*Invoice::billing_change($this->type->supplied->getBillingIntervalAttribute(),$this->getBillingIntervalAttribute()) ?: 0,2);
|
||||
}
|
||||
|
||||
/**
|
||||
* The base cost of this product at the appropriate billing interval including taxes
|
||||
*
|
||||
* @param Collection|NULL $taxes
|
||||
* @return float
|
||||
*/
|
||||
public function getBaseCostTaxableAttribute(Collection $taxes=NULL): float
|
||||
{
|
||||
return Tax::tax_calc($this->getBaseCostAttribute(),$taxes ?: config('site')->taxes);;
|
||||
}
|
||||
|
||||
/**
|
||||
* Our default billing interval
|
||||
* Its the max of what we define, or what the supplier bills us at
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getBillingIntervalAttribute(): int
|
||||
{
|
||||
return max($this->price_recur_default,$this->type->supplied->getBillingIntervalAttribute());
|
||||
}
|
||||
|
||||
/**
|
||||
* How long must this product be purchased for as a service.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getContractTermAttribute(): int
|
||||
{
|
||||
return $this->type->getContractTermAttribute();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the minimum cost of this product
|
||||
*
|
||||
* @param int|null $timeperiod
|
||||
* @param Group|null $go
|
||||
* @return float
|
||||
*/
|
||||
public function getMinChargeAttribute(int $timeperiod=NULL,Group $go=NULL): float
|
||||
{
|
||||
return $this->getSetupChargeAttribute($timeperiod,$go)+$this->getBaseChargeAttribute($timeperiod,$go)*Invoice::billing_term($this->getContractTermAttribute(),$this->getBillingIntervalAttribute());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the minimum cost of this product with taxes
|
||||
*
|
||||
* @param int|null $timeperiod
|
||||
* @param Group|null $go
|
||||
* @param Collection|NULL $taxes
|
||||
* @return float
|
||||
*/
|
||||
public function getMinChargeTaxableAttribute(int $timeperiod=NULL,Group $go=NULL,Collection $taxes=NULL): float
|
||||
{
|
||||
return Tax::tax_calc($this->getMinChargeAttribute($timeperiod,$go),$taxes ?: config('site')->taxes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Our products short descriptive name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getSIDattribute(): string
|
||||
public function getNameAttribute(): string
|
||||
{
|
||||
return sprintf('%02s-%s',$this->site_id,$this->getLIDattribute());
|
||||
return $this->description ? $this->description->description_short : 'Unknown PRODUCT';
|
||||
}
|
||||
|
||||
/**
|
||||
* Our products PID
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getNameShortAttribute(): string
|
||||
{
|
||||
return $this->description ? $this->description->name : 'Unknown PID';
|
||||
}
|
||||
|
||||
/**
|
||||
* This product full description
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getNameLongAttribute(): string
|
||||
{
|
||||
return $this->description->description_full;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get our product type
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getProductTypeAttribute(): string
|
||||
{
|
||||
return ($this->type && $this->type->supplied) ? $this->type->supplied->getTypeAttribute() : 'Unknown';
|
||||
}
|
||||
|
||||
/**
|
||||
* The charge to setup this service
|
||||
*
|
||||
* @param int|null $timeperiod
|
||||
* @param Group|null $go
|
||||
* @return float
|
||||
*/
|
||||
public function getSetupChargeAttribute(int $timeperiod=NULL,Group $go=NULL): float
|
||||
{
|
||||
return $this->getCharge('setup',$timeperiod,$go);
|
||||
}
|
||||
|
||||
/**
|
||||
* The charge to setup this service including taxes
|
||||
*
|
||||
* @param int|null $timeperiod
|
||||
* @param Group|null $go
|
||||
* @param Collection|null $taxes
|
||||
* @return float
|
||||
*/
|
||||
public function getSetupChargeTaxableAttribute(int $timeperiod=NULL,Group $go=NULL,Collection $taxes=NULL): float
|
||||
{
|
||||
return Tax::tax_calc($this->getSetupChargeAttribute($timeperiod,$go),$taxes ?: config('site')->taxes);
|
||||
}
|
||||
|
||||
/**
|
||||
* The charge to setup this service
|
||||
*
|
||||
* @return float
|
||||
*/
|
||||
public function getSetupCostAttribute(): float
|
||||
{
|
||||
return $this->type->supplied->setup_cost ?: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* The charge to setup this service
|
||||
*
|
||||
* @param Collection|null $taxes
|
||||
* @return float
|
||||
*/
|
||||
public function getSetupCostTaxableAttribute(Collection $taxes=NULL): float
|
||||
{
|
||||
return Tax::tax_calc($this->getSetupCostAttribute(),$taxes ?: config('site')->taxes);;
|
||||
}
|
||||
|
||||
/* METHODS */
|
||||
|
||||
/**
|
||||
* Return if this product captures usage data
|
||||
*
|
||||
@ -204,81 +299,49 @@ class Product extends Model implements IDs
|
||||
*/
|
||||
public function hasUsage(): bool
|
||||
{
|
||||
// @todo This should be configured in the DB
|
||||
return in_array($this->model, ['App\Models\Product\Adsl']);
|
||||
}
|
||||
|
||||
public function scopeActive()
|
||||
{
|
||||
return $this->where('active',TRUE);
|
||||
}
|
||||
|
||||
public function description(Language $lo=NULL)
|
||||
{
|
||||
if (is_null($lo))
|
||||
$lo = $this->getDefaultLanguage();
|
||||
|
||||
return $this->descriptions->where('language_id',$lo->id)->first()->description_full;
|
||||
}
|
||||
|
||||
public function orderValidation(Request $request)
|
||||
{
|
||||
return $this->plugin()->orderValidation($request);
|
||||
}
|
||||
|
||||
private function plugin()
|
||||
{
|
||||
switch ($this->prod_plugin_file) {
|
||||
case 'ADSL':
|
||||
return AdslPlan::findOrFail($this->prod_plugin_data);
|
||||
case 'VOIP':
|
||||
return new PlanVoip;
|
||||
}
|
||||
return $this->type->supplied->hasUsage();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the price for this product based on the period being requested.
|
||||
* Get a charge value from the pricing array
|
||||
*
|
||||
* If the price period doesnt exist, we'll take the default period (0) which should.
|
||||
* @param string $type
|
||||
* @param int|NULL $timeperiod
|
||||
* @param Group|NULL $go
|
||||
* @return float
|
||||
*/
|
||||
private function getCharge(string $type,int $timeperiod=NULL,Group $go=NULL): float
|
||||
{
|
||||
static $default = NULL;
|
||||
if (! $go) {
|
||||
if (is_null($default))
|
||||
$default = Group::findOrFail(0); // All public users
|
||||
|
||||
$go = $default;
|
||||
}
|
||||
|
||||
if (is_null($timeperiod))
|
||||
$timeperiod = $this->getBillingIntervalAttribute();
|
||||
|
||||
// If the price doesnt exist for $go->id, use $go->id = 0 which is all users.
|
||||
if (! $price=Arr::get($this->pricing,sprintf('%d.%d.%s',$timeperiod,$go->id,$type)))
|
||||
$price = Arr::get($this->pricing,sprintf('%d.%d.%s',$timeperiod,0,$type));
|
||||
|
||||
// @todo - if price doesnt exist for the time period, reduce down to timeperiod 1 and multiply appropriately.
|
||||
if (is_null($price))
|
||||
abort(500,sprintf('Price is NULL, we need to find it timeperiod[%s] group[%s]',$timeperiod,$go->id));
|
||||
|
||||
return round($price,2);
|
||||
}
|
||||
|
||||
/**
|
||||
* When receiving an order, validate that we have all the required information for the product type
|
||||
*
|
||||
* @param int $period
|
||||
* @param Request $request
|
||||
* @return mixed
|
||||
*/
|
||||
public function price(int $period,string $key='price_base')
|
||||
public function orderValidation(Request $request): ?Model
|
||||
{
|
||||
return Arr::get(
|
||||
$this->price_array,
|
||||
sprintf('%s.1.%s',$period,$key),
|
||||
Arr::get($this->price_array,sprintf('%s.0.%s',$period,$key))
|
||||
);
|
||||
}
|
||||
|
||||
public function PricePeriods()
|
||||
{
|
||||
return [
|
||||
0=>_('Weekly'),
|
||||
1=>_('Monthly'),
|
||||
2=>_('Quarterly'),
|
||||
3=>_('Semi-Annually'),
|
||||
4=>_('Annually'),
|
||||
5=>_('Two years'),
|
||||
6=>_('Three Years'),
|
||||
7=>_('Four Years'),
|
||||
8=>_('Five Years'),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the product name
|
||||
*
|
||||
* @param Language $lo
|
||||
* @return string Product Name
|
||||
*/
|
||||
public function name(Language $lo=NULL)
|
||||
{
|
||||
if (is_null($lo))
|
||||
$lo = $this->getDefaultLanguage();
|
||||
|
||||
return $this->descriptions->where('language_id',$lo->id)->first()->name;
|
||||
return $this->type->orderValidation($request);
|
||||
}
|
||||
}
|
@ -1,159 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\Product;
|
||||
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
use App\Interfaces\ProductSupplier;
|
||||
use App\Models\Base\ProductType;
|
||||
use App\Models\AdslSupplier;
|
||||
use App\Models\AdslSupplierPlan;
|
||||
use App\Traits\NextKey;
|
||||
|
||||
class Adsl extends ProductType implements ProductSupplier
|
||||
{
|
||||
use NextKey;
|
||||
const RECORD_ID = 'adsl_plan';
|
||||
|
||||
protected $table = 'ab_adsl_plan';
|
||||
|
||||
public static $map = [
|
||||
'base_up_offpeak'=>'extra_up_offpeak',
|
||||
'base_down_offpeak'=>'extra_down_offpeak',
|
||||
'base_up_peak'=>'extra_up_peak',
|
||||
'base_down_peak'=>'extra_down_peak',
|
||||
];
|
||||
|
||||
/**
|
||||
* Map upstream metrics into traffic allowance metrics
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $metrics = [
|
||||
'down_peak'=>'base_down_peak',
|
||||
'down_offpeak'=>'base_down_offpeak',
|
||||
'up_peak'=>'base_up_peak',
|
||||
'up_offpeak'=>'base_up_offpeak',
|
||||
'peer'=>'base_down_peak',
|
||||
'internal'=>'base_down_offpeak',
|
||||
];
|
||||
|
||||
/**
|
||||
* The suppliers product
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Relations\HasOne
|
||||
*/
|
||||
public function product()
|
||||
{
|
||||
return $this->hasOne(AdslSupplierPlan::class,'id','adsl_supplier_plan_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* The supplier
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Relations\HasOneThrough
|
||||
*/
|
||||
public function supplier()
|
||||
{
|
||||
return $this->hasOneThrough(AdslSupplier::class,AdslSupplierPlan::class,'id','id','adsl_supplier_plan_id','supplier_id');
|
||||
}
|
||||
|
||||
public function __get($key)
|
||||
{
|
||||
switch($key) {
|
||||
case 'speed':
|
||||
return $this->product->speed;
|
||||
}
|
||||
|
||||
// If we dont have a specific key, we'll resolve it normally
|
||||
return parent::__get($key);
|
||||
}
|
||||
|
||||
/** ATTRIBUTES **/
|
||||
|
||||
/**
|
||||
* Calculate the allowance array or traffic used array
|
||||
*
|
||||
* @param array Traffic Used in each metric.
|
||||
* @param bool $ceil Round the numbers to integers
|
||||
* @return array|string
|
||||
*/
|
||||
public function allowance(array $data=[],bool $ceil=TRUE): Collection
|
||||
{
|
||||
$config = collect();
|
||||
|
||||
// Base Config
|
||||
foreach (array_keys(static::$map) as $k) {
|
||||
$config->put($k,$this->{$k});
|
||||
}
|
||||
|
||||
// Excess Config
|
||||
foreach (array_values(static::$map) as $k) {
|
||||
$config->put($k,$this->{$k});
|
||||
}
|
||||
|
||||
// Shaped or Charge
|
||||
$config->put('shaped',$this->extra_shaped);
|
||||
$config->put('charged',$this->extra_charged);
|
||||
|
||||
// Metric - used to round down data in $data.
|
||||
$config->put('metric',$this->metric);
|
||||
|
||||
return $this->product->allowance($config,$data,$ceil);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the suppliers cost for this service
|
||||
*
|
||||
* @return float
|
||||
*/
|
||||
public function allowance_cost(): float
|
||||
{
|
||||
$result = 0;
|
||||
foreach ($this->product->allowance(NULL,$this->allowance([])->toArray()) as $k=>$v) {
|
||||
$result += -$v*$this->product->{static::$map[$k]};
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the allowance as a string
|
||||
* eg: 50/100
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function allowance_string(): string
|
||||
{
|
||||
$result = '';
|
||||
$data = $this->allowance();
|
||||
|
||||
foreach ([
|
||||
'base_down_peak',
|
||||
'base_up_peak',
|
||||
'base_down_offpeak',
|
||||
'base_up_offpeak',
|
||||
] as $k)
|
||||
{
|
||||
if ($data->has($k)) {
|
||||
if ($result)
|
||||
$result .= '/';
|
||||
|
||||
$result .= $data->get($k);
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function getCostAttribute(): float
|
||||
{
|
||||
// @todo Tax shouldnt be hard coded
|
||||
return ($this->product->base_cost+$this->allowance_cost())*1.1;
|
||||
}
|
||||
|
||||
public function getSupplierAttribute()
|
||||
{
|
||||
return $this->getRelationValue('supplier');
|
||||
}
|
||||
}
|
178
app/Models/Product/Broadband.php
Normal file
178
app/Models/Product/Broadband.php
Normal file
@ -0,0 +1,178 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\Product;
|
||||
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
use App\Interfaces\ProductSupplier;
|
||||
use App\Models\Base\ProductType;
|
||||
use App\Models\{Product,Supplier};
|
||||
use App\Models\Service\Broadband as ServiceBroadband;
|
||||
use App\Models\Supplier\Broadband as SupplierBroadband;
|
||||
use App\Traits\{OrderServiceOptions,SiteID};
|
||||
|
||||
class Broadband extends ProductType implements ProductSupplier
|
||||
{
|
||||
use SiteID;
|
||||
use OrderServiceOptions;
|
||||
|
||||
protected $table = 'product_broadband';
|
||||
|
||||
// Information required during the order process
|
||||
private array $order_attributes = [
|
||||
'options.address'=>[
|
||||
'request'=>'options.address',
|
||||
'key'=>'service_address',
|
||||
'validation'=>'required|string:10|unique:ab_service__adsl,service_address',
|
||||
'validation_message'=>'Address is a required field.',
|
||||
],
|
||||
'options.notes'=>[
|
||||
'request'=>'options.notes',
|
||||
'key'=>'order_info.notes',
|
||||
'validation'=>'present',
|
||||
'validation_message'=>'Special Instructions here.',
|
||||
],
|
||||
];
|
||||
|
||||
protected string $order_model = ServiceBroadband::class;
|
||||
|
||||
/* RELATIONS */
|
||||
|
||||
/**
|
||||
* The product that sells this type
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Relations\MorphOne
|
||||
*/
|
||||
public function product()
|
||||
{
|
||||
return $this->morphOne(Product::class, null,'model','model_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* The offering supplied with this product
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Relations\HasOne
|
||||
*/
|
||||
public function supplied()
|
||||
{
|
||||
return $this->hasOne(SupplierBroadband::class,'id','supplier_broadband_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* The supplier
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Relations\HasOneThrough
|
||||
*/
|
||||
// @todo To check
|
||||
public function supplier()
|
||||
{
|
||||
return $this->hasOneThrough(Supplier::class,SupplierBroadband::class,'id','id','adsl_supplier_plan_id','supplier_id');
|
||||
}
|
||||
|
||||
/* INTERFACES */
|
||||
|
||||
/**
|
||||
* Calculate the allowance array or traffic used array
|
||||
*
|
||||
* @param array Traffic Used in each metric.
|
||||
* @param bool $ceil Round the numbers to integers
|
||||
* @return array|string
|
||||
*/
|
||||
public function allowance(array $data=[],bool $ceil=TRUE): Collection
|
||||
{
|
||||
$config = collect();
|
||||
|
||||
foreach (array_keys(Supplier\Broadband::traffic_map) as $k => $v) {
|
||||
// Base Config
|
||||
$config->put($k,$this->{$k});
|
||||
// Excess Config
|
||||
$config->put($v,$this->{$v});
|
||||
}
|
||||
|
||||
// Shaped or Charge
|
||||
$config->put('shaped',$this->extra_shaped);
|
||||
$config->put('charged',$this->extra_charged);
|
||||
|
||||
// Metric - used to round down data in $data.
|
||||
$config->put('metric',$this->metric);
|
||||
|
||||
return $this->supplied->allowance($config,$data,$ceil);
|
||||
}
|
||||
|
||||
/* ATTRIBUTES */
|
||||
|
||||
/**
|
||||
* Return the suppliers cost for this service
|
||||
*
|
||||
* @return float
|
||||
*/
|
||||
// @todo To check
|
||||
public function allowance_cost(): float
|
||||
{
|
||||
$result = 0;
|
||||
foreach ($this->supplied->allowance(NULL,$this->allowance([])->toArray()) as $k=>$v) {
|
||||
$result += -$v*$this->supplied->{Supplier\Broadband::traffic_map[$k]};
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the allowance as a string
|
||||
* eg: 50/100
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function allowance_string(): string
|
||||
{
|
||||
$result = '';
|
||||
$data = $this->allowance();
|
||||
|
||||
foreach ([
|
||||
'base_down_peak',
|
||||
'base_up_peak',
|
||||
'base_down_offpeak',
|
||||
'base_up_offpeak',
|
||||
] as $k)
|
||||
{
|
||||
if ($data->has($k)) {
|
||||
if ($result)
|
||||
$result .= '/';
|
||||
|
||||
$result .= $data->get($k);
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* The product contract term is the highest of
|
||||
* + This defined contract_term
|
||||
* + The suppliers contract_term
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getContractTermAttribute(): int
|
||||
{
|
||||
return max($this->attributes['contract_term'],$this->supplied->getContractTermAttribute());
|
||||
}
|
||||
|
||||
public function getCostAttribute(): float
|
||||
{
|
||||
abort(500,'deprecated');
|
||||
// @todo Tax shouldnt be hard coded
|
||||
return ($this->supplied->base_cost+$this->allowance_cost())*1.1;
|
||||
}
|
||||
|
||||
public function getSupplierAttribute()
|
||||
{
|
||||
abort(500,'deprecated');
|
||||
return $this->getRelationValue('supplier');
|
||||
}
|
||||
|
||||
public function hasUsage(): bool
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
}
|
@ -25,6 +25,11 @@ class Domain extends ProductType implements ProductSupplier
|
||||
return '';
|
||||
}
|
||||
|
||||
public function getContractTermAttribute(): int
|
||||
{
|
||||
return 12;
|
||||
}
|
||||
|
||||
public function getCostAttribute(): float
|
||||
{
|
||||
// N/A
|
||||
@ -35,4 +40,14 @@ class Domain extends ProductType implements ProductSupplier
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
public function getTypeAttribute()
|
||||
{
|
||||
return 'Domain Name';
|
||||
}
|
||||
|
||||
public function hasUsage(): bool
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
}
|
@ -6,4 +6,18 @@ use App\Models\Base\ProductType;
|
||||
|
||||
class Generic extends ProductType
|
||||
{
|
||||
public function getContractTermAttribute(): int
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
public function getTypeAttribute()
|
||||
{
|
||||
return 'Generic';
|
||||
}
|
||||
|
||||
public function hasUsage(): bool
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
@ -9,4 +9,19 @@ class Host extends ProductType
|
||||
{
|
||||
use NextKey;
|
||||
const RECORD_ID = '';
|
||||
|
||||
public function getContractTermAttribute(): int
|
||||
{
|
||||
return 12;
|
||||
}
|
||||
|
||||
public function getTypeAttribute()
|
||||
{
|
||||
return 'Hosting';
|
||||
}
|
||||
|
||||
public function hasUsage(): bool
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
}
|
@ -27,6 +27,11 @@ class SSL extends ProductType implements ProductSupplier
|
||||
return '';
|
||||
}
|
||||
|
||||
public function getContractTermAttribute(): int
|
||||
{
|
||||
return 12;
|
||||
}
|
||||
|
||||
public function getCostAttribute(): float
|
||||
{
|
||||
// N/A
|
||||
@ -51,4 +56,14 @@ class SSL extends ProductType implements ProductSupplier
|
||||
|
||||
return $o;
|
||||
}
|
||||
|
||||
public function getTypeAttribute()
|
||||
{
|
||||
return 'SSL Certificate';
|
||||
}
|
||||
|
||||
public function hasUsage(): bool
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
}
|
@ -4,9 +4,56 @@ namespace App\Models\Product;
|
||||
|
||||
use App\Models\Base\ProductType;
|
||||
use App\Traits\NextKey;
|
||||
use App\Traits\OrderServiceOptions;
|
||||
|
||||
class Voip extends ProductType
|
||||
{
|
||||
use NextKey;
|
||||
const RECORD_ID = '';
|
||||
|
||||
use OrderServiceOptions;
|
||||
|
||||
protected $order_attributes = [
|
||||
'options.phonenumber'=>[
|
||||
'request'=>'options.phonenumber',
|
||||
'key'=>'service_number',
|
||||
'validation'=>'nullable|size:10|unique:ab_service__voip,service_number',
|
||||
'validation_message'=>'Phone Number is a required field.',
|
||||
],
|
||||
'options.supplier'=>[
|
||||
'request'=>'options.supplier',
|
||||
'key'=>'order_info.supplier',
|
||||
'validation'=>'required_with:options.phonenumber',
|
||||
'validation_message'=>'Phone Supplier is a required field.',
|
||||
],
|
||||
'options.supplieraccnum'=>[
|
||||
'request'=>'options.supplieraccnum',
|
||||
'key'=>'order_info.supplieraccnum',
|
||||
'validation'=>'required_with:options.phonenumber',
|
||||
'validation_message'=>'Phone Supplier Account Number is a required field.',
|
||||
],
|
||||
'options.notes'=>[
|
||||
'request'=>'options.notes',
|
||||
'key'=>'order_info.notes',
|
||||
'validation'=>'required_if:options.phonenumber,null',
|
||||
'validation_message'=>'Special Instructions here.',
|
||||
],
|
||||
];
|
||||
|
||||
protected $order_model = \App\Models\Service\Voip::class;
|
||||
|
||||
public function getContractTermAttribute(): int
|
||||
{
|
||||
return 12;
|
||||
}
|
||||
|
||||
public function getTypeAttribute()
|
||||
{
|
||||
return 'VOIP';
|
||||
}
|
||||
|
||||
public function hasUsage(): bool
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
}
|
@ -18,14 +18,15 @@ use Leenooks\Carbon;
|
||||
use Symfony\Component\HttpKernel\Exception\HttpException;
|
||||
|
||||
use App\Interfaces\IDs;
|
||||
use App\Traits\NextKey;
|
||||
|
||||
/**
|
||||
* Class Service
|
||||
* Services that belong to an account
|
||||
*
|
||||
* Attributes for services:
|
||||
* + billing_period : The period that this service is billed for by default
|
||||
* + additional_cost : Pending additional charges for this service (excluding setup)
|
||||
* + billing_cost : Charge for this service each invoice period
|
||||
* + billing_interval : The period that this service is billed for by default
|
||||
* + name : Service short name with service address
|
||||
* + name_short : Service Product short name, eg: phone number, domain name, certificate CN
|
||||
* + name_detail : Service Detail, eg: service_address
|
||||
@ -33,9 +34,10 @@ use App\Traits\NextKey;
|
||||
*
|
||||
* @package App\Models
|
||||
*/
|
||||
// @todo All the methods/attributes in this file need to be checked.
|
||||
class Service extends Model implements IDs
|
||||
{
|
||||
use NextKey,HasFactory;
|
||||
use HasFactory;
|
||||
|
||||
const RECORD_ID = 'service';
|
||||
public $incrementing = FALSE;
|
||||
@ -85,6 +87,7 @@ class Service extends Model implements IDs
|
||||
'status',
|
||||
];
|
||||
|
||||
/*
|
||||
protected $with = [
|
||||
'account.language',
|
||||
'charges',
|
||||
@ -92,6 +95,7 @@ class Service extends Model implements IDs
|
||||
'product',
|
||||
'type',
|
||||
];
|
||||
*/
|
||||
|
||||
// @todo Change to self::INACTIVE_STATUS
|
||||
private $inactive_status = [
|
||||
@ -370,7 +374,6 @@ class Service extends Model implements IDs
|
||||
* Product of the service
|
||||
*
|
||||
* @return BelongsTo
|
||||
* @deprecated use type->product
|
||||
*/
|
||||
public function product()
|
||||
{
|
||||
@ -491,10 +494,10 @@ class Service extends Model implements IDs
|
||||
public function getBillingPriceAttribute(): float
|
||||
{
|
||||
// @todo Temporary for services that dont have recur_schedule set.
|
||||
if (is_null($this->recur_schedule) OR is_null($this->product->price($this->recur_schedule)))
|
||||
if (is_null($this->recur_schedule) OR is_null($this->product->getBaseChargeAttribute($this->recur_schedule,$this->account->group)))
|
||||
$this->price=0;
|
||||
|
||||
return $this->addTax(is_null($this->price) ? $this->product->price($this->recur_schedule) : $this->price);
|
||||
return $this->addTax(is_null($this->price) ? $this->product->getBaseChargeAttribute($this->recur_schedule,$this->account->group) : $this->price);
|
||||
}
|
||||
|
||||
public function getBillingMonthlyPriceAttribute(): float
|
||||
@ -516,11 +519,32 @@ class Service extends Model implements IDs
|
||||
/**
|
||||
* Return the service billing period
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getBillingIntervalAttribute(): int
|
||||
{
|
||||
return $this->recur_schedule ?: $this->product->getBillingIntervalAttribute();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a human friendly name for the billing interval
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getBillingPeriodAttribute(): string
|
||||
public function getBillingIntervalStringAttribute(): string
|
||||
{
|
||||
return Arr::get($this->product->PricePeriods(),$this->recur_schedule,'Unknown');
|
||||
return Invoice::billing_name($this->getBillingIntervalAttribute());
|
||||
}
|
||||
|
||||
/**
|
||||
* This function will determine the minimum contract term for a service, which is the maximum of
|
||||
* this::type->contract_term, or the product->type->contract_term();
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getContractTermAttribute(): int
|
||||
{
|
||||
abort(500,'To implement (Dec 2021)');
|
||||
}
|
||||
|
||||
/**
|
||||
@ -561,42 +585,42 @@ class Service extends Model implements IDs
|
||||
{
|
||||
switch ($this->recur_schedule) {
|
||||
// Weekly
|
||||
case 0: $date = $this->product->price_recurr_strict
|
||||
case 0: $date = $this->product->price_recur_strict
|
||||
? $this->getInvoiceNextAttribute()->endOfWeek()
|
||||
: $this->getInvoiceNextAttribute()->addWeek()->subDay();
|
||||
break;
|
||||
|
||||
// Monthly
|
||||
case 1:
|
||||
$date = $this->product->price_recurr_strict
|
||||
$date = $this->product->price_recur_strict
|
||||
? $this->getInvoiceNextAttribute()->endOfMonth()
|
||||
: $this->getInvoiceNextAttribute()->addMonth()->subDay();
|
||||
break;
|
||||
|
||||
// Quarterly
|
||||
case 2:
|
||||
$date = $this->product->price_recurr_strict
|
||||
$date = $this->product->price_recur_strict
|
||||
? $this->getInvoiceNextAttribute()->endOfQuarter()
|
||||
: $this->getInvoiceNextAttribute()->addQuarter()->subDay();
|
||||
break;
|
||||
|
||||
// Half Yearly
|
||||
case 3:
|
||||
$date = $this->product->price_recurr_strict
|
||||
$date = $this->product->price_recur_strict
|
||||
? $this->getInvoiceNextAttribute()->endOfHalf()
|
||||
: $this->getInvoiceNextAttribute()->addQuarter(2)->subDay();
|
||||
break;
|
||||
|
||||
// Yearly
|
||||
case 4:
|
||||
$date = $this->product->price_recurr_strict
|
||||
$date = $this->product->price_recur_strict
|
||||
? $this->getInvoiceNextAttribute()->endOfYear()
|
||||
: $this->getInvoiceNextAttribute()->addYear()->subDay();
|
||||
break;
|
||||
|
||||
// Two Yearly
|
||||
case 5:
|
||||
if (!$this->product->price_recurr_strict)
|
||||
if (!$this->product->price_recur_strict)
|
||||
$date = $this->getInvoiceNextAttribute()->addYear(2)->subDay();
|
||||
else {
|
||||
$date = $this->getInvoiceNextAttribute()->addYear(2)->subDay()->endOfYear();
|
||||
@ -608,7 +632,7 @@ class Service extends Model implements IDs
|
||||
break;
|
||||
|
||||
// Three Yearly
|
||||
// NOTE: price_recurr_strict ignored
|
||||
// NOTE: price_recur_strict ignored
|
||||
case 6: $date = $this->getInvoiceNextAttribute()->addYear(3)->subDay(); break;
|
||||
|
||||
default: throw new Exception('Unknown recur_schedule');
|
||||
@ -624,7 +648,7 @@ class Service extends Model implements IDs
|
||||
public function getInvoiceNextQuantityAttribute()
|
||||
{
|
||||
// If we are not rounding to the first day of the cycle, then it is always a full cycle
|
||||
if (! $this->product->price_recurr_strict)
|
||||
if (! $this->product->price_recur_strict)
|
||||
return 1;
|
||||
|
||||
$n = $this->invoice_next->diff($this->invoice_next_end)->days+1;
|
||||
@ -759,11 +783,12 @@ class Service extends Model implements IDs
|
||||
/**
|
||||
* Get the Product's Category for this service
|
||||
*
|
||||
* @deprecated use product->category directly
|
||||
* @deprecated use product->getProductTypeAttribute() directly
|
||||
*/
|
||||
public function getProductCategoryAttribute(): string
|
||||
{
|
||||
return $this->product->category;
|
||||
abort(500,'deprecated');
|
||||
return $this->product->getProductTypeAttribute();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -774,6 +799,7 @@ class Service extends Model implements IDs
|
||||
*/
|
||||
public function getProductNameAttribute(): string
|
||||
{
|
||||
abort(500,'deprecated');
|
||||
return $this->product->name($this->account->language);
|
||||
}
|
||||
|
||||
@ -860,7 +886,7 @@ class Service extends Model implements IDs
|
||||
public function getSTypeAttribute(): string
|
||||
{
|
||||
switch($this->product->model) {
|
||||
case 'App\Models\Product\Adsl': return 'broadband';
|
||||
case 'App\Models\Product\Broadband': return 'broadband';
|
||||
default: return $this->type->type;
|
||||
}
|
||||
}
|
||||
@ -918,6 +944,18 @@ class Service extends Model implements IDs
|
||||
: $this->status;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return the type of service is provided.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getServiceTypeAttribute(): string
|
||||
{
|
||||
// @todo This is temporary, while we clean the database.
|
||||
return ($this->product->type && $this->product->type->supplied) ? $this->product->type->supplied->getTypeAttribute() : '** TBA **';
|
||||
}
|
||||
|
||||
/**
|
||||
* URL used by an admin to administer the record
|
||||
*
|
||||
@ -1275,7 +1313,7 @@ class Service extends Model implements IDs
|
||||
// Connection charges are only charged once
|
||||
if ((! $this->invoice_items->filter(function($item) { return $item->item_type==4; })->sum('total'))
|
||||
AND ($this->isPending() OR is_null($this->invoice_to))
|
||||
AND $this->product->price($this->recur_schedule,'price_setup'))
|
||||
AND $this->product->getSetupChargeAttribute($this->recur_schedule,$this->account->group))
|
||||
{
|
||||
$o = new InvoiceItem;
|
||||
|
||||
@ -1283,7 +1321,7 @@ class Service extends Model implements IDs
|
||||
$o->service_id = $this->id;
|
||||
$o->product_id = $this->product_id;
|
||||
$o->item_type = 4; // @todo change to const or something
|
||||
$o->price_base = $this->product->price($this->recur_schedule,'price_setup'); // @todo change to a method in this class
|
||||
$o->price_base = $this->product->getSetupChargeAttribute($this->recur_schedule,$this->account->group);
|
||||
//$o->recurring_schedule = $this->recur_schedule;
|
||||
$o->date_start = $this->invoice_next;
|
||||
$o->date_stop = $this->invoice_next;
|
||||
@ -1309,7 +1347,7 @@ class Service extends Model implements IDs
|
||||
$o->product_id = $this->product_id;
|
||||
$o->item_type = 0;
|
||||
$o->price_base = is_null($this->price)
|
||||
? (is_null($this->price_override) ? $this->product->price($this->recur_schedule) : $this->price_override)
|
||||
? (is_null($this->price_override) ? $this->product->getBaseChargeAttribute($this->recur_schedule,$this->account->group) : $this->price_override)
|
||||
: $this->price; // @todo change to a method in this class
|
||||
$o->recurring_schedule = $this->recur_schedule;
|
||||
$o->date_start = $this->invoice_next;
|
||||
@ -1354,6 +1392,7 @@ class Service extends Model implements IDs
|
||||
*/
|
||||
private function ServicePlugin()
|
||||
{
|
||||
abort(500,'deprecated');
|
||||
// @todo: All services should be linked to a product. This might require data cleaning for old services not linked to a product.
|
||||
if (! is_object($this->product))
|
||||
return NULL;
|
||||
|
@ -3,17 +3,15 @@
|
||||
namespace App\Models\Service;
|
||||
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Leenooks\Carbon;
|
||||
|
||||
use App\Interfaces\{ServiceItem,ServiceUsage};
|
||||
use App\Models\AdslSupplierPlan;
|
||||
use App\Models\Base\ServiceType;
|
||||
use App\Models\Supplier\Broadband as SupplierBroadband;
|
||||
use App\Traits\NextKey;
|
||||
|
||||
class Adsl extends ServiceType implements ServiceItem,ServiceUsage
|
||||
class Broadband extends ServiceType implements ServiceItem,ServiceUsage
|
||||
{
|
||||
private const LOGKEY = 'MSA';
|
||||
|
||||
@ -26,21 +24,7 @@ class Adsl extends ServiceType implements ServiceItem,ServiceUsage
|
||||
];
|
||||
protected $table = 'ab_service__adsl';
|
||||
|
||||
/** RELATIONSHIPS **/
|
||||
|
||||
/**
|
||||
* The suppliers product
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Relations\HasOne
|
||||
*/
|
||||
public function product()
|
||||
{
|
||||
return $this
|
||||
->hasOne(AdslSupplierPlan::class,'id','adsl_supplier_plan_id')
|
||||
->withDefault(function() {
|
||||
$o = new AdslSupplierPlan;
|
||||
});
|
||||
}
|
||||
/* RELATIONS */
|
||||
|
||||
/**
|
||||
* The accounts that this user manages
|
||||
@ -53,7 +37,7 @@ class Adsl extends ServiceType implements ServiceItem,ServiceUsage
|
||||
return $this->hasMany(AdslTraffic::class,'ab_service_adsl_id');
|
||||
}
|
||||
|
||||
/** SCOPES */
|
||||
/* SCOPES */
|
||||
|
||||
/**
|
||||
* Search for a record
|
||||
@ -71,12 +55,13 @@ class Adsl extends ServiceType implements ServiceItem,ServiceUsage
|
||||
->orWhere('ipaddress','like','%'.$term.'%');
|
||||
}
|
||||
|
||||
/** ATTRIBUTES **/
|
||||
|
||||
/**
|
||||
* @deprecated use $o->service_name;
|
||||
* @return mixed|string
|
||||
*/
|
||||
|
||||
/* ATTRIBUTES */
|
||||
|
||||
public function getNameAttribute()
|
||||
{
|
||||
return $this->service_number ?: $this->service_address;
|
||||
@ -107,6 +92,8 @@ class Adsl extends ServiceType implements ServiceItem,ServiceUsage
|
||||
return $this->service_number ?: $this->service_address;
|
||||
}
|
||||
|
||||
/* METHODS */
|
||||
|
||||
/**
|
||||
* Is this service currently in a contract
|
||||
*
|
||||
@ -117,6 +104,18 @@ class Adsl extends ServiceType implements ServiceItem,ServiceUsage
|
||||
return $this->service_contract_date AND $this->service_contract_date->addMonths($this->contract_term)->isFuture();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the suppliers offering that this service is providing
|
||||
*
|
||||
* @return SupplierBroadband
|
||||
*/
|
||||
public function supplied(): SupplierBroadband
|
||||
{
|
||||
return $this->provided_adsl_plan_id
|
||||
? SupplierBroadband::findOrFail($this->provided_adsl_plan_id)
|
||||
: $this->service->product->type->supplied;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return service usage data
|
||||
*
|
@ -67,7 +67,7 @@ class SSL extends ServiceType implements ServiceItem
|
||||
{
|
||||
return $this->cert
|
||||
? Arr::get($this->crt_parse,'subject.CN')
|
||||
: Arr::get(openssl_csr_get_subject($this->csr),'CN');
|
||||
: Arr::get(openssl_csr_get_subject($this->csr),'CN','');
|
||||
}
|
||||
|
||||
public function inContract(): bool
|
||||
|
@ -58,6 +58,11 @@ class Site extends Model
|
||||
return $this->belongsTo(Language::class);
|
||||
}
|
||||
|
||||
public function taxes()
|
||||
{
|
||||
return $this->hasMany(Tax::class,'country_id','country_id');
|
||||
}
|
||||
|
||||
/* ATTRIBUTES */
|
||||
|
||||
/**
|
||||
|
@ -3,18 +3,93 @@
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Collection;
|
||||
use Leenooks\Traits\ScopeActive;
|
||||
|
||||
use App\Models\Supplier\{Broadband,Ethernet,HSPA};
|
||||
|
||||
class Supplier extends Model
|
||||
{
|
||||
use ScopeActive;
|
||||
|
||||
public $timestamps = FALSE;
|
||||
|
||||
/* The offerings we provide */
|
||||
public const offering_types = [
|
||||
'broadband' => [
|
||||
'name' => 'Broadband',
|
||||
'class' => Broadband::class,
|
||||
],
|
||||
'hspa' => [
|
||||
'name' => 'Mobile Broadband',
|
||||
'class' => HSPA::class,
|
||||
],
|
||||
'ethernet' => [
|
||||
'name' => 'Ethernet Broadband',
|
||||
'class' => Ethernet::class,
|
||||
],
|
||||
'domainname' => [
|
||||
'name' => 'Domain Name',
|
||||
//'class' => Domain::class,
|
||||
],
|
||||
'generic' => [
|
||||
'name' => 'Generic',
|
||||
//'class' => Generic::class,
|
||||
],
|
||||
'hosting' => [
|
||||
'name' => 'Hosting',
|
||||
//'class' => Host::class,
|
||||
],
|
||||
'voip' => [
|
||||
'name' => 'VOIP Telephone',
|
||||
//'class' => Voip::class,
|
||||
],
|
||||
];
|
||||
|
||||
/* RELATIONS */
|
||||
|
||||
public function detail()
|
||||
{
|
||||
return $this->hasOne(SupplierDetail::class);
|
||||
}
|
||||
|
||||
/* METHODS */
|
||||
|
||||
/**
|
||||
* Return the offerings that this supplier provides
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function offeringTypes(): Collection
|
||||
{
|
||||
$result = collect();
|
||||
|
||||
// See if we have any configurations
|
||||
foreach (self::offering_types as $key => $type) {
|
||||
if (! $class = Arr::get($type,'class'))
|
||||
continue;
|
||||
|
||||
if (Arr::get($this->detail->connections,$key)) {
|
||||
$result->put($key,(object)[
|
||||
'type' => Arr::get($type,'name'),
|
||||
'items' => (new $class)->where('supplier_detail_id',$this->detail->id),
|
||||
]);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// See if we have any products defined
|
||||
$o = new $class;
|
||||
$o->where('supplier_detail_id',$this->detail->id);
|
||||
|
||||
if ($o->count())
|
||||
$result->put($key,(object)[
|
||||
'type' => Arr::get($type,'name'),
|
||||
'items' => (new $class)->where('supplier_detail_id',$this->detail->id),
|
||||
]);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
@ -1,14 +1,112 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
namespace App\Models\Supplier;
|
||||
|
||||
use App\Traits\ProductDetails;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Collection;
|
||||
use Leenooks\Traits\ScopeActive;
|
||||
|
||||
class AdslSupplierPlan extends Model
|
||||
use App\Interfaces\SupplierItem;
|
||||
use App\Models\{Invoice,Supplier,SupplierDetail,Tax};
|
||||
use App\Models\Product\Broadband as ProductBroadband;
|
||||
use App\Traits\SiteID;
|
||||
|
||||
class Broadband extends Model implements SupplierItem
|
||||
{
|
||||
protected $table = 'ab_adsl_supplier_plan';
|
||||
use SiteID,ScopeActive,ProductDetails;
|
||||
|
||||
protected $casts = [
|
||||
'offpeak_start' => 'datetime:H:i',
|
||||
'offpeak_end' => 'datetime:H:i',
|
||||
];
|
||||
|
||||
protected $table = 'supplier_broadband';
|
||||
|
||||
// Map the table fields, with the extra fields
|
||||
public const traffic_map = [
|
||||
'base_up_offpeak' => 'extra_up_offpeak',
|
||||
'base_down_offpeak' => 'extra_down_offpeak',
|
||||
'base_up_peak' => 'extra_up_peak',
|
||||
'base_down_peak' => 'extra_down_peak',
|
||||
];
|
||||
|
||||
// Map the NULL relationships - and where traffic gets applied if NULL
|
||||
public const traffic_merge = [
|
||||
'extra_up_offpeak' => 'base_down_offpeak',
|
||||
'extra_down_offpeak' => 'base_down_peak',
|
||||
'extra_up_peak' => 'base_down_peak',
|
||||
'extra_down_peak' => 'base_down_peak',
|
||||
];
|
||||
|
||||
/* INTERFACES */
|
||||
|
||||
public function supplier_detail(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(SupplierDetail::class);
|
||||
}
|
||||
|
||||
public function types(): BelongsToMany
|
||||
{
|
||||
return $this->belongsToMany(ProductBroadband::class,'supplier_broadband','id','id','id','supplier_broadband_id');
|
||||
}
|
||||
|
||||
public function getBaseCostTaxableAttribute(): float
|
||||
{
|
||||
return Tax::tax_calc($this->attributes['base_cost'],config('site')->taxes);
|
||||
}
|
||||
|
||||
public function getBillingIntervalAttribute(): int
|
||||
{
|
||||
return 1; // Monthly
|
||||
}
|
||||
|
||||
/**
|
||||
* This contract term is the highest of
|
||||
* + The defined contract_term
|
||||
* + The default months in a billing interval
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getContractTermAttribute(): int
|
||||
{
|
||||
return max(Invoice::billing_period(self::getBillingIntervalAttribute()),Arr::get($this->attributes,'contract_term'));
|
||||
}
|
||||
|
||||
public function getMinCostAttribute(): float
|
||||
{
|
||||
return $this->attributes['setup_cost']+$this->attributes['base_cost']*Invoice::billing_term($this->getContractTermAttribute(),$this->getBillingIntervalAttribute());
|
||||
}
|
||||
|
||||
public function getMinCostTaxableAttribute(): float
|
||||
{
|
||||
return Tax::tax_calc($this->getMinCostAttribute(),config('site')->taxes);
|
||||
}
|
||||
|
||||
public function getNameAttribute(): string
|
||||
{
|
||||
return $this->product_id ?: 'Supplier PID Unknown';
|
||||
}
|
||||
|
||||
public function getNameLongAttribute(): string
|
||||
{
|
||||
return $this->product_desc ?: 'Supplier NAME Unknown';
|
||||
}
|
||||
|
||||
public function getSetupCostTaxableAttribute(): float
|
||||
{
|
||||
return Tax::tax_calc($this->attributes['setup_cost'],config('site')->taxes);
|
||||
}
|
||||
|
||||
public function getTypeAttribute(): string
|
||||
{
|
||||
return Arr::get(collect(Supplier::offering_types)->firstWhere('class',get_class($this)),'name','Unknown');
|
||||
}
|
||||
|
||||
/* METHODS */
|
||||
|
||||
/**
|
||||
* Determine how traffic is counted for Broadband links.
|
||||
@ -17,7 +115,7 @@ class AdslSupplierPlan extends Model
|
||||
* + down_peak, when not NULL, traffic is included to value of this metric, extra traffic is charged per extra_peak
|
||||
* + down_offpeak, when not NULL, traffic is included to the value of metric, extra traffic is charged per extra_offpeak
|
||||
*
|
||||
* If:
|
||||
* If:
|
||||
* + UPLOADS are charged and there are no PEAK/OFFPEAK periods (therefore all
|
||||
* traffic is charged), the allowance will be shown as 1 metric - TRAFFIC.
|
||||
* + UPLOADS are charged and there are PEAK/OFFPEAK periods the allowance
|
||||
@ -39,29 +137,15 @@ class AdslSupplierPlan extends Model
|
||||
* + If extra_up_peak is NULL add traffic_up_peak to traffic_down_peak
|
||||
* + If extra_up_offpeak is NULL add traffic_up_offpeak to traffic_down_offpeak
|
||||
*
|
||||
* @param array $config The configuration of the link, if NULL assume the supplieres configuration
|
||||
* @param array $data The traffic used on this link, determine whats left or over
|
||||
* @param bool $format @deprecate
|
||||
* @param bool $over @deprecate
|
||||
* @param bool $ceil Round the numbers to integers
|
||||
* @param Collection|null $config The configuration of the link, if NULL assume the supplieres configuration
|
||||
* @param array $data The traffic used on this link, determine whats left or over
|
||||
* @param bool $ceil Round the numbers to integers
|
||||
* @return array|string
|
||||
*/
|
||||
public function allowance(Collection $config=NULL,array $data=[],$ceil=TRUE) {
|
||||
// Map the table fields, with the extra fields
|
||||
$map = collect([
|
||||
'base_up_offpeak'=>'extra_up_offpeak',
|
||||
'base_down_offpeak'=>'extra_down_offpeak',
|
||||
'base_up_peak'=>'extra_up_peak',
|
||||
'base_down_peak'=>'extra_down_peak',
|
||||
]);
|
||||
|
||||
// Map the NULL relationships - and where traffic gets applied if NULL
|
||||
$merge = collect([
|
||||
'extra_up_offpeak'=>'base_down_offpeak',
|
||||
'extra_down_offpeak'=>'base_down_peak',
|
||||
'extra_up_peak'=>'base_down_peak',
|
||||
'extra_down_peak'=>'base_down_peak',
|
||||
]);
|
||||
public function allowance(Collection $config=NULL,array $data=[],bool $ceil=TRUE)
|
||||
{
|
||||
$map = collect(self::traffic_map);
|
||||
$merge = collect(self::traffic_merge);
|
||||
|
||||
if (is_null($config))
|
||||
$config = collect($config);
|
||||
@ -69,14 +153,12 @@ class AdslSupplierPlan extends Model
|
||||
// If config is null, use the configuration from this Model
|
||||
if (! $config->count()) {
|
||||
// Base Config
|
||||
foreach ($map->keys() as $k) {
|
||||
foreach ($map->keys() as $k)
|
||||
$config->put($k,$this->{$k});
|
||||
}
|
||||
|
||||
// Excess Config
|
||||
foreach ($map->values() as $k) {
|
||||
foreach ($map->values() as $k)
|
||||
$config->put($k,$this->{$k});
|
||||
}
|
||||
|
||||
// Shaped or Charge
|
||||
$config->put('shaped',$this->extra_shaped);
|
||||
@ -89,7 +171,7 @@ class AdslSupplierPlan extends Model
|
||||
$result = collect();
|
||||
|
||||
// If data is empty, we'll report on allowance, otherwise we'll report on consumption
|
||||
$report = $data ? FALSE : TRUE;
|
||||
$report = ! $data;
|
||||
|
||||
// Work out if we charge each period
|
||||
foreach ($map as $k => $v) {
|
||||
@ -136,7 +218,12 @@ class AdslSupplierPlan extends Model
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function getNameAttribute()
|
||||
/**
|
||||
* Return the Broadband Speed
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function speed(): string
|
||||
{
|
||||
return $this->speed;
|
||||
}
|
7
app/Models/Supplier/Ethernet.php
Normal file
7
app/Models/Supplier/Ethernet.php
Normal file
@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\Supplier;
|
||||
|
||||
class Ethernet extends Broadband
|
||||
{
|
||||
}
|
7
app/Models/Supplier/HSPA.php
Normal file
7
app/Models/Supplier/HSPA.php
Normal file
@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\Supplier;
|
||||
|
||||
class HSPA extends Broadband
|
||||
{
|
||||
}
|
@ -10,6 +10,8 @@ class SupplierDetail extends Model
|
||||
{
|
||||
use SiteID;
|
||||
|
||||
protected $casts = [ 'connections'=>'collection' ];
|
||||
|
||||
/* RELATIONS */
|
||||
|
||||
public function supplier()
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Tax extends Model
|
||||
@ -14,4 +15,30 @@ class Tax extends Model
|
||||
{
|
||||
return $this->belongsTo(Country::class);
|
||||
}
|
||||
|
||||
/* METHODS */
|
||||
|
||||
/**
|
||||
* Calculate Tax on a value
|
||||
*
|
||||
* @param float $value
|
||||
* @param Collection $taxes
|
||||
* @return void
|
||||
*/
|
||||
public static function tax_calc(?float $value,Collection $taxes): float
|
||||
{
|
||||
if (! $value)
|
||||
$value = 0;
|
||||
$tax = 0;
|
||||
|
||||
foreach ($taxes as $o) {
|
||||
// Quick sanity check
|
||||
if (! $o instanceof self)
|
||||
abort(500,'Invalid object for tax calculation');
|
||||
|
||||
$tax += round($value*$o->rate,2);
|
||||
}
|
||||
|
||||
return round($value+$tax,2);
|
||||
}
|
||||
}
|
@ -495,7 +495,7 @@ class User extends Authenticatable
|
||||
}
|
||||
|
||||
$result->load([
|
||||
'product.descriptions',
|
||||
'product.description',
|
||||
'service.type',
|
||||
]);
|
||||
|
||||
|
21
app/Traits/ProductDetails.php
Normal file
21
app/Traits/ProductDetails.php
Normal file
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Consistent Details on Products
|
||||
*/
|
||||
namespace App\Traits;
|
||||
|
||||
use App\Models\Invoice;
|
||||
|
||||
trait ProductDetails
|
||||
{
|
||||
/**
|
||||
* Return a human friendly name for the billing interval
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getBillingIntervalStringAttribute(): string
|
||||
{
|
||||
return Invoice::billing_name(self::getBillingIntervalAttribute());
|
||||
}
|
||||
}
|
@ -2,20 +2,20 @@
|
||||
|
||||
use Faker\Generator as Faker;
|
||||
|
||||
$factory->define(App\Models\Product\Adsl::class, function (Faker $faker) {
|
||||
$factory->define(App\Models\Product\Broadband::class, function (Faker $faker) {
|
||||
return [
|
||||
'id'=>1,
|
||||
'contract_term'=>12,
|
||||
];
|
||||
});
|
||||
|
||||
$factory->afterMaking(App\Models\Product\Adsl::class, function ($adsl,$faker) {
|
||||
$product = factory(App\Models\AdslSupplierPlan::class)->make();
|
||||
$factory->afterMaking(App\Models\Product\Broadband::class, function ($adsl,$faker) {
|
||||
$product = factory(App\Models\Supplier\Broadband::class)->make();
|
||||
$adsl->setRelation('product',$product);
|
||||
$adsl->adsl_supplier_plan_id = $product->id;
|
||||
});
|
||||
|
||||
$factory->state(App\Models\Product\Adsl::class,'unlimit',[
|
||||
$factory->state(App\Models\Product\Broadband::class,'unlimit',[
|
||||
'base_down_peak'=>NULL,
|
||||
'base_up_peak'=>NULL,
|
||||
'base_down_offpeak'=>NULL,
|
||||
@ -29,7 +29,7 @@ $factory->state(App\Models\Product\Adsl::class,'unlimit',[
|
||||
'metric'=>1,
|
||||
]);
|
||||
|
||||
$factory->state(App\Models\Product\Adsl::class,'140/0/0/0',[
|
||||
$factory->state(App\Models\Product\Broadband::class,'140/0/0/0',[
|
||||
'base_down_peak'=>140,
|
||||
'base_up_peak'=>0,
|
||||
'base_down_offpeak'=>0,
|
||||
@ -43,7 +43,7 @@ $factory->state(App\Models\Product\Adsl::class,'140/0/0/0',[
|
||||
'metric'=>1,
|
||||
]);
|
||||
|
||||
$factory->state(App\Models\Product\Adsl::class,'70/-/0/-',[
|
||||
$factory->state(App\Models\Product\Broadband::class,'70/-/0/-',[
|
||||
'base_down_peak'=>70,
|
||||
'base_up_peak'=>NULL,
|
||||
'base_down_offpeak'=>0,
|
||||
@ -57,7 +57,7 @@ $factory->state(App\Models\Product\Adsl::class,'70/-/0/-',[
|
||||
'metric'=>1,
|
||||
]);
|
||||
|
||||
$factory->state(App\Models\Product\Adsl::class,'100/0/40/0',[
|
||||
$factory->state(App\Models\Product\Broadband::class,'100/0/40/0',[
|
||||
'base_down_peak'=>100,
|
||||
'base_up_peak'=>0,
|
||||
'base_down_offpeak'=>40,
|
||||
@ -71,7 +71,7 @@ $factory->state(App\Models\Product\Adsl::class,'100/0/40/0',[
|
||||
'metric'=>1,
|
||||
]);
|
||||
|
||||
$factory->state(App\Models\Product\Adsl::class,'50/-/20/-',[
|
||||
$factory->state(App\Models\Product\Broadband::class,'50/-/20/-',[
|
||||
'base_down_peak'=>50,
|
||||
'base_up_peak'=>NULL,
|
||||
'base_down_offpeak'=>20,
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
use Faker\Generator as Faker;
|
||||
|
||||
$factory->define(App\Models\AdslSupplierPlan::class, function (Faker $faker) {
|
||||
$factory->define(App\Models\Supplier\Broadband::class, function (Faker $faker) {
|
||||
return [
|
||||
'id'=>1,
|
||||
'contract_term'=>12,
|
||||
|
@ -33,7 +33,6 @@ class ProductFactory extends Factory
|
||||
'taxable' => TRUE,
|
||||
'active' => TRUE,
|
||||
// 'position'
|
||||
// 'cart_multiple'
|
||||
// 'group_avail'
|
||||
// 'avail_category'
|
||||
// 'price_type'
|
||||
@ -50,7 +49,7 @@ class ProductFactory extends Factory
|
||||
// 'price_recurr_weekday'
|
||||
// 'price_recurr_strict'
|
||||
// 'prod_plugin_file'
|
||||
//'prod_plugin_data' => 1,
|
||||
// 'prod_plugin_data' => 1,
|
||||
// 'accounting'
|
||||
// 'model' => 'App\Models\Product\Adsl',
|
||||
];
|
||||
|
@ -10,9 +10,8 @@ use Illuminate\Support\Facades\Schema;
|
||||
| ab_account_log |
|
||||
| ab_account_memo |
|
||||
| ab_account_oauth |
|
||||
| ab_adsl_plan |
|
||||
| ab_adsl_supplier |
|
||||
| ab_adsl_supplier_plan |
|
||||
| product_broadband |*DONE*
|
||||
| supplier_broadband |*DONE*
|
||||
| ab_affiliate |
|
||||
| ab_asset |
|
||||
| ab_asset_pool |
|
||||
@ -48,7 +47,7 @@ use Illuminate\Support\Facades\Schema;
|
||||
| ab_module_method_token |
|
||||
| ab_oauth |
|
||||
| ab_pivot_product_cat |
|
||||
| ab_product |
|
||||
| products |*PARTIAL* - make model/model_id NOT NULL
|
||||
| ab_product_cat |
|
||||
| ab_product_cat_translate |
|
||||
| ab_product_translate |
|
||||
@ -71,7 +70,7 @@ use Illuminate\Support\Facades\Schema;
|
||||
| ab_task |
|
||||
| ab_task_log |
|
||||
| ab_voip_plan |
|
||||
| accounts |*PARTIAL*
|
||||
| accounts |*PARTIAL* - make timestamp columns, make date_expire timestamp
|
||||
| charges |
|
||||
| countries |*DONE*
|
||||
| currencies |*DONE*
|
||||
@ -98,7 +97,7 @@ use Illuminate\Support\Facades\Schema;
|
||||
| supplier_details |*DONE*
|
||||
| suppliers |*DONE*
|
||||
| taxes |*DONE*
|
||||
| users |*PARTIAL*
|
||||
| users |*DONE*
|
||||
*/
|
||||
class IntUnsigned extends Migration
|
||||
{
|
||||
@ -322,7 +321,8 @@ class IntUnsigned extends Migration
|
||||
|
||||
DB::statement('ALTER TABLE users MODIFY country_id int unsigned NOT NULL,MODIFY language_id int unsigned NOT NULL,MODIFY currency_id int unsigned NOT NULL');
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
$table->foreign(['country_id','currency_id'])->references(['id','currency_id'])->on('countries');
|
||||
$table->dropColumn(['currency_id']);
|
||||
$table->foreign(['country_id'])->references(['id'])->on('countries');
|
||||
$table->foreign(['parent_id','site_id'])->references(['id','site_id'])->on('users');
|
||||
$table->foreign(['language_id'])->references(['id'])->on('languages');
|
||||
});
|
||||
@ -332,11 +332,11 @@ class IntUnsigned extends Migration
|
||||
DB::statement('ALTER TABLE accounts MODIFY site_id int unsigned NOT NULL');
|
||||
DB::statement('ALTER TABLE accounts MODIFY country_id int unsigned NOT NULL,MODIFY language_id int unsigned NOT NULL,MODIFY currency_id int unsigned NOT NULL,MODIFY rtm_id int unsigned DEFAULT NULL,MODIFY active tinyint(1) NOT NULL');
|
||||
Schema::table('accounts', function (Blueprint $table) {
|
||||
$table->dropColumn(['currency_id','language_id']);
|
||||
$table->foreign(['site_id'])->references(['site_id'])->on('sites');
|
||||
$table->index(['id','site_id']);
|
||||
$table->foreign(['country_id','currency_id'])->references(['id','currency_id'])->on('countries');
|
||||
$table->foreign(['country_id'])->references(['id'])->on('countries');
|
||||
$table->foreign(['user_id','site_id'])->references(['id','site_id'])->on('users');
|
||||
$table->foreign(['language_id'])->references(['id'])->on('languages');
|
||||
$table->foreign(['rtm_id','site_id'])->references(['id','site_id'])->on('rtm');
|
||||
});
|
||||
|
||||
|
295
database/migrations/2021_12_20_225017_optimize_product.php
Normal file
295
database/migrations/2021_12_20_225017_optimize_product.php
Normal file
@ -0,0 +1,295 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class OptimizeProduct extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
/*
|
||||
Schema::table('ab_product', function (Blueprint $table) {
|
||||
$table->dropForeign(['site_id']);
|
||||
$table->dropIndex(['id','site_id']);
|
||||
$table->dropIndex('ab_product_site_id_foreign');
|
||||
});
|
||||
|
||||
DB::statement('ALTER TABLE ab_product RENAME TO products');
|
||||
|
||||
Schema::table('products', function (Blueprint $table) {
|
||||
$table->dropColumn(['cart_multiple']);
|
||||
$table->index(['id','site_id']);
|
||||
$table->foreign(['site_id'])->references(['id'])->on('sites');
|
||||
$table->dateTime('created_at')->nullable()->after('id');
|
||||
$table->dateTime('updated_at')->nullable()->after('created_at');
|
||||
});
|
||||
|
||||
Schema::table('supplier_details', function (Blueprint $table) {
|
||||
$table->jsonb('connections')->nullable();
|
||||
});
|
||||
|
||||
foreach (\Illuminate\Support\Facades\DB::select('SELECT * FROM AB_ADSL_SUPPLIER') as $o) {
|
||||
switch($o->name) {
|
||||
case 'PeopleAgent':
|
||||
$type = 'broadband';
|
||||
$name = 'People Telecom';
|
||||
break;
|
||||
|
||||
case 'iiNetADSL':
|
||||
$type = 'broadband';
|
||||
$name = 'iiNet';
|
||||
break;
|
||||
|
||||
case 'ExetelVisp':
|
||||
$type = 'broadband';
|
||||
$name = 'Exetel';
|
||||
break;
|
||||
|
||||
case 'ExetelHSPA':
|
||||
$type = 'hspa';
|
||||
$name = 'Exetel';
|
||||
break;
|
||||
|
||||
case 'ExetelPE':
|
||||
$type = 'ethernet';
|
||||
$name = 'Exetel';
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new Exception('Unknown Supplier: '.$o->name);
|
||||
}
|
||||
|
||||
$so = \App\Models\Supplier::where('name',$name)->singleOrNew();
|
||||
|
||||
if (! $so->exists) {
|
||||
$so->name = $name;
|
||||
$so->address1 = '...';
|
||||
$so->city = '...';
|
||||
$so->state = '...';
|
||||
$so->postcode = '...';
|
||||
}
|
||||
|
||||
$so->active = $so->active ?: $o->active;
|
||||
$so->save();
|
||||
|
||||
$sdo = \App\Models\SupplierDetail::withoutGlobalScope(\App\Models\Scopes\SiteScope::class)
|
||||
->where('supplier_id',$so->id)
|
||||
->where('site_id',$o->site_id)
|
||||
->firstOrNew();
|
||||
|
||||
if (! $sdo->exists) {
|
||||
$sdo->site_id = $o->site_id;
|
||||
}
|
||||
|
||||
$connections = $sdo->connections ?: collect();
|
||||
|
||||
$connections->put($type,[
|
||||
'user'=>$o->stats_username,
|
||||
'pass'=>$o->stats_password,
|
||||
'last'=>$o->stats_lastupdate,
|
||||
'url'=>$o->stats_url,
|
||||
]);
|
||||
|
||||
$sdo->connections = $connections;
|
||||
|
||||
$so->detail()->save($sdo);
|
||||
};
|
||||
|
||||
Schema::table('ab_adsl_supplier_plan', function (Blueprint $table) {
|
||||
$table->dropForeign(['site_id']);
|
||||
$table->dropIndex(['id','site_id']);
|
||||
$table->dropIndex('ab_adsl_supplier_plan_site_id_foreign');
|
||||
});
|
||||
|
||||
DB::statement('ALTER TABLE ab_adsl_supplier_plan RENAME TO supplier_broadband');
|
||||
DB::statement('ALTER TABLE supplier_broadband MODIFY product_id varchar(16) NOT NULL');
|
||||
DB::statement('ALTER TABLE supplier_broadband MODIFY base_cost double NOT NULL');
|
||||
DB::statement('ALTER TABLE supplier_broadband MODIFY active tinyint(1)');
|
||||
DB::statement('ALTER TABLE supplier_broadband RENAME COLUMN supplier_id TO old_supplier_id');
|
||||
DB::statement('ALTER TABLE supplier_broadband RENAME COLUMN offpeak_start TO old_offpeak_start');
|
||||
DB::statement('ALTER TABLE supplier_broadband RENAME COLUMN offpeak_end TO old_offpeak_end');
|
||||
|
||||
Schema::table('supplier_broadband', function (Blueprint $table) {
|
||||
$table->index(['id','site_id']);
|
||||
$table->foreign(['site_id'])->references(['id'])->on('sites');
|
||||
$table->dateTime('created_at')->nullable()->after('id');
|
||||
$table->dateTime('updated_at')->nullable()->after('created_at');
|
||||
$table->time('offpeak_start')->nullable()->after('old_offpeak_end');
|
||||
$table->time('offpeak_end')->nullable()->after('offpeak_start');
|
||||
});
|
||||
|
||||
Schema::table('supplier_broadband', function (Blueprint $table) {
|
||||
$table->integer('supplier_detail_id')->unsigned()->nullable()->after('old_supplier_id');
|
||||
|
||||
$table->foreign(['supplier_detail_id','site_id'])->references(['id','site_id'])->on('supplier_details');
|
||||
});
|
||||
|
||||
\Illuminate\Support\Facades\DB::select("UPDATE ab_service SET model='App\\\\Models\\\\Service\\\\Broadband' where model='App\\\\Models\\\\Service\\\\Adsl'");
|
||||
\Illuminate\Support\Facades\DB::select("UPDATE products SET model='App\\\\Models\\\\Product\\\\Broadband' where model='App\\\\Models\\\\Product\\\\Adsl'");
|
||||
|
||||
// Convert to use the new supplier
|
||||
foreach (\Illuminate\Support\Facades\DB::select('SELECT * FROM AB_ADSL_SUPPLIER') as $o) {
|
||||
switch ($o->name) {
|
||||
case 'PeopleAgent':
|
||||
$so = \App\Models\Supplier::where('name','People Telecom')->singleOrFail();
|
||||
break;
|
||||
|
||||
case 'iiNetADSL':
|
||||
$so = \App\Models\Supplier::where('name','iiNet')->singleOrFail();
|
||||
break;
|
||||
|
||||
case 'ExetelVisp':
|
||||
case 'ExetelHSPA':
|
||||
case 'ExetelPE':
|
||||
$so = \App\Models\Supplier::where('name','Exetel')->singleOrFail();
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new Exception('Unknown Supplier: ' . $o->name);
|
||||
}
|
||||
|
||||
$sdo = \App\Models\SupplierDetail::withoutGlobalScope(\App\Models\Scopes\SiteScope::class)
|
||||
->where('supplier_id',$so->id)
|
||||
->where('site_id',$o->site_id)
|
||||
->singleOrFail();
|
||||
|
||||
\Illuminate\Support\Facades\DB::select(sprintf("UPDATE supplier_broadband SET supplier_detail_id=%d where old_supplier_id=%d and site_id=%d",$sdo->id,$o->id,$sdo->site_id));
|
||||
}
|
||||
|
||||
// Convert out dates
|
||||
foreach (\App\Models\Supplier\Broadband::withoutGlobalScope(\App\Models\Scopes\SiteScope::class)->cursor() as $o) {
|
||||
if ($o->date_orig)
|
||||
$o->created_at = \Carbon\Carbon::createFromTimestamp($o->date_orig);
|
||||
if ($o->date_last)
|
||||
$o->updated_at = \Carbon\Carbon::createFromTimestamp($o->date_last);
|
||||
if ($o->old_offpeak_start)
|
||||
$o->offpeak_start = \Carbon\Carbon::createFromTimestamp($o->old_offpeak_start);
|
||||
if ($o->old_offpeak_end)
|
||||
$o->offpeak_end = \Carbon\Carbon::createFromTimestamp($o->old_offpeak_end);
|
||||
$o->save();
|
||||
}
|
||||
|
||||
Schema::table('supplier_broadband', function (Blueprint $table) {
|
||||
$table->dropPrimary();
|
||||
$table->primary(['id','site_id']);
|
||||
$table->dropColumn(['date_orig','date_last','old_supplier_id','old_offpeak_start','old_offpeak_end']);
|
||||
});
|
||||
Schema::dropIfExists('ab_adsl_supplier');
|
||||
|
||||
DB::statement('ALTER TABLE supplier_broadband MODIFY extra_charged tinyint(1)');
|
||||
DB::statement('ALTER TABLE supplier_broadband MODIFY extra_shaped tinyint(1)');
|
||||
DB::statement('ALTER TABLE supplier_broadband MODIFY contract_term int unsigned');
|
||||
|
||||
Schema::table('ab_adsl_plan', function (Blueprint $table) {
|
||||
$table->dropForeign(['site_id']);
|
||||
$table->dropIndex(['id','site_id']);
|
||||
$table->dropIndex('ab_adsl_plan_site_id_foreign');
|
||||
});
|
||||
|
||||
DB::statement('ALTER TABLE ab_adsl_plan RENAME TO product_broadband');
|
||||
DB::statement('ALTER TABLE product_broadband DROP PRIMARY KEY,ADD PRIMARY KEY (id,site_id)');
|
||||
DB::statement('ALTER TABLE product_broadband MODIFY extra_charged tinyint(1)');
|
||||
DB::statement('ALTER TABLE product_broadband MODIFY extra_shaped tinyint(1)');
|
||||
DB::statement('ALTER TABLE product_broadband MODIFY contract_term int unsigned');
|
||||
DB::statement('ALTER TABLE product_broadband RENAME COLUMN adsl_supplier_plan_id TO supplier_broadband_id');
|
||||
DB::statement('ALTER TABLE product_broadband MODIFY supplier_broadband_id int unsigned');
|
||||
|
||||
Schema::table('product_broadband', function (Blueprint $table) {
|
||||
$table->index(['id','site_id']);
|
||||
$table->foreign(['site_id'])->references(['id'])->on('sites');
|
||||
$table->foreign(['supplier_broadband_id','site_id'])->references(['id','site_id'])->on('supplier_broadband');
|
||||
$table->dateTime('created_at')->nullable()->after('id');
|
||||
$table->dateTime('updated_at')->nullable()->after('created_at');
|
||||
});
|
||||
|
||||
// Convert product pricegroups
|
||||
foreach (\App\Models\Product::withoutGlobalScope(\App\Models\Scopes\SiteScope::class)->cursor() as $po) {
|
||||
if ($po->date_orig)
|
||||
$po->created_at = \Carbon\Carbon::createFromTimestamp($po->date_orig);
|
||||
if ($po->date_last)
|
||||
$po->updated_at = \Carbon\Carbon::createFromTimestamp($po->date_last);
|
||||
|
||||
if (! ($po instanceof \Illuminate\Support\Collection) || ! $po->price_group->count()) {
|
||||
$original = $po->getRawOriginal('price_group');
|
||||
|
||||
// serialized
|
||||
if (preg_match('/^a:/',$original)) {
|
||||
try {
|
||||
$price_group = collect(unserialize(str_replace("\n","",$original)));
|
||||
} catch (Exception $e) {
|
||||
dd(['error'=>$e->getMessage(),'raw'=>$po->getRawOriginal('price_group')]);
|
||||
}
|
||||
|
||||
} elseif (is_null($po->getRawOriginal('price_group'))) {
|
||||
$price_group = collect();
|
||||
|
||||
} else {
|
||||
try {
|
||||
$price_group = unserialize(gzuncompress($po->getRawOriginal('price_group')));
|
||||
} catch (Exception $e) {
|
||||
dd(['error'=>$e->getMessage(),'raw'=>$po->getRawOriginal('price_group')]);
|
||||
}
|
||||
}
|
||||
|
||||
$new_price_group = collect();
|
||||
|
||||
// Remove any blank entries, or when base/setup = 0
|
||||
foreach ($price_group as $group => $values) {
|
||||
$build = collect();
|
||||
|
||||
foreach ($values as $key => $pricing) {
|
||||
switch ($key) {
|
||||
case 'show':
|
||||
$build->put('show',(bool) $pricing);
|
||||
break;
|
||||
|
||||
default:
|
||||
// key is a time period
|
||||
if ((! Arr::get($pricing,'price_base')) && (! Arr::get($pricing,'price_setup')))
|
||||
break;
|
||||
|
||||
$build->put($key,[
|
||||
'base'=>Arr::get($pricing,'price_base'),
|
||||
'setup'=>Arr::get($pricing,'price_setup'),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
$new_price_group->put($group,$build);
|
||||
}
|
||||
|
||||
$po->price_group = $new_price_group;
|
||||
}
|
||||
|
||||
$po->save();
|
||||
}
|
||||
|
||||
DB::statement('ALTER TABLE products MODIFY taxable tinyint(1),MODIFY active tinyint(1),MODIFY price_recurr_strict tinyint(1),MODIFY prod_plugin_data int unsigned');
|
||||
DB::statement('ALTER TABLE products RENAME COLUMN price_group TO pricing');
|
||||
DB::statement('ALTER TABLE products RENAME COLUMN price_recurr_default TO price_recur_default');
|
||||
DB::statement('ALTER TABLE products RENAME COLUMN price_recurr_strict TO price_recur_strict');
|
||||
DB::statement('ALTER TABLE products RENAME COLUMN prod_plugin_data TO model_id');
|
||||
Schema::table('products', function (Blueprint $table) {
|
||||
$table->dropColumn(['date_orig','date_last','group_avail','avail_category','price_recurr_day','price_recurr_weekday','prod_plugin_file']);
|
||||
});
|
||||
*/
|
||||
|
||||
abort(500,'here');
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
//abort(500,'Cant go back');
|
||||
}
|
||||
}
|
6
public/css/fixes.css
vendored
6
public/css/fixes.css
vendored
@ -19,6 +19,12 @@ body {
|
||||
box-shadow: 0 0 0 #fff;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
/* Fix select width */
|
||||
.dataTables_wrapper .dataTables_length select {
|
||||
width: 5em !important;
|
||||
}
|
||||
|
||||
.dataTables_scrollHeadInner {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
@ -1,32 +1,80 @@
|
||||
<!-- $o = Service::class -->
|
||||
<table class="table table-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th> </th>
|
||||
<th>{{ $o->product->type->supplied->supplier_detail->supplier->name }}</th>
|
||||
<th>Us</th>
|
||||
<th> </th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>Supplier</th><td>{{ $o->product->type ? $o->product->type->supplier->name : 'Supplier Unknown' }}</td>
|
||||
<th>Product</th>
|
||||
<td>#{{ ($s=$o->product->type->supplied)->id }}: {{ $s->name }}</td>
|
||||
<td>#{{ $o->product->id }}: {{ $o->product->name }}</td>
|
||||
<td>{{ $s->type }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Supplier Product</th><td>#{{ $o->product_id }}: {{ $o->product->type ? $o->product->type->product->product_id : 'Product Unknown' }}</td>
|
||||
</tr>
|
||||
@if($o->product->type)
|
||||
<tr>
|
||||
<!-- @todo Tax shouldnt be hard coded -->
|
||||
<th>Supplier Setup</th><td>${{ number_format($o->product->type->product->setup_cost*1.1,2) }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Supplier Cost</th><td>${{ number_format($o->product->type->cost,2) }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Supplier Contract</th><td>{{ $o->product->type->product->contract_term }} months</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<!-- @todo Tax shouldnt be hard coded -->
|
||||
<th>Supplier Min Cost</th><td>${{ number_format((($x=$o->product->type->product)->setup_cost+$x->base_cost*$x->contract_term)*1.1,2) }}</td>
|
||||
</tr>
|
||||
@endif
|
||||
<tr>
|
||||
<th>Price</th><td>${{ number_format($o->billing_monthly_price,2) }} <small>(${{ number_format($o->billing_monthly_price*12,2) }} Annually)</small></td>
|
||||
</tr>
|
||||
@if($o->product->type AND $o->product->type->cost)
|
||||
<tr>
|
||||
<th>Markup</th><td>{{ number_format(($o->billing_monthly_price/$o->product->type->cost-1)*100,2) }}%</td>
|
||||
</tr>
|
||||
@endif
|
||||
<tr>
|
||||
<th>Setup</th>
|
||||
<td>${{ number_format($a=\App\Models\Tax::tax_calc($s->setup_cost,$o->account->taxes),2) }}</td>
|
||||
<td>${{ number_format($b=\App\Models\Tax::tax_calc($o->product->setup_charge,$o->account->taxes),2) }}</td>
|
||||
<td>
|
||||
@if ($a > $b)
|
||||
<span class="badge bg-danger>">({{ number_format(($a-$b)/($b ?: 1)*100,1) }})%</span>
|
||||
@else
|
||||
<span class="badge">{{ number_format(($b-$a)/($a ?: 1)*100,1) }}%</span>
|
||||
@endif
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Billed</th>
|
||||
<td>{{ $s->billing_interval_string }}</td>
|
||||
<td>{{ $o->product->billing_interval_string }}</td>
|
||||
<td> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Billing Charge</th>
|
||||
<td>${{ number_format($a=\App\Models\Tax::tax_calc($s->base_cost*\App\Models\Invoice::billing_change($s->billing_interval,$o->product->billing_interval),$o->account->taxes),2) }}</td>
|
||||
<td>${{ number_format($b=\App\Models\Tax::tax_calc($o->product->getBaseChargeAttribute($o->billing_interval),$o->account->taxes),2) }}</td>
|
||||
<td>
|
||||
@if ($a > $b)
|
||||
<span class="badge bg-danger>">({{ number_format(($a-$b)/($b ?: 1)*100,1) }})%</span>
|
||||
@else
|
||||
<span class="badge">{{ number_format(($b-$a)/($a ?: 1)*100,1) }}%</span>
|
||||
@endif
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Monthly Cost</th>
|
||||
<td>${{ number_format($a=\App\Models\Tax::tax_calc($s->base_cost*\App\Models\Invoice::billing_change($s->billing_interval,1),$o->account->taxes),2) }}</td>
|
||||
<td>${{ number_format($b=\App\Models\Tax::tax_calc($o->product->getBaseChargeAttribute($o->billing_interval)*\App\Models\Invoice::billing_change($o->billing_interval,1),$o->account->taxes),2) }}</td>
|
||||
<td>
|
||||
@if ($a > $b)
|
||||
<span class="badge bg-danger>">({{ number_format(($a-$b)/($b ?: 1)*100,1) }})%</span>
|
||||
@else
|
||||
<span class="badge">{{ number_format(($b-$a)/($a ?: 1)*100,1) }}%</span>
|
||||
@endif
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Contract</th>
|
||||
<td>{{ $s->contract_term }} months</td>
|
||||
<td>{{ $o->product->contract_term }} months</td>
|
||||
<td> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Min Cost</th>
|
||||
<td>${{ number_format($a=\App\Models\Tax::tax_calc($s->min_cost,$o->account->taxes),2) }}</td>
|
||||
<td>${{ number_format($b=\App\Models\Tax::tax_calc($o->product->getMinChargeAttribute($o->billing_interval),$o->account->taxes),2) }}</td>
|
||||
<td>
|
||||
@if ($a > $b)
|
||||
<span class="badge bg-danger>">({{ number_format(($a-$b)/($b ?: 1)*100,1) }})%</span>
|
||||
@else
|
||||
<span class="badge">{{ number_format(($b-$a)/($a ?: 1)*100,1) }}%</span>
|
||||
@endif
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
@ -0,0 +1,57 @@
|
||||
@extends('adminlte::layouts.app')
|
||||
|
||||
@section('htmlheader_title')
|
||||
{{ $o->name ?: 'New Supplier' }}
|
||||
@endsection
|
||||
@section('page_title')
|
||||
{{ $o->name ?: 'New Supplier' }}
|
||||
@endsection
|
||||
|
||||
@section('contentheader_title')
|
||||
{{ $o->name ?: 'New Supplier' }}
|
||||
@endsection
|
||||
@section('contentheader_description')
|
||||
@endsection
|
||||
|
||||
@section('main-content')
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
@include('adminlte::widget.status')
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-header bg-dark d-flex p-0">
|
||||
<ul class="nav nav-pills w-100 p-2">
|
||||
<li class="nav-item"><a class="nav-link active" href="#details" data-toggle="tab">Detail</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="#products" data-toggle="tab">Products</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="#offerings" data-toggle="tab">Offerings</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="#connections" data-toggle="tab">Connections</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="card-body">
|
||||
<div class="tab-content">
|
||||
<div class="tab-pane fade active show" id="details" role="tabpanel">
|
||||
@include('a.supplier.widgets.detail')
|
||||
</div>
|
||||
|
||||
<div class="tab-pane fade" id="products" role="tabpanel">
|
||||
@include('a.supplier.widgets.products')
|
||||
</div>
|
||||
|
||||
<div class="tab-pane fade" id="offerings" role="tabpanel">
|
||||
@include('a.supplier.widgets.offerings')
|
||||
</div>
|
||||
|
||||
<div class="tab-pane fade" id="connections" role="tabpanel">
|
||||
@include('a.supplier.widgets.connections')
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
@ -0,0 +1,34 @@
|
||||
<!-- $o = Supplier::class -->
|
||||
<div class="row">
|
||||
<div class="col-9">
|
||||
<table class="table table-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Service Type</th>
|
||||
<th>User Name</th>
|
||||
<th>Password</th>
|
||||
<th>URL</th>
|
||||
<th>Last Connect</th>
|
||||
<th class="text-right">Offerings</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
@foreach ($o->offeringTypes() as $key => $offering)
|
||||
<tr>
|
||||
<th>{{ $offering->type }}</th>
|
||||
@if(Arr::get($o->detail->connections,$key))
|
||||
<td>{{ Arr::get($o->detail->connections,$key.'.user') }}</td>
|
||||
<td>{{ Arr::get($o->detail->connections,$key.'.pass') }}</td>
|
||||
<td>{{ Arr::get($o->detail->connections,$key.'.url') }}</td>
|
||||
<td>{{ \Carbon\Carbon::createFromFormat('Y-m-d',Arr::get($o->detail->connections,$key.'.last'))->format('Y-m-d') }}</td>
|
||||
<td class="text-right">{{ number_format($offering->items->count()) }}</td>
|
||||
@else
|
||||
<td colspan="5"> </td>
|
||||
@endif
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
@ -0,0 +1,185 @@
|
||||
<!-- $o = Supplier::class -->
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<h3>Supplier Details</h3>
|
||||
<hr>
|
||||
@if(session()->has('success'))
|
||||
<span class="ml-3 pt-0 pb-0 pr-1 pl-1 btn btn-outline-success"><small>{{ session()->get('success') }}</small></span>
|
||||
@endif
|
||||
|
||||
<form class="g-0 needs-validation" method="POST" enctype="multipart/form-data" role="form">
|
||||
@csrf
|
||||
|
||||
<div class="row">
|
||||
<div class="col-4">
|
||||
<div class="row">
|
||||
<!-- Supplier Name -->
|
||||
<div class="col-9">
|
||||
<div class="form-group has-validation">
|
||||
<label for="name">Supplier Name</label>
|
||||
<input type="text" class="form-control form-control-border @error('name') is-invalid @enderror" id="name" name="name" placeholder="Supplier Name" value="{{ old('name',$o->name) }}" required>
|
||||
<span class="invalid-feedback" role="alert">
|
||||
@error('name')
|
||||
{{ $message }}
|
||||
@else
|
||||
Supplier Name required.
|
||||
@enderror
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Supplier Name -->
|
||||
<div class="col-3">
|
||||
<div class="form-group">
|
||||
<div class="custom-control custom-switch custom-switch-off-danger custom-switch-on-success">
|
||||
<input type="checkbox" class="custom-control-input" id="active" name="active" {{ old('active',$o->active) ? 'checked' : '' }}>
|
||||
<label class="custom-control-label" for="active">Active</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<!-- Address Lines -->
|
||||
<div class="col-12">
|
||||
<div class="form-group has-validation">
|
||||
<label for="address1">Address Lines</label>
|
||||
<input type="text" class="form-control form-control-border @error('address1') is-invalid @enderror" id="address1" name="address1" placeholder="Address1" value="{{ old('address1',$o->address1) }}" required>
|
||||
<input type="text" class="form-control form-control-border" id="address2" name="address2" placeholder="Address2" value="{{ old('address2',$o->address2) }}">
|
||||
<span class="invalid-feedback" role="alert">
|
||||
@error('address1')
|
||||
{{ $message }}
|
||||
@else
|
||||
Atleast 1 address line required.
|
||||
@enderror
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<!-- City -->
|
||||
<div class="col-12">
|
||||
<div class="form-group has-validation">
|
||||
<label for="city">City</label>
|
||||
<input type="text" class="form-control form-control-border @error('city') is-invalid @enderror" id="city" name="city" placeholder="City" value="{{ old('city',$o->city) }}">
|
||||
<span class="invalid-feedback" role="alert">
|
||||
@error('city')
|
||||
{{ $message }}
|
||||
@else
|
||||
City is required.
|
||||
@enderror
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<!-- State -->
|
||||
<div class="col-9">
|
||||
<div class="form-group has-validation">
|
||||
<label for="state">State</label>
|
||||
<input type="text" class="form-control form-control-border @error('state') is-invalid @enderror" id="state" name="state" placeholder="State" value="{{ old('state',$o->state) }}">
|
||||
<span class="invalid-feedback" role="alert">
|
||||
@error('state')
|
||||
{{ $message }}
|
||||
@else
|
||||
State is required.
|
||||
@enderror
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Postal Code -->
|
||||
<div class="col-3">
|
||||
<div class="form-group has-validation">
|
||||
<label for="postcode">Postal Code</label>
|
||||
<input type="text" class="form-control form-control-border @error('postcode') is-invalid @enderror" id="postcode" name="postcode" placeholder="Postal Code" value="{{ old('postcode',$o->postcode) }}">
|
||||
<span class="invalid-feedback" role="alert">
|
||||
@error('postcode')
|
||||
{{ $message }}
|
||||
@else
|
||||
Postcode is required.
|
||||
@enderror
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="offset-1 col-4">
|
||||
<div class="row">
|
||||
<!-- Accounts Email -->
|
||||
<div class="col-12">
|
||||
<div class="form-group has-validation">
|
||||
<label for="accounts">Accounts Email</label>
|
||||
<input type="accounts" class="form-control form-control-border @error('supplier_details.accounts') is-invalid @enderror" id="accounts" name="supplier_details[accounts]" placeholder="Accounts Email" value="{{ old('supplier_details.accounts',($o->detail ? $o->detail->accounts : '')) }}">
|
||||
<span class="invalid-feedback" role="alert">
|
||||
@error('supplier_details.accounts')
|
||||
{{ $message }}
|
||||
@enderror
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<!-- Support Email -->
|
||||
<div class="col-12">
|
||||
<div class="form-group has-validation">
|
||||
<label for="support">Support Email</label>
|
||||
<input type="support" class="form-control form-control-border @error('supplier_details.support') is-invalid @enderror" id="support" name="supplier_details[support]" placeholder="Support Email" value="{{ old('supplier_details.support',($o->detail ? $o->detail->support : '')) }}">
|
||||
<span class="invalid-feedback" role="alert">
|
||||
@error('supplier_details.support')
|
||||
{{ $message }}
|
||||
@enderror
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<!-- Payment Details -->
|
||||
<div class="col-12">
|
||||
<div class="form-group has-validation">
|
||||
<label for="payments">Payment Details</label>
|
||||
<input type="payments" class="form-control form-control-border @error('supplier_details.payments') is-invalid @enderror" id="payments" name="supplier_details[payments]" placeholder="Payment Details" value="{{ old('supplier_details.payments',($o->detail ? $o->detail->payments : '')) }}">
|
||||
<span class="invalid-feedback" role="alert">
|
||||
@error('supplier_details.payments')
|
||||
{{ $message }}
|
||||
@enderror
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<!-- Notes -->
|
||||
<div class="col-12">
|
||||
<div class="form-group has-validation">
|
||||
<label for="notes">Notes</label>
|
||||
<textarea class="form-control @error('supplier_details.notes') is-invalid @enderror" id="notes" name="supplier_details[notes]" placeholder="Notes...">{{ old('supplier_details.notes',($o->detail ? $o->detail->notes : '')) }}</textarea>
|
||||
<span class="input-helper">Notes.</span>
|
||||
<span class="invalid-feedback" role="alert">
|
||||
@error('supplier_details.notes')
|
||||
{{ $message }}
|
||||
@enderror
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<!-- Buttons -->
|
||||
<div class="col-12">
|
||||
<a href="{{ url('/home') }}" class="btn btn-danger">Cancel</a>
|
||||
@can('wholesaler')
|
||||
<button type="submit" name="submit" class="btn btn-success mr-0 float-right">@if ($o->exists)Save @else Add @endif</button>
|
||||
@endcan
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
@ -0,0 +1,84 @@
|
||||
<!-- $o = Supplier::class -->
|
||||
<div class="row">
|
||||
<div class="col-5 col-sm-3">
|
||||
<div class="nav flex-column nav-tabs h-100" role="tablist" aria-orientation="vertical">
|
||||
@foreach($o->offeringTypes() as $key => $offering)
|
||||
<a class="nav-link @if($loop->first)active @endif" id="offering-{{ $key }}-tab" data-toggle="pill" href="#offering-{{ $key }}-profile" role="tab" aria-controls="offering-{{ $key }}-tab" aria-selected="true">{{ $offering->type }}</a>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-7 col-sm-9">
|
||||
<div class="tab-content">
|
||||
@foreach($o->offeringTypes() as $key => $offering)
|
||||
<div class="tab-pane text-left fade show @if($loop->first)active @endif" id="offering-{{ $key }}-profile" role="tabpanel" aria-labelledby="offering-{{ $key }}-tab">
|
||||
<table class="table table-sm table-bordered w-100" id="offering-{{ $key }}-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th colspan="8">Product</th>
|
||||
<th colspan="2">Services</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Product ID</th>
|
||||
<th>Product Name</th>
|
||||
<th>Active</th>
|
||||
<th class="text-right">Setup Cost</th>
|
||||
<th class="text-right">Base Cost</th>
|
||||
<th class="text-right">Types</th>
|
||||
<th class="text-right">Products</th>
|
||||
<th class="text-right">Sold</th>
|
||||
<th class="text-right">Active</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
@foreach($xx=$offering->items->with(['types.product.services'])->get() as $oo)
|
||||
<tr>
|
||||
<td>{{ $oo->id }}</td>
|
||||
<td>{{ $oo->name }}</td>
|
||||
<td>{{ $oo->name_long }}</td>
|
||||
<td class="text-right">{{ $oo->active ? 'YES' : 'NO' }}</td>
|
||||
<td class="text-right">{{ number_format($oo->setup_cost_taxable,2) }}</td>
|
||||
<td class="text-right">{{ number_format($oo->base_cost_taxable,2) }}</td>
|
||||
<td class="text-right">{{ number_format($oo->types->count()) }}</td>
|
||||
<td class="text-right">{{ number_format($oo->types->pluck('product')->filter()->count()) }}</td>
|
||||
<td class="text-right">{{ number_format(($x=$oo->types->pluck('product.services')->flatten()->filter())->count()) }}</td>
|
||||
<td class="text-right">{{ number_format($x->where('active')->count()) }}</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
|
||||
<tfoot>
|
||||
<tr>
|
||||
<th colspan="3">TOTALS</th>
|
||||
<td class="text-right" colspan="3">{{ $xx->where('active',TRUE)->count() }}</td>
|
||||
<td class="text-right">{{ number_format(($x=$xx->pluck('types')->flatten()->filter())->count()) }}</td>
|
||||
<td class="text-right">{{ number_format($x->pluck('product')->filter()->count()) }}</td>
|
||||
<td class="text-right">{{ number_format(($xxx=$x->pluck('product.services')->flatten()->filter())->count()) }}</td>
|
||||
<td class="text-right">{{ number_format($xxx->where('active')->count()) }}</td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@section('page-scripts')
|
||||
@css(datatables,bootstrap4)
|
||||
@js(datatables,bootstrap4)
|
||||
|
||||
<script type="text/javascript">
|
||||
$(document).ready(function() {
|
||||
@foreach($o->offeringTypes() as $key => $offering)
|
||||
$('#offering-{{ $key }}-table').DataTable();
|
||||
|
||||
$('#offering-{{ $key }}-table tbody').on('click','tr', function () {
|
||||
$(this).toggleClass('selected');
|
||||
});
|
||||
@endforeach
|
||||
});
|
||||
</script>
|
||||
@append
|
@ -0,0 +1,88 @@
|
||||
<!-- $o = Supplier::class -->
|
||||
<div class="row">
|
||||
<div class="col-5 col-sm-3">
|
||||
<div class="nav flex-column nav-tabs h-100" role="tablist" aria-orientation="vertical">
|
||||
@foreach($o->offeringTypes() as $key => $offering)
|
||||
<a class="nav-link @if($loop->first)active @endif" id="products-{{ $key }}-tab" data-toggle="pill" href="#products-{{ $key }}-profile" role="tab" aria-controls="products-{{ $key }}-tab" aria-selected="true">{{ $offering->type }}</a>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-7 col-sm-9">
|
||||
<div class="tab-content">
|
||||
@foreach($o->offeringTypes() as $key => $offering)
|
||||
<div class="tab-pane text-left fade show @if($loop->first)active @endif" id="products-{{ $key }}-profile" role="tabpanel" aria-labelledby="products-{{ $key }}-tab">
|
||||
<table class="table table-sm table-bordered w-100" id="products-{{ $key }}-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th colspan="7">Product</th>
|
||||
<th colspan="4">Services</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Product ID</th>
|
||||
<th>Product Name</th>
|
||||
<th>Active</th>
|
||||
<th>Default Billing</th>
|
||||
<th class="text-right">Setup Cost</th>
|
||||
<th class="text-right">Base Cost</th>
|
||||
<th class="text-right">Setup Charge</th>
|
||||
<th class="text-right">Base Charge</th>
|
||||
<th class="text-right">Sold</th>
|
||||
<th class="text-right">Active</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
@foreach($xx=$offering->items->with(['types.product.services','types.product.type.supplied','types.product.description'])->get() as $oo)
|
||||
@foreach($oo->types->pluck('product')->filter() as $po)
|
||||
|
||||
<tr>
|
||||
<td>{{ $po->lid }}</td>
|
||||
<td>{{ $po->name_short }}</td>
|
||||
<td>{{ $po->name }}</td>
|
||||
<td class="text-right">{{ $po->active ? 'YES' : 'NO' }}</td>
|
||||
<td class="text-right">{{ $po->billing_interval_string }}</td>
|
||||
<td class="text-right">{{ number_format($po->setup_cost_taxable,2) }}</td>
|
||||
<td class="text-right">{{ number_format($po->base_cost_taxable,2) }}</td>
|
||||
<td class="text-right">{{ number_format($po->setup_charge_taxable,2) }}</td>
|
||||
<td class="text-right">{{ number_format($po->base_charge_taxable,2) }}</td>
|
||||
<td class="text-right">{{ number_format($po->services->count()) }}</td>
|
||||
<td class="text-right">{{ number_format($po->services->where('active')->count()) }}</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
@endforeach
|
||||
</tbody>
|
||||
|
||||
<tfoot>
|
||||
<tr>
|
||||
<th colspan="3">TOTALS</th>
|
||||
<td class="text-right">{{ $xx->where('active',TRUE)->count() }}</td>
|
||||
<th colspan="5"> </th>
|
||||
<td class="text-right">{{ number_format(($xxx=$xx->pluck('types')->flatten()->pluck('product.services')->flatten()->filter())->count()) }}</td>
|
||||
<td class="text-right">{{ number_format($xxx->where('active')->count()) }}</td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@section('page-scripts')
|
||||
@css(datatables,bootstrap4)
|
||||
@js(datatables,bootstrap4)
|
||||
|
||||
<script type="text/javascript">
|
||||
$(document).ready(function() {
|
||||
@foreach($o->offeringTypes() as $key => $offering)
|
||||
$('#products-{{ $key }}-table').DataTable();
|
||||
|
||||
$('#products-{{ $key }}-table tbody').on('click','tr', function () {
|
||||
$(this).toggleClass('selected');
|
||||
});
|
||||
@endforeach
|
||||
});
|
||||
</script>
|
||||
@append
|
@ -1,225 +0,0 @@
|
||||
@extends('adminlte::layouts.app')
|
||||
|
||||
@section('htmlheader_title')
|
||||
{{ $o->name ?: 'New Supplier' }}
|
||||
@endsection
|
||||
@section('page_title')
|
||||
{{ $o->name ?: 'New Supplier' }}
|
||||
@endsection
|
||||
|
||||
@section('contentheader_title')
|
||||
{{ $o->name ?: 'New Supplier' }}
|
||||
@endsection
|
||||
@section('contentheader_description')
|
||||
@endsection
|
||||
|
||||
@section('main-content')
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
@include('adminlte::widget.status')
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-header bg-dark d-flex p-0">
|
||||
<ul class="nav nav-pills w-100 p-2">
|
||||
<li class="nav-item"><a class="nav-link active" href="#details" data-toggle="tab">Details</a></li>
|
||||
<li class="nav-item"><a class="nav-link " href="#products" data-toggle="tab">Products</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="card-body">
|
||||
<div class="tab-content">
|
||||
<div class="tab-pane fade active show" id="details" role="tabpanel">
|
||||
<h3>Supplier Details</h3>
|
||||
<hr>
|
||||
@if(session()->has('success'))
|
||||
<span class="ml-3 pt-0 pb-0 pr-1 pl-1 btn btn-outline-success"><small>{{ session()->get('success') }}</small></span>
|
||||
@endif
|
||||
|
||||
<form class="g-0 needs-validation" method="POST" enctype="multipart/form-data" role="form">
|
||||
@csrf
|
||||
|
||||
<div class="row">
|
||||
<div class="col-4">
|
||||
<div class="row">
|
||||
<!-- Supplier Name -->
|
||||
<div class="col-9">
|
||||
<div class="form-group has-validation">
|
||||
<label for="name">Supplier Name</label>
|
||||
<input type="text" class="form-control form-control-border @error('name') is-invalid @enderror" id="name" name="name" placeholder="Supplier Name" value="{{ old('name',$o->name) }}" required>
|
||||
<span class="invalid-feedback" role="alert">
|
||||
@error('name')
|
||||
{{ $message }}
|
||||
@else
|
||||
Supplier Name required.
|
||||
@enderror
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Supplier Name -->
|
||||
<div class="col-3">
|
||||
<div class="form-group">
|
||||
<div class="custom-control custom-switch custom-switch-off-danger custom-switch-on-success">
|
||||
<input type="checkbox" class="custom-control-input" id="active" name="active" {{ old('active',$o->active) ? 'checked' : '' }}>
|
||||
<label class="custom-control-label" for="active">Active</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<!-- Address Lines -->
|
||||
<div class="col-12">
|
||||
<div class="form-group has-validation">
|
||||
<label for="address1">Address Lines</label>
|
||||
<input type="text" class="form-control form-control-border @error('address1') is-invalid @enderror" id="address1" name="address1" placeholder="Address1" value="{{ old('address1',$o->address1) }}" required>
|
||||
<input type="text" class="form-control form-control-border" id="address2" name="address2" placeholder="Address2" value="{{ old('address2',$o->address2) }}">
|
||||
<span class="invalid-feedback" role="alert">
|
||||
@error('address1')
|
||||
{{ $message }}
|
||||
@else
|
||||
Atleast 1 address line required.
|
||||
@enderror
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<!-- City -->
|
||||
<div class="col-12">
|
||||
<div class="form-group has-validation">
|
||||
<label for="city">City</label>
|
||||
<input type="text" class="form-control form-control-border @error('city') is-invalid @enderror" id="city" name="city" placeholder="City" value="{{ old('city',$o->city) }}">
|
||||
<span class="invalid-feedback" role="alert">
|
||||
@error('city')
|
||||
{{ $message }}
|
||||
@else
|
||||
City is required.
|
||||
@enderror
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<!-- State -->
|
||||
<div class="col-9">
|
||||
<div class="form-group has-validation">
|
||||
<label for="state">State</label>
|
||||
<input type="text" class="form-control form-control-border @error('state') is-invalid @enderror" id="state" name="state" placeholder="State" value="{{ old('state',$o->state) }}">
|
||||
<span class="invalid-feedback" role="alert">
|
||||
@error('state')
|
||||
{{ $message }}
|
||||
@else
|
||||
State is required.
|
||||
@enderror
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Postal Code -->
|
||||
<div class="col-3">
|
||||
<div class="form-group has-validation">
|
||||
<label for="postcode">Postal Code</label>
|
||||
<input type="text" class="form-control form-control-border @error('postcode') is-invalid @enderror" id="postcode" name="postcode" placeholder="Postal Code" value="{{ old('postcode',$o->postcode) }}">
|
||||
<span class="invalid-feedback" role="alert">
|
||||
@error('postcode')
|
||||
{{ $message }}
|
||||
@else
|
||||
Postcode is required.
|
||||
@enderror
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="offset-1 col-4">
|
||||
<div class="row">
|
||||
<!-- Accounts Email -->
|
||||
<div class="col-12">
|
||||
<div class="form-group has-validation">
|
||||
<label for="accounts">Accounts Email</label>
|
||||
<input type="accounts" class="form-control form-control-border @error('supplier_details.accounts') is-invalid @enderror" id="accounts" name="supplier_details[accounts]" placeholder="Accounts Email" value="{{ old('supplier_details.accounts',($o->detail ? $o->detail->accounts : '')) }}">
|
||||
<span class="invalid-feedback" role="alert">
|
||||
@error('supplier_details.accounts')
|
||||
{{ $message }}
|
||||
@enderror
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<!-- Support Email -->
|
||||
<div class="col-12">
|
||||
<div class="form-group has-validation">
|
||||
<label for="support">Support Email</label>
|
||||
<input type="support" class="form-control form-control-border @error('supplier_details.support') is-invalid @enderror" id="support" name="supplier_details[support]" placeholder="Support Email" value="{{ old('supplier_details.support',($o->detail ? $o->detail->support : '')) }}">
|
||||
<span class="invalid-feedback" role="alert">
|
||||
@error('supplier_details.support')
|
||||
{{ $message }}
|
||||
@enderror
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<!-- Payment Details -->
|
||||
<div class="col-12">
|
||||
<div class="form-group has-validation">
|
||||
<label for="payments">Payment Details</label>
|
||||
<input type="payments" class="form-control form-control-border @error('supplier_details.payments') is-invalid @enderror" id="payments" name="supplier_details[payments]" placeholder="Payment Details" value="{{ old('supplier_details.payments',($o->detail ? $o->detail->payments : '')) }}">
|
||||
<span class="invalid-feedback" role="alert">
|
||||
@error('supplier_details.payments')
|
||||
{{ $message }}
|
||||
@enderror
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<!-- Notes -->
|
||||
<div class="col-12">
|
||||
<div class="form-group has-validation">
|
||||
<label for="notes">Notes</label>
|
||||
<textarea class="form-control @error('supplier_details.notes') is-invalid @enderror" id="notes" name="supplier_details[notes]" placeholder="Notes...">{{ old('supplier_details.notes',($o->detail ? $o->detail->notes : '')) }}</textarea>
|
||||
<span class="input-helper">Notes.</span>
|
||||
<span class="invalid-feedback" role="alert">
|
||||
@error('supplier_details.notes')
|
||||
{{ $message }}
|
||||
@enderror
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<!-- Buttons -->
|
||||
<div class="col-12">
|
||||
<a href="{{ url('/home') }}" class="btn btn-danger">Cancel</a>
|
||||
@can('wholesaler')
|
||||
<button type="submit" name="submit" class="btn btn-success mr-0 float-right">@if ($o->exists)Save @else Add @endif</button>
|
||||
@endcan
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="tab-pane fade" id="products" role="tabpanel">
|
||||
Products.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
@ -102,7 +102,7 @@
|
||||
<tr id="invoice-services">
|
||||
<td>{{ $po->count }}</td>
|
||||
<td>#{{ $po->lid }}</td>
|
||||
<td colspan="2">{{ $po->name($o->account->user->language) }}</td>
|
||||
<td colspan="2">{{ $po->name }}</td>
|
||||
<td colspan="3" class="text-right">${{ number_format($o->items->filter(function($item) use ($po) {return $item->product_id == $po->id; })->sum('total'),$o->currency()->rounding) }}</td>
|
||||
</tr>
|
||||
|
||||
|
@ -42,7 +42,8 @@
|
||||
<li class="nav-item"><a class="nav-link" href="#emails" data-toggle="tab">Emails</a></li>
|
||||
--}}
|
||||
@can('wholesaler')
|
||||
<li class="nav-item ml-auto"><a class="nav-link" href="#internal" data-toggle="tab">Internal</a></li>
|
||||
<li class="nav-item ml-auto"><a class="nav-link" href="#internal" data-toggle="tab">Billing History</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="#internal" data-toggle="tab">Internal</a></li>
|
||||
<li class="nav-item"><a class="nav-link {{ session()->has('service_update') ? 'active' : '' }}" href="#update" data-toggle="tab">Update</a></li>
|
||||
@endcan
|
||||
</ul>
|
||||
|
@ -10,7 +10,7 @@
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Category</th>
|
||||
<th>Type</th>
|
||||
<th>Service</th>
|
||||
<th>Product</th>
|
||||
<th>Next Invoice</th>
|
||||
@ -21,9 +21,9 @@
|
||||
@foreach ($o->services as $oo)
|
||||
<tr>
|
||||
<td><a href="{{ url('u/service',[$oo->id]) }}">{{ $oo->sid }}</a></td>
|
||||
<td>{{ $oo->product_category }}</td>
|
||||
<td>{{ $oo->service_type }}</td>
|
||||
<td>{{ $oo->name_short }}</td>
|
||||
<td>{{ $oo->product_name }}</td>
|
||||
<td>{{ $oo->product->name }}</td>
|
||||
<td>{{ $oo->external_billing ? '-' : $oo->next_invoice->format('Y-m-d') }}</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
|
@ -7,7 +7,7 @@
|
||||
@php
|
||||
$po = $selected = NULL;
|
||||
@endphp
|
||||
@foreach (\App\Models\Product::active()->get()->filter(function($item) { return $item->type && (get_class($item->type) == 'App\Models\Product\Adsl'); })->sortBy('name') as $o)
|
||||
@foreach (\App\Models\Product::active()->get()->filter(function($item) { return $item->type && (get_class($item->type) == 'App\Models\Product\Broadband'); })->sortBy('name') as $o)
|
||||
@php
|
||||
if ($o->id == old('product_id')) {
|
||||
$selected = 'selected';
|
||||
|
@ -1,3 +1,4 @@
|
||||
<!-- $o = Service\Broadband::class -->
|
||||
<div class="card">
|
||||
@if($o->service->isPending())
|
||||
<div class="ribbon-wrapper ribbon-lg">
|
||||
@ -44,7 +45,7 @@
|
||||
@endif
|
||||
<tr>
|
||||
<th>Speed</th>
|
||||
<td>{{ $o->service->product->type->product->speed }} Mbps</td>
|
||||
<td>{{ $o->supplied()->speed() }} Mbps</td>
|
||||
</tr>
|
||||
<!-- @todo -->
|
||||
<tr>
|
||||
|
@ -9,7 +9,7 @@
|
||||
<div class="container">
|
||||
<div class="col-12">
|
||||
<h1>Order Service</h1>
|
||||
@if ($errors->count()))<h4><span class="note-danger">There were errors with your order, please try again.</span></h4>@endif
|
||||
@if ($errors->count())<h4><span class="note-danger">There were errors with your order, please try again.</span></h4>@endif
|
||||
<div class="order-page" id="order-page">
|
||||
<div class="row">
|
||||
<div class="col-3">
|
||||
|
@ -1,8 +1,9 @@
|
||||
@if(View::exists('order.widget.info.'.$o->category))
|
||||
<!-- $o = Product::class [{{$o->product_type}}]-->
|
||||
@if(View::exists('order.widget.info.'.strtolower($o->product_type)))
|
||||
<div class="box box-primary">
|
||||
<div class="box-body">
|
||||
{{-- Return Category Requirements --}}
|
||||
@include('order.widget.info.'.$o->category)
|
||||
@include('order.widget.info.'.strtolower($o->product_type))
|
||||
|
||||
{{-- Return Supplier Requirements --}}
|
||||
{{-- Return Product Requirements --}}
|
||||
|
@ -1,30 +0,0 @@
|
||||
<div class="col-md-12">
|
||||
<p>{!! $o->description !!}</p>
|
||||
</div>
|
||||
|
||||
<table class="table table-condensed">
|
||||
<tr>
|
||||
<th>Type</th>
|
||||
<td class="text-right">VOIP</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Setup Charges</th>
|
||||
<td class="text-right">${{ is_numeric($o->setup_cost) ? number_format($o->setup_cost,2) : $o->setup_cost }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Cost</th>
|
||||
<td class="text-right">${{ is_numeric($o->default_cost) ? number_format($o->default_cost,2) : $o->default_cost }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Default Billing</th>
|
||||
<td class="text-right">{{ is_numeric($o->default_billing) ? number_format($o->default_billing,2) : $o->default_billing }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Contract Term</th>
|
||||
<td class="text-right">{{ $o->contract_term }} mths</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Minimum Costs</th>
|
||||
<td class="text-right">${{ is_numeric($o->minimum_cost) ? number_format($o->minimum_cost,2) : $o->minimum_cost }}</td>
|
||||
</tr>
|
||||
</table>
|
@ -1,3 +1,4 @@
|
||||
<!-- $o = Product::class -->
|
||||
<div class="col-md-12">
|
||||
<p>{!! $o->description !!}</p>
|
||||
</div>
|
||||
@ -9,15 +10,15 @@
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Setup Charges <sup>*</sup></th>
|
||||
<td class="text-right">${{ is_numeric($o->setup_cost) ? number_format($o->setup_cost,2) : $o->setup_cost }}</td>
|
||||
<td class="text-right">${{ number_format($o->setup_charge_taxable,2) }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Cost <sup>+</sup></th>
|
||||
<td class="text-right">${{ is_numeric($o->default_cost) ? number_format($o->default_cost,2) : $o->default_cost }}</td>
|
||||
<td class="text-right">${{ number_format($o->base_charge_taxable,2) }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Default Billing</th>
|
||||
<td class="text-right">{{ is_numeric($o->default_billing) ? number_format($o->default_billing,2) : $o->default_billing }}</td>
|
||||
<td class="text-right">{{ $o->billing_interval_string }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Contract Term</th>
|
||||
@ -25,7 +26,7 @@
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Minimum Costs <sup>+*</sup></th>
|
||||
<td class="text-right">${{ is_numeric($o->minimum_cost) ? number_format($o->minimum_cost,2) : $o->minimum_cost }}</td>
|
||||
<td class="text-right">${{ number_format($o->min_charge_taxable,2) }}</td>
|
||||
</tr>
|
||||
|
||||
<tfoot>
|
@ -0,0 +1,31 @@
|
||||
<!-- $o = Product::class -->
|
||||
<div class="col-md-12">
|
||||
<p>{!! $o->description !!}</p>
|
||||
</div>
|
||||
|
||||
<table class="table table-condensed">
|
||||
<tr>
|
||||
<th>Type</th>
|
||||
<td class="text-right">{{ $o->product_type }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Setup Charges</th>
|
||||
<td class="text-right">${{ number_format($o->setup_charge_taxable,2) }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Cost</th>
|
||||
<td class="text-right">${{ number_format($o->base_charge_taxable,2) }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Default Billing</th>
|
||||
<td class="text-right">{{ $o->billing_interval_string }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Contract Term</th>
|
||||
<td class="text-right">{{ $o->contract_term }} mths</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Minimum Costs</th>
|
||||
<td class="text-right">${{ number_format($o->min_charge_taxable,2) }}</td>
|
||||
</tr>
|
||||
</table>
|
@ -1,4 +1,5 @@
|
||||
@if(View::exists('order.widget.order.'.$o->category))
|
||||
<!-- $o = Product::class -->
|
||||
@if(View::exists('order.widget.order.'.strtolower($o->product_type)))
|
||||
<div class="box box-primary">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title">Order Configuration</h3>
|
||||
@ -6,7 +7,7 @@
|
||||
|
||||
<div class="box-body">
|
||||
{{-- Return Category Requirements --}}
|
||||
@include('order.widget.order.'.$o->category)
|
||||
@include('order.widget.order.'.strtolower($o->product_type))
|
||||
|
||||
{{-- Return Supplier Requirements --}}
|
||||
{{-- Return Product Requirements --}}
|
||||
|
Loading…
Reference in New Issue
Block a user