From 1e9f15b40f502c7e51f1bf9ea45340117e94aa20 Mon Sep 17 00:00:00 2001 From: Deon George Date: Fri, 24 Dec 2021 12:14:01 +1100 Subject: [PATCH] Work on products, first completed broadband --- app/Console/Commands/ServiceList.php | 49 +- app/Http/Controllers/AdminController.php | 4 +- app/Http/Controllers/OrderController.php | 27 +- app/Http/Controllers/SearchController.php | 2 +- app/Interfaces/IDs.php | 4 +- app/Interfaces/ProductItem.php | 76 +++ app/Interfaces/ProductSupplier.php | 17 + app/Interfaces/SupplierItem.php | 90 ++++ app/Models/Account.php | 23 +- app/Models/AccountGroup.php | 11 + app/Models/AdslPlan.php | 36 -- app/Models/AdslSupplier.php | 3 + app/Models/Base/ProductType.php | 8 +- app/Models/Invoice.php | 94 +++- app/Models/PlanVoip.php | 41 -- app/Models/Product.php | 467 ++++++++++-------- app/Models/Product/Adsl.php | 159 ------ app/Models/Product/Broadband.php | 178 +++++++ app/Models/Product/Domain.php | 15 + app/Models/Product/Generic.php | 14 + app/Models/Product/Host.php | 15 + app/Models/Product/SSL.php | 15 + app/Models/Product/Voip.php | 47 ++ app/Models/Service.php | 83 +++- .../Service/{Adsl.php => Broadband.php} | 43 +- app/Models/Service/SSL.php | 2 +- app/Models/Site.php | 5 + app/Models/Supplier.php | 75 +++ .../Broadband.php} | 149 ++++-- app/Models/Supplier/Ethernet.php | 7 + app/Models/Supplier/HSPA.php | 7 + app/Models/SupplierDetail.php | 2 + app/Models/Tax.php | 27 + app/Models/User.php | 2 +- app/Traits/ProductDetails.php | 21 + database/factories/AdslPlanFactory.php | 16 +- .../factories/AdslSupplierPlanFactory.php | 2 +- database/factories/ProductFactory.php | 3 +- .../2021_12_20_000000_int_unsigned.php | 18 +- .../2021_12_20_225017_optimize_product.php | 295 +++++++++++ public/css/fixes.css | 6 + .../a/service/widgets/internal.blade.php | 104 ++-- .../adminlte/a/supplier/details.blade.php | 57 +++ .../find.blade.php} | 0 .../a/supplier/widgets/connections.blade.php | 34 ++ .../a/supplier/widgets/detail.blade.php | 185 +++++++ .../a/supplier/widgets/offerings.blade.php | 84 ++++ .../a/supplier/widgets/products.blade.php | 88 ++++ .../adminlte/a/supplierdetails.blade.php | 225 --------- .../backend/adminlte/u/invoice/home.blade.php | 2 +- .../backend/adminlte/u/service/home.blade.php | 3 +- .../u/service/widgets/active.blade.php | 6 +- .../widgets/broadband/change.blade.php | 2 +- .../widgets/broadband/details.blade.php | 3 +- .../frontend/metronic/order/home.blade.php | 2 +- .../metronic/order/widget/info.blade.php | 5 +- .../metronic/order/widget/info/VOIP.blade.php | 30 -- .../{ADSL.blade.php => broadband.blade.php} | 9 +- .../metronic/order/widget/info/voip.blade.php | 31 ++ .../metronic/order/widget/order.blade.php | 5 +- .../{ADSL.blade.php => broadband.blade.php} | 0 .../order/{VOIP.blade.php => voip.blade.php} | 0 62 files changed, 2139 insertions(+), 894 deletions(-) create mode 100644 app/Interfaces/ProductItem.php create mode 100644 app/Interfaces/SupplierItem.php create mode 100644 app/Models/AccountGroup.php delete mode 100644 app/Models/AdslPlan.php delete mode 100644 app/Models/PlanVoip.php delete mode 100644 app/Models/Product/Adsl.php create mode 100644 app/Models/Product/Broadband.php rename app/Models/Service/{Adsl.php => Broadband.php} (88%) rename app/Models/{AdslSupplierPlan.php => Supplier/Broadband.php} (51%) create mode 100644 app/Models/Supplier/Ethernet.php create mode 100644 app/Models/Supplier/HSPA.php create mode 100644 app/Traits/ProductDetails.php create mode 100644 database/migrations/2021_12_20_225017_optimize_product.php create mode 100644 resources/views/theme/backend/adminlte/a/supplier/details.blade.php rename resources/views/theme/backend/adminlte/a/{supplier.blade.php => supplier/find.blade.php} (100%) create mode 100644 resources/views/theme/backend/adminlte/a/supplier/widgets/connections.blade.php create mode 100644 resources/views/theme/backend/adminlte/a/supplier/widgets/detail.blade.php create mode 100644 resources/views/theme/backend/adminlte/a/supplier/widgets/offerings.blade.php create mode 100644 resources/views/theme/backend/adminlte/a/supplier/widgets/products.blade.php delete mode 100644 resources/views/theme/backend/adminlte/a/supplierdetails.blade.php delete mode 100644 resources/views/theme/frontend/metronic/order/widget/info/VOIP.blade.php rename resources/views/theme/frontend/metronic/order/widget/info/{ADSL.blade.php => broadband.blade.php} (57%) create mode 100644 resources/views/theme/frontend/metronic/order/widget/info/voip.blade.php rename resources/views/theme/frontend/metronic/order/widget/order/{ADSL.blade.php => broadband.blade.php} (100%) rename resources/views/theme/frontend/metronic/order/widget/order/{VOIP.blade.php => voip.blade.php} (100%) diff --git a/app/Console/Commands/ServiceList.php b/app/Console/Commands/ServiceList.php index 7a16f9c..601e321 100644 --- a/app/Console/Commands/ServiceList.php +++ b/app/Console/Commands/ServiceList.php @@ -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, )); } } diff --git a/app/Http/Controllers/AdminController.php b/app/Http/Controllers/AdminController.php index 37aa591..29cf8d4 100644 --- a/app/Http/Controllers/AdminController.php +++ b/app/Http/Controllers/AdminController.php @@ -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); } diff --git a/app/Http/Controllers/OrderController.php b/app/Http/Controllers/OrderController.php index cbaa381..adc9982 100644 --- a/app/Http/Controllers/OrderController.php +++ b/app/Http/Controllers/OrderController.php @@ -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]); } -} +} \ No newline at end of file diff --git a/app/Http/Controllers/SearchController.php b/app/Http/Controllers/SearchController.php index 3189fd0..4897e44 100644 --- a/app/Http/Controllers/SearchController.php +++ b/app/Http/Controllers/SearchController.php @@ -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) diff --git a/app/Interfaces/IDs.php b/app/Interfaces/IDs.php index cd7dbe9..dfdf138 100644 --- a/app/Interfaces/IDs.php +++ b/app/Interfaces/IDs.php @@ -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; } \ No newline at end of file diff --git a/app/Interfaces/ProductItem.php b/app/Interfaces/ProductItem.php new file mode 100644 index 0000000..f5c54fc --- /dev/null +++ b/app/Interfaces/ProductItem.php @@ -0,0 +1,76 @@ +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); diff --git a/app/Models/AccountGroup.php b/app/Models/AccountGroup.php new file mode 100644 index 0000000..e47c74b --- /dev/null +++ b/app/Models/AccountGroup.php @@ -0,0 +1,11 @@ +[ - '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'); - } -} \ No newline at end of file diff --git a/app/Models/AdslSupplier.php b/app/Models/AdslSupplier.php index 91e0780..cd246ed 100644 --- a/app/Models/AdslSupplier.php +++ b/app/Models/AdslSupplier.php @@ -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'; diff --git a/app/Models/Base/ProductType.php b/app/Models/Base/ProductType.php index d66057c..09a2b63 100644 --- a/app/Models/Base/ProductType.php +++ b/app/Models/Base/ProductType.php @@ -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'; } \ No newline at end of file diff --git a/app/Models/Invoice.php b/app/Models/Invoice.php index 2070e00..3d83208 100644 --- a/app/Models/Invoice.php +++ b/app/Models/Invoice.php @@ -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; }); } diff --git a/app/Models/PlanVoip.php b/app/Models/PlanVoip.php deleted file mode 100644 index f5fc54e..0000000 --- a/app/Models/PlanVoip.php +++ /dev/null @@ -1,41 +0,0 @@ -[ - '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; -} \ No newline at end of file diff --git a/app/Models/Product.php b/app/Models/Product.php index 29996fe..e124f32 100644 --- a/app/Models/Product.php +++ b/app/Models/Product.php @@ -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); } } \ No newline at end of file diff --git a/app/Models/Product/Adsl.php b/app/Models/Product/Adsl.php deleted file mode 100644 index 44bc7fb..0000000 --- a/app/Models/Product/Adsl.php +++ /dev/null @@ -1,159 +0,0 @@ -'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'); - } -} \ No newline at end of file diff --git a/app/Models/Product/Broadband.php b/app/Models/Product/Broadband.php new file mode 100644 index 0000000..ddf48a8 --- /dev/null +++ b/app/Models/Product/Broadband.php @@ -0,0 +1,178 @@ +[ + '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; + } +} \ No newline at end of file diff --git a/app/Models/Product/Domain.php b/app/Models/Product/Domain.php index d0b0ec8..348d59e 100644 --- a/app/Models/Product/Domain.php +++ b/app/Models/Product/Domain.php @@ -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; + } } \ No newline at end of file diff --git a/app/Models/Product/Generic.php b/app/Models/Product/Generic.php index 81b1797..6d81e20 100644 --- a/app/Models/Product/Generic.php +++ b/app/Models/Product/Generic.php @@ -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; + } } diff --git a/app/Models/Product/Host.php b/app/Models/Product/Host.php index cdd961a..e9f23d2 100644 --- a/app/Models/Product/Host.php +++ b/app/Models/Product/Host.php @@ -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; + } } \ No newline at end of file diff --git a/app/Models/Product/SSL.php b/app/Models/Product/SSL.php index 874e980..0896bbb 100644 --- a/app/Models/Product/SSL.php +++ b/app/Models/Product/SSL.php @@ -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; + } } \ No newline at end of file diff --git a/app/Models/Product/Voip.php b/app/Models/Product/Voip.php index f500361..e271dc5 100644 --- a/app/Models/Product/Voip.php +++ b/app/Models/Product/Voip.php @@ -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; + } } \ No newline at end of file diff --git a/app/Models/Service.php b/app/Models/Service.php index d97f297..9405f0a 100644 --- a/app/Models/Service.php +++ b/app/Models/Service.php @@ -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; diff --git a/app/Models/Service/Adsl.php b/app/Models/Service/Broadband.php similarity index 88% rename from app/Models/Service/Adsl.php rename to app/Models/Service/Broadband.php index 99b7221..06cc33b 100644 --- a/app/Models/Service/Adsl.php +++ b/app/Models/Service/Broadband.php @@ -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 * diff --git a/app/Models/Service/SSL.php b/app/Models/Service/SSL.php index 33455ef..232b293 100644 --- a/app/Models/Service/SSL.php +++ b/app/Models/Service/SSL.php @@ -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 diff --git a/app/Models/Site.php b/app/Models/Site.php index 65b7147..6749972 100644 --- a/app/Models/Site.php +++ b/app/Models/Site.php @@ -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 */ /** diff --git a/app/Models/Supplier.php b/app/Models/Supplier.php index 6d0d1c7..50c6014 100644 --- a/app/Models/Supplier.php +++ b/app/Models/Supplier.php @@ -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; + } } \ No newline at end of file diff --git a/app/Models/AdslSupplierPlan.php b/app/Models/Supplier/Broadband.php similarity index 51% rename from app/Models/AdslSupplierPlan.php rename to app/Models/Supplier/Broadband.php index 0367df5..d7e18fa 100644 --- a/app/Models/AdslSupplierPlan.php +++ b/app/Models/Supplier/Broadband.php @@ -1,14 +1,112 @@ '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; } diff --git a/app/Models/Supplier/Ethernet.php b/app/Models/Supplier/Ethernet.php new file mode 100644 index 0000000..7db3244 --- /dev/null +++ b/app/Models/Supplier/Ethernet.php @@ -0,0 +1,7 @@ +'collection' ]; + /* RELATIONS */ public function supplier() diff --git a/app/Models/Tax.php b/app/Models/Tax.php index b135a92..3120b4a 100644 --- a/app/Models/Tax.php +++ b/app/Models/Tax.php @@ -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); + } } \ No newline at end of file diff --git a/app/Models/User.php b/app/Models/User.php index 879c57e..22fcaaf 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -495,7 +495,7 @@ class User extends Authenticatable } $result->load([ - 'product.descriptions', + 'product.description', 'service.type', ]); diff --git a/app/Traits/ProductDetails.php b/app/Traits/ProductDetails.php new file mode 100644 index 0000000..9ada39a --- /dev/null +++ b/app/Traits/ProductDetails.php @@ -0,0 +1,21 @@ +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, diff --git a/database/factories/AdslSupplierPlanFactory.php b/database/factories/AdslSupplierPlanFactory.php index 4cfb7ad..49e3100 100644 --- a/database/factories/AdslSupplierPlanFactory.php +++ b/database/factories/AdslSupplierPlanFactory.php @@ -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, diff --git a/database/factories/ProductFactory.php b/database/factories/ProductFactory.php index 19b5beb..164a1a0 100644 --- a/database/factories/ProductFactory.php +++ b/database/factories/ProductFactory.php @@ -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', ]; diff --git a/database/migrations/2021_12_20_000000_int_unsigned.php b/database/migrations/2021_12_20_000000_int_unsigned.php index a9037b7..00b9110 100644 --- a/database/migrations/2021_12_20_000000_int_unsigned.php +++ b/database/migrations/2021_12_20_000000_int_unsigned.php @@ -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'); }); diff --git a/database/migrations/2021_12_20_225017_optimize_product.php b/database/migrations/2021_12_20_225017_optimize_product.php new file mode 100644 index 0000000..11f9683 --- /dev/null +++ b/database/migrations/2021_12_20_225017_optimize_product.php @@ -0,0 +1,295 @@ +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'); + } +} diff --git a/public/css/fixes.css b/public/css/fixes.css index 9081cfb..cbc5d4b 100644 --- a/public/css/fixes.css +++ b/public/css/fixes.css @@ -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; } diff --git a/resources/views/theme/backend/adminlte/a/service/widgets/internal.blade.php b/resources/views/theme/backend/adminlte/a/service/widgets/internal.blade.php index 1be797d..05e1698 100644 --- a/resources/views/theme/backend/adminlte/a/service/widgets/internal.blade.php +++ b/resources/views/theme/backend/adminlte/a/service/widgets/internal.blade.php @@ -1,32 +1,80 @@ + + + + + + + + + + + - + + + + - - - - @if($o->product->type) - - - - - - - - - - - - - - - @endif - - - - @if($o->product->type AND $o->product->type->cost) - - - - @endif + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 {{ $o->product->type->supplied->supplier_detail->supplier->name }}Us 
Supplier{{ $o->product->type ? $o->product->type->supplier->name : 'Supplier Unknown' }}Product#{{ ($s=$o->product->type->supplied)->id }}: {{ $s->name }}#{{ $o->product->id }}: {{ $o->product->name }}{{ $s->type }}
Supplier Product#{{ $o->product_id }}: {{ $o->product->type ? $o->product->type->product->product_id : 'Product Unknown' }}
Supplier Setup${{ number_format($o->product->type->product->setup_cost*1.1,2) }}
Supplier Cost${{ number_format($o->product->type->cost,2) }}
Supplier Contract{{ $o->product->type->product->contract_term }} months
Supplier Min Cost${{ number_format((($x=$o->product->type->product)->setup_cost+$x->base_cost*$x->contract_term)*1.1,2) }}
Price${{ number_format($o->billing_monthly_price,2) }} (${{ number_format($o->billing_monthly_price*12,2) }} Annually)
Markup{{ number_format(($o->billing_monthly_price/$o->product->type->cost-1)*100,2) }}%
Setup${{ number_format($a=\App\Models\Tax::tax_calc($s->setup_cost,$o->account->taxes),2) }}${{ number_format($b=\App\Models\Tax::tax_calc($o->product->setup_charge,$o->account->taxes),2) }} + @if ($a > $b) + ({{ number_format(($a-$b)/($b ?: 1)*100,1) }})% + @else + {{ number_format(($b-$a)/($a ?: 1)*100,1) }}% + @endif +
Billed{{ $s->billing_interval_string }}{{ $o->product->billing_interval_string }} 
Billing Charge${{ 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) }}${{ number_format($b=\App\Models\Tax::tax_calc($o->product->getBaseChargeAttribute($o->billing_interval),$o->account->taxes),2) }} + @if ($a > $b) + ({{ number_format(($a-$b)/($b ?: 1)*100,1) }})% + @else + {{ number_format(($b-$a)/($a ?: 1)*100,1) }}% + @endif +
Monthly Cost${{ number_format($a=\App\Models\Tax::tax_calc($s->base_cost*\App\Models\Invoice::billing_change($s->billing_interval,1),$o->account->taxes),2) }}${{ 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) }} + @if ($a > $b) + ({{ number_format(($a-$b)/($b ?: 1)*100,1) }})% + @else + {{ number_format(($b-$a)/($a ?: 1)*100,1) }}% + @endif +
Contract{{ $s->contract_term }} months{{ $o->product->contract_term }} months 
Min Cost${{ number_format($a=\App\Models\Tax::tax_calc($s->min_cost,$o->account->taxes),2) }}${{ number_format($b=\App\Models\Tax::tax_calc($o->product->getMinChargeAttribute($o->billing_interval),$o->account->taxes),2) }} + @if ($a > $b) + ({{ number_format(($a-$b)/($b ?: 1)*100,1) }})% + @else + {{ number_format(($b-$a)/($a ?: 1)*100,1) }}% + @endif +
\ No newline at end of file diff --git a/resources/views/theme/backend/adminlte/a/supplier/details.blade.php b/resources/views/theme/backend/adminlte/a/supplier/details.blade.php new file mode 100644 index 0000000..65112eb --- /dev/null +++ b/resources/views/theme/backend/adminlte/a/supplier/details.blade.php @@ -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') +
+
+ @include('adminlte::widget.status') +
+
+ +
+
+
+ + +
+
+
+ @include('a.supplier.widgets.detail') +
+ +
+ @include('a.supplier.widgets.products') +
+ +
+ @include('a.supplier.widgets.offerings') +
+ +
+ @include('a.supplier.widgets.connections') +
+
+
+
+
+
+@endsection \ No newline at end of file diff --git a/resources/views/theme/backend/adminlte/a/supplier.blade.php b/resources/views/theme/backend/adminlte/a/supplier/find.blade.php similarity index 100% rename from resources/views/theme/backend/adminlte/a/supplier.blade.php rename to resources/views/theme/backend/adminlte/a/supplier/find.blade.php diff --git a/resources/views/theme/backend/adminlte/a/supplier/widgets/connections.blade.php b/resources/views/theme/backend/adminlte/a/supplier/widgets/connections.blade.php new file mode 100644 index 0000000..3c96bf5 --- /dev/null +++ b/resources/views/theme/backend/adminlte/a/supplier/widgets/connections.blade.php @@ -0,0 +1,34 @@ + +
+
+ + + + + + + + + + + + + + @foreach ($o->offeringTypes() as $key => $offering) + + + @if(Arr::get($o->detail->connections,$key)) + + + + + + @else + + @endif + + @endforeach + +
Service TypeUser NamePasswordURLLast ConnectOfferings
{{ $offering->type }}{{ Arr::get($o->detail->connections,$key.'.user') }}{{ Arr::get($o->detail->connections,$key.'.pass') }}{{ Arr::get($o->detail->connections,$key.'.url') }}{{ \Carbon\Carbon::createFromFormat('Y-m-d',Arr::get($o->detail->connections,$key.'.last'))->format('Y-m-d') }}{{ number_format($offering->items->count()) }} 
+
+
\ No newline at end of file diff --git a/resources/views/theme/backend/adminlte/a/supplier/widgets/detail.blade.php b/resources/views/theme/backend/adminlte/a/supplier/widgets/detail.blade.php new file mode 100644 index 0000000..b0f5d4c --- /dev/null +++ b/resources/views/theme/backend/adminlte/a/supplier/widgets/detail.blade.php @@ -0,0 +1,185 @@ + +
+
+

Supplier Details

+
+ @if(session()->has('success')) + {{ session()->get('success') }} + @endif + +
+ @csrf + +
+
+
+ +
+
+ + + + @error('name') + {{ $message }} + @else + Supplier Name required. + @enderror + +
+
+ + +
+
+
+ active) ? 'checked' : '' }}> + +
+
+
+
+ +
+ +
+
+ + + + + @error('address1') + {{ $message }} + @else + Atleast 1 address line required. + @enderror + +
+
+
+ +
+ +
+
+ + + + @error('city') + {{ $message }} + @else + City is required. + @enderror + +
+
+
+ +
+ +
+
+ + + + @error('state') + {{ $message }} + @else + State is required. + @enderror + +
+
+ + +
+
+ + + + @error('postcode') + {{ $message }} + @else + Postcode is required. + @enderror + +
+
+
+
+ +
+
+ +
+
+ + + + @error('supplier_details.accounts') + {{ $message }} + @enderror + +
+
+
+ +
+ +
+
+ + + + @error('supplier_details.support') + {{ $message }} + @enderror + +
+
+
+ +
+ +
+
+ + + + @error('supplier_details.payments') + {{ $message }} + @enderror + +
+
+
+
+
+ +
+ +
+
+ + + Notes. + + @error('supplier_details.notes') + {{ $message }} + @enderror + +
+
+
+ +
+ +
+ Cancel + @can('wholesaler') + + @endcan +
+
+
+
+
\ No newline at end of file diff --git a/resources/views/theme/backend/adminlte/a/supplier/widgets/offerings.blade.php b/resources/views/theme/backend/adminlte/a/supplier/widgets/offerings.blade.php new file mode 100644 index 0000000..f0f12a7 --- /dev/null +++ b/resources/views/theme/backend/adminlte/a/supplier/widgets/offerings.blade.php @@ -0,0 +1,84 @@ + +
+
+ +
+ +
+
+ @foreach($o->offeringTypes() as $key => $offering) +
+ + + + + + + + + + + + + + + + + + + + + + @foreach($xx=$offering->items->with(['types.product.services'])->get() as $oo) + + + + + + + + + + + + + @endforeach + + + + + + + + + + + + +
ProductServices
IDProduct IDProduct NameActiveSetup CostBase CostTypesProductsSoldActive
{{ $oo->id }}{{ $oo->name }}{{ $oo->name_long }}{{ $oo->active ? 'YES' : 'NO' }}{{ number_format($oo->setup_cost_taxable,2) }}{{ number_format($oo->base_cost_taxable,2) }}{{ number_format($oo->types->count()) }}{{ number_format($oo->types->pluck('product')->filter()->count()) }}{{ number_format(($x=$oo->types->pluck('product.services')->flatten()->filter())->count()) }}{{ number_format($x->where('active')->count()) }}
TOTALS{{ $xx->where('active',TRUE)->count() }}{{ number_format(($x=$xx->pluck('types')->flatten()->filter())->count()) }}{{ number_format($x->pluck('product')->filter()->count()) }}{{ number_format(($xxx=$x->pluck('product.services')->flatten()->filter())->count()) }}{{ number_format($xxx->where('active')->count()) }}
+
+ @endforeach +
+
+
+ +@section('page-scripts') + @css(datatables,bootstrap4) + @js(datatables,bootstrap4) + + +@append \ No newline at end of file diff --git a/resources/views/theme/backend/adminlte/a/supplier/widgets/products.blade.php b/resources/views/theme/backend/adminlte/a/supplier/widgets/products.blade.php new file mode 100644 index 0000000..146c6bc --- /dev/null +++ b/resources/views/theme/backend/adminlte/a/supplier/widgets/products.blade.php @@ -0,0 +1,88 @@ + +
+
+ +
+ +
+
+ @foreach($o->offeringTypes() as $key => $offering) +
+ + + + + + + + + + + + + + + + + + + + + + + @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) + + + + + + + + + + + + + + + @endforeach + @endforeach + + + + + + + + + + + +
ProductServices
IDProduct IDProduct NameActiveDefault BillingSetup CostBase CostSetup ChargeBase ChargeSoldActive
{{ $po->lid }}{{ $po->name_short }}{{ $po->name }}{{ $po->active ? 'YES' : 'NO' }}{{ $po->billing_interval_string }}{{ number_format($po->setup_cost_taxable,2) }}{{ number_format($po->base_cost_taxable,2) }}{{ number_format($po->setup_charge_taxable,2) }}{{ number_format($po->base_charge_taxable,2) }}{{ number_format($po->services->count()) }}{{ number_format($po->services->where('active')->count()) }}
TOTALS{{ $xx->where('active',TRUE)->count() }} {{ number_format(($xxx=$xx->pluck('types')->flatten()->pluck('product.services')->flatten()->filter())->count()) }}{{ number_format($xxx->where('active')->count()) }}
+
+ @endforeach +
+
+
+ +@section('page-scripts') + @css(datatables,bootstrap4) + @js(datatables,bootstrap4) + + +@append \ No newline at end of file diff --git a/resources/views/theme/backend/adminlte/a/supplierdetails.blade.php b/resources/views/theme/backend/adminlte/a/supplierdetails.blade.php deleted file mode 100644 index 0a64a95..0000000 --- a/resources/views/theme/backend/adminlte/a/supplierdetails.blade.php +++ /dev/null @@ -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') -
-
- @include('adminlte::widget.status') -
-
- -
-
-
-
- -
- -
-
-
-

Supplier Details

-
- @if(session()->has('success')) - {{ session()->get('success') }} - @endif - -
- @csrf - -
-
-
- -
-
- - - - @error('name') - {{ $message }} - @else - Supplier Name required. - @enderror - -
-
- - -
-
-
- active) ? 'checked' : '' }}> - -
-
-
-
- -
- -
-
- - - - - @error('address1') - {{ $message }} - @else - Atleast 1 address line required. - @enderror - -
-
-
- -
- -
-
- - - - @error('city') - {{ $message }} - @else - City is required. - @enderror - -
-
-
- -
- -
-
- - - - @error('state') - {{ $message }} - @else - State is required. - @enderror - -
-
- - -
-
- - - - @error('postcode') - {{ $message }} - @else - Postcode is required. - @enderror - -
-
-
-
- -
-
- -
-
- - - - @error('supplier_details.accounts') - {{ $message }} - @enderror - -
-
-
- -
- -
-
- - - - @error('supplier_details.support') - {{ $message }} - @enderror - -
-
-
- -
- -
-
- - - - @error('supplier_details.payments') - {{ $message }} - @enderror - -
-
-
-
-
- -
- -
-
- - - Notes. - - @error('supplier_details.notes') - {{ $message }} - @enderror - -
-
-
- -
- -
- Cancel - @can('wholesaler') - - @endcan -
-
-
-
-
- Products. -
-
-
-
-
-
-@endsection \ No newline at end of file diff --git a/resources/views/theme/backend/adminlte/u/invoice/home.blade.php b/resources/views/theme/backend/adminlte/u/invoice/home.blade.php index 5f75c5a..b358f74 100644 --- a/resources/views/theme/backend/adminlte/u/invoice/home.blade.php +++ b/resources/views/theme/backend/adminlte/u/invoice/home.blade.php @@ -102,7 +102,7 @@ {{ $po->count }} #{{ $po->lid }} - {{ $po->name($o->account->user->language) }} + {{ $po->name }} ${{ number_format($o->items->filter(function($item) use ($po) {return $item->product_id == $po->id; })->sum('total'),$o->currency()->rounding) }} diff --git a/resources/views/theme/backend/adminlte/u/service/home.blade.php b/resources/views/theme/backend/adminlte/u/service/home.blade.php index 1384fe8..06e101e 100644 --- a/resources/views/theme/backend/adminlte/u/service/home.blade.php +++ b/resources/views/theme/backend/adminlte/u/service/home.blade.php @@ -42,7 +42,8 @@ --}} @can('wholesaler') - + + @endcan diff --git a/resources/views/theme/backend/adminlte/u/service/widgets/active.blade.php b/resources/views/theme/backend/adminlte/u/service/widgets/active.blade.php index d648925..154affe 100644 --- a/resources/views/theme/backend/adminlte/u/service/widgets/active.blade.php +++ b/resources/views/theme/backend/adminlte/u/service/widgets/active.blade.php @@ -10,7 +10,7 @@ ID - Category + Type Service Product Next Invoice @@ -21,9 +21,9 @@ @foreach ($o->services as $oo) {{ $oo->sid }} - {{ $oo->product_category }} + {{ $oo->service_type }} {{ $oo->name_short }} - {{ $oo->product_name }} + {{ $oo->product->name }} {{ $oo->external_billing ? '-' : $oo->next_invoice->format('Y-m-d') }} @endforeach diff --git a/resources/views/theme/backend/adminlte/u/service/widgets/broadband/change.blade.php b/resources/views/theme/backend/adminlte/u/service/widgets/broadband/change.blade.php index 9201de6..c00a5a0 100644 --- a/resources/views/theme/backend/adminlte/u/service/widgets/broadband/change.blade.php +++ b/resources/views/theme/backend/adminlte/u/service/widgets/broadband/change.blade.php @@ -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'; diff --git a/resources/views/theme/backend/adminlte/u/service/widgets/broadband/details.blade.php b/resources/views/theme/backend/adminlte/u/service/widgets/broadband/details.blade.php index 6758be0..7b3d60f 100644 --- a/resources/views/theme/backend/adminlte/u/service/widgets/broadband/details.blade.php +++ b/resources/views/theme/backend/adminlte/u/service/widgets/broadband/details.blade.php @@ -1,3 +1,4 @@ +
@if($o->service->isPending())
@@ -44,7 +45,7 @@ @endif Speed - {{ $o->service->product->type->product->speed }} Mbps + {{ $o->supplied()->speed() }} Mbps diff --git a/resources/views/theme/frontend/metronic/order/home.blade.php b/resources/views/theme/frontend/metronic/order/home.blade.php index ca960fc..49b0e72 100644 --- a/resources/views/theme/frontend/metronic/order/home.blade.php +++ b/resources/views/theme/frontend/metronic/order/home.blade.php @@ -9,7 +9,7 @@

Order Service

- @if ($errors->count()))

There were errors with your order, please try again.

@endif + @if ($errors->count())

There were errors with your order, please try again.

@endif
diff --git a/resources/views/theme/frontend/metronic/order/widget/info.blade.php b/resources/views/theme/frontend/metronic/order/widget/info.blade.php index 13f8da0..2f6027b 100644 --- a/resources/views/theme/frontend/metronic/order/widget/info.blade.php +++ b/resources/views/theme/frontend/metronic/order/widget/info.blade.php @@ -1,8 +1,9 @@ -@if(View::exists('order.widget.info.'.$o->category)) + +@if(View::exists('order.widget.info.'.strtolower($o->product_type)))
{{-- Return Category Requirements --}} - @include('order.widget.info.'.$o->category) + @include('order.widget.info.'.strtolower($o->product_type)) {{-- Return Supplier Requirements --}} {{-- Return Product Requirements --}} diff --git a/resources/views/theme/frontend/metronic/order/widget/info/VOIP.blade.php b/resources/views/theme/frontend/metronic/order/widget/info/VOIP.blade.php deleted file mode 100644 index 8598a8e..0000000 --- a/resources/views/theme/frontend/metronic/order/widget/info/VOIP.blade.php +++ /dev/null @@ -1,30 +0,0 @@ -
-

{!! $o->description !!}

-
- - - - - - - - - - - - - - - - - - - - - - - - - - -
TypeVOIP
Setup Charges${{ is_numeric($o->setup_cost) ? number_format($o->setup_cost,2) : $o->setup_cost }}
Cost${{ is_numeric($o->default_cost) ? number_format($o->default_cost,2) : $o->default_cost }}
Default Billing{{ is_numeric($o->default_billing) ? number_format($o->default_billing,2) : $o->default_billing }}
Contract Term{{ $o->contract_term }} mths
Minimum Costs${{ is_numeric($o->minimum_cost) ? number_format($o->minimum_cost,2) : $o->minimum_cost }}
\ No newline at end of file diff --git a/resources/views/theme/frontend/metronic/order/widget/info/ADSL.blade.php b/resources/views/theme/frontend/metronic/order/widget/info/broadband.blade.php similarity index 57% rename from resources/views/theme/frontend/metronic/order/widget/info/ADSL.blade.php rename to resources/views/theme/frontend/metronic/order/widget/info/broadband.blade.php index 8855275..914b46c 100644 --- a/resources/views/theme/frontend/metronic/order/widget/info/ADSL.blade.php +++ b/resources/views/theme/frontend/metronic/order/widget/info/broadband.blade.php @@ -1,3 +1,4 @@ +

{!! $o->description !!}

@@ -9,15 +10,15 @@ Setup Charges * - ${{ is_numeric($o->setup_cost) ? number_format($o->setup_cost,2) : $o->setup_cost }} + ${{ number_format($o->setup_charge_taxable,2) }} Cost + - ${{ is_numeric($o->default_cost) ? number_format($o->default_cost,2) : $o->default_cost }} + ${{ number_format($o->base_charge_taxable,2) }} Default Billing - {{ is_numeric($o->default_billing) ? number_format($o->default_billing,2) : $o->default_billing }} + {{ $o->billing_interval_string }} Contract Term @@ -25,7 +26,7 @@ Minimum Costs +* - ${{ is_numeric($o->minimum_cost) ? number_format($o->minimum_cost,2) : $o->minimum_cost }} + ${{ number_format($o->min_charge_taxable,2) }} diff --git a/resources/views/theme/frontend/metronic/order/widget/info/voip.blade.php b/resources/views/theme/frontend/metronic/order/widget/info/voip.blade.php new file mode 100644 index 0000000..d2c4f01 --- /dev/null +++ b/resources/views/theme/frontend/metronic/order/widget/info/voip.blade.php @@ -0,0 +1,31 @@ + +
+

{!! $o->description !!}

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Type{{ $o->product_type }}
Setup Charges${{ number_format($o->setup_charge_taxable,2) }}
Cost${{ number_format($o->base_charge_taxable,2) }}
Default Billing{{ $o->billing_interval_string }}
Contract Term{{ $o->contract_term }} mths
Minimum Costs${{ number_format($o->min_charge_taxable,2) }}
\ No newline at end of file diff --git a/resources/views/theme/frontend/metronic/order/widget/order.blade.php b/resources/views/theme/frontend/metronic/order/widget/order.blade.php index f28de81..e8661c9 100644 --- a/resources/views/theme/frontend/metronic/order/widget/order.blade.php +++ b/resources/views/theme/frontend/metronic/order/widget/order.blade.php @@ -1,4 +1,5 @@ -@if(View::exists('order.widget.order.'.$o->category)) + +@if(View::exists('order.widget.order.'.strtolower($o->product_type)))

Order Configuration

@@ -6,7 +7,7 @@
{{-- Return Category Requirements --}} - @include('order.widget.order.'.$o->category) + @include('order.widget.order.'.strtolower($o->product_type)) {{-- Return Supplier Requirements --}} {{-- Return Product Requirements --}} diff --git a/resources/views/theme/frontend/metronic/order/widget/order/ADSL.blade.php b/resources/views/theme/frontend/metronic/order/widget/order/broadband.blade.php similarity index 100% rename from resources/views/theme/frontend/metronic/order/widget/order/ADSL.blade.php rename to resources/views/theme/frontend/metronic/order/widget/order/broadband.blade.php diff --git a/resources/views/theme/frontend/metronic/order/widget/order/VOIP.blade.php b/resources/views/theme/frontend/metronic/order/widget/order/voip.blade.php similarity index 100% rename from resources/views/theme/frontend/metronic/order/widget/order/VOIP.blade.php rename to resources/views/theme/frontend/metronic/order/widget/order/voip.blade.php