diff --git a/app/Http/Controllers/OrderController.php b/app/Http/Controllers/OrderController.php index 7ff8946..09e899b 100644 --- a/app/Http/Controllers/OrderController.php +++ b/app/Http/Controllers/OrderController.php @@ -3,9 +3,13 @@ namespace App\Http\Controllers; use Illuminate\Http\Request; - -use App\Models\Product; use Igaster\LaravelTheme\Facades\Theme; +use Illuminate\Support\Facades\Auth; +use Illuminate\Support\Facades\Validator; +use Illuminate\Database\Eloquent\Model; + +use App\Models\{Product,Service}; +use App\User; class OrderController extends Controller { @@ -30,5 +34,50 @@ class OrderController extends Controller public function submit(Request $request) { + Validator::make($request->all(),[ + 'product_id'=>'required|exists:ab_product,id', + ]) + ->sometimes('order_email','required|email',function($input) use ($request) { + return ($input->order_email AND ! $input->order_email_manual) OR (! $input->order_email_manual); + }) + ->sometimes('order_email_manual','required|email',function($input) use ($request) { + return $input->order_email_manual AND ! $input->order_email; + })->validate(); + + // Check the plugin details. + $po = Product::findOrFail($request->post('product_id')); + + // Check we have the custom attributes for the product + $options = $po->orderValidation($request); + + $uo = User::where('email','=',$request->post('order_email') ?: $request->post('order_email_manual'))->firstOrFail(); + + $ao = $request->input('account_id') + ? $uo->accounts->where('account_id',$request->input('account_id')) + : $uo->accounts->first(); + + $so = new Service; + $so->id = Service::NextId(); + + // @todo Make this automatic + $so->site_id = config('SITE_SETUP')->id; + $so->product_id = $request->post('product_id'); + $so->order_status = 'ORDER-SUBMIT'; + $so->orderby_id = Auth::user()->id; + + if ($options->order_info) + { + $so->order_info = $options->order_info; + unset($options->order_info); + } + + $so = $ao->services()->save($so); + + if ($options instanceOf Model) { + $options->service_id = $so->id; + $options->save(); + } + + return view('order_received',['o'=>$so]); } } \ No newline at end of file diff --git a/app/Http/Controllers/ResellerServicesController.php b/app/Http/Controllers/ResellerServicesController.php index 006eba2..0c3e587 100644 --- a/app/Http/Controllers/ResellerServicesController.php +++ b/app/Http/Controllers/ResellerServicesController.php @@ -20,4 +20,9 @@ class ResellerServicesController extends Controller { return ['data'=>Auth::user()->all_clients()->values()]; } + + public function service_movements() + { + return ['data'=>Auth::user()->all_client_service_movements()->values()]; + } } \ No newline at end of file diff --git a/app/Models/AdslPlan.php b/app/Models/AdslPlan.php new file mode 100644 index 0000000..ce61022 --- /dev/null +++ b/app/Models/AdslPlan.php @@ -0,0 +1,30 @@ +[ + 'request'=>'options.address', + 'key'=>'service_address', + 'validation'=>'required|string:10', + 'validation_message'=>'Address is a required field.', + ], + ]; + + protected $order_model = ServiceAdsl::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/AdslSupplierPlan.php b/app/Models/AdslSupplierPlan.php new file mode 100644 index 0000000..9114f99 --- /dev/null +++ b/app/Models/AdslSupplierPlan.php @@ -0,0 +1,15 @@ +speed; + } +} \ No newline at end of file diff --git a/app/Models/PlanVoip.php b/app/Models/PlanVoip.php new file mode 100644 index 0000000..31cb7eb --- /dev/null +++ b/app/Models/PlanVoip.php @@ -0,0 +1,35 @@ +[ + 'request'=>'options.phonenumber', + 'key'=>'service_number', + 'validation'=>'required|min:10', + 'validation_message'=>'Phone Number is a required field.', + ], + 'options.supplier'=>[ + 'request'=>'options.supplier', + 'key'=>'order_info.supplier', + 'validation'=>'required|min:4', + 'validation_message'=>'Phone Supplier is a required field.', + ], + 'options.supplieraccnum'=>[ + 'request'=>'options.supplieraccnum', + 'key'=>'order_info.supplieraccnum', + 'validation'=>'required|min:4', + 'validation_message'=>'Phone Supplier Account Number is a required field.', + ], + ]; + + protected $order_model = ServiceVoip::class; +} \ No newline at end of file diff --git a/app/Models/Product.php b/app/Models/Product.php index 7fa68fc..97b0778 100644 --- a/app/Models/Product.php +++ b/app/Models/Product.php @@ -4,6 +4,7 @@ namespace App\Models; use Illuminate\Database\Eloquent\Model; use Illuminate\Support\Facades\Auth; +use Illuminate\Http\Request; class Product extends Model { @@ -25,22 +26,93 @@ class Product extends Model return $this->prod_plugin_file; } + 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 array_get($this->PricePeriods(),$this->price_recurr_default); + } + + public function getDefaultCostAttribute() + { + // @todo Integrate this into a Tax::class + return array_get($this->price_array,sprintf('%s.1.price_base',$this->price_recurr_default))*1.1; + } + + private function getDefaultLanguage() + { + return config('SITE_SETUP')->language; + } + public function getDescriptionAttribute() { // @todo If the user has selected a specific language. return $this->description($this->getDefaultLanguage()); } + 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 * array_get($table,$this->price_recurr_default) * $this->contract_term); + } + public function getNameAttribute() { return $this->name(Auth::user()->language); } + public function getProductTypeAttribute() + { + return $this->plugin()->product->name; + } + + public function getPriceArrayAttribute() + { + return unserialize($this->price_group); + } + + 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 array_get($this->price_array,sprintf('%s.1.price_setup',$this->price_recurr_default))*1.1; + } + public function scopeActive() { return $this->where('active',TRUE); @@ -54,9 +126,34 @@ class Product extends Model return $this->descriptions->where('language_id',$lo->id)->first()->description_short; } - private function getDefaultLanguage() + public function orderValidation(Request $request) { - return config('SITE_SETUP')->language; + 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; + } + } + + 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'), + ]; } /** diff --git a/app/Models/Service.php b/app/Models/Service.php index aa32ce3..60622e4 100644 --- a/app/Models/Service.php +++ b/app/Models/Service.php @@ -3,14 +3,25 @@ namespace App\Models; use Illuminate\Database\Eloquent\Model; +use App\Traits\NextKey; class Service extends Model { + use NextKey; + protected $table = 'ab_service'; protected $with = ['product.descriptions','account.language','service_adsl','service_domain.tld','service_ssl','service_voip']; protected $dates = ['date_last_invoice','date_next_invoice']; + protected $casts = [ + 'order_info'=>'array', + ]; + public $incrementing = FALSE; + + const CREATED_AT = 'date_orig'; + const UPDATED_AT = 'date_last'; protected $appends = [ + 'account_name', 'category', 'name', 'next_invoice', @@ -19,7 +30,9 @@ class Service extends Model 'service_id_url', 'status', ]; + protected $visible = [ + 'account_name', 'active', 'category', 'data_orig', @@ -32,6 +45,10 @@ class Service extends Model 'status', ]; + private $inactive_status = [ + 'CANCELLED', + ]; + public function account() { return $this->belongsTo(Account::class); @@ -70,9 +87,16 @@ class Service extends Model /** * Only query active categories */ - public function scopeActive() + public function scopeActive($query) { - return $this->where('active',TRUE); + return $query->where(function () use ($query) { + return $query->where('active',TRUE)->orWhereNotIn('order_status',$this->inactive_status); + }); + } + + public function getAccountNameAttribute() + { + return $this->account->company; } public function getCategoryAttribute() @@ -82,10 +106,10 @@ class Service extends Model public function getNameAttribute() { - if (! isset($this->getServiceDetail()->name)) + if (! isset($this->ServicePlugin()->name)) return 'Unknown'; - return $this->getServiceDetail()->name; + return $this->ServicePlugin()->name; } public function getNextInvoiceAttribute() @@ -98,28 +122,6 @@ class Service extends Model return $this->product->name($this->account->language); } - /** - * This function will return the associated service model for the product type - */ - public function getServiceDetail() - { - switch ($this->product->prod_plugin_file) - { - case 'ADSL': return $this->service_adsl; - case 'DOMAIN': return $this->service_domain; - case 'HOST': return $this->service_host; - case 'SSL': return $this->service_ssl; - case 'VOIP': return $this->service_voip; - default: return NULL; - } - - } - - public function getStatusAttribute() - { - return $this->active ? 'Active' : 'Inactive'; - } - public function getServiceExpireAttribute() { return 'TBA'; @@ -139,4 +141,41 @@ class Service extends Model { return sprintf('%02s.%04s.%04s',$this->site_id,$this->account_id,$this->id); } + + public function getStatusAttribute() + { + return $this->order_status ? $this->order_status : ( $this->active ? 'Active' : 'Inactive'); + } + + public function setDateOrigAttribute($value) + { + $this->attributes['date_orig'] = $value->timestamp; + } + + public function setDateLastAttribute($value) + { + $this->attributes['date_last'] = $value->timestamp; + } + + public function isActive() + { + return $this->active OR ($this->order_status AND ! in_array($this->order_status,$this->inactive_status)); + } + + /** + * This function will return the associated service model for the product type + */ + private function ServicePlugin() + { + switch ($this->product->prod_plugin_file) + { + case 'ADSL': return $this->service_adsl; + case 'DOMAIN': return $this->service_domain; + case 'HOST': return $this->service_host; + case 'SSL': return $this->service_ssl; + case 'VOIP': return $this->service_voip; + + default: return NULL; + } + } } \ No newline at end of file diff --git a/app/Models/ServiceAdsl.php b/app/Models/ServiceAdsl.php index e8fd01d..3799878 100644 --- a/app/Models/ServiceAdsl.php +++ b/app/Models/ServiceAdsl.php @@ -3,13 +3,17 @@ namespace App\Models; use App\Models\Service_Model as Model; +use App\Traits\NextKey; class ServiceAdsl extends Model { + use NextKey; + protected $table = 'ab_service__adsl'; + public $timestamps = FALSE; public function getNameAttribute() { - return $this->service_number; + return $this->service_number ?: $this->service_address; } } \ No newline at end of file diff --git a/app/Models/ServiceDomain.php b/app/Models/ServiceDomain.php index ad1bdf3..8d5d012 100644 --- a/app/Models/ServiceDomain.php +++ b/app/Models/ServiceDomain.php @@ -3,10 +3,14 @@ namespace App\Models; use App\Models\Service_Model as Model; +use App\Traits\NextKey; class ServiceDomain extends Model { + use NextKey; + protected $table = 'ab_service__domain'; + public $timestamps = FALSE; public function tld() { diff --git a/app/Models/ServiceHost.php b/app/Models/ServiceHost.php index 542b8dc..6985b79 100644 --- a/app/Models/ServiceHost.php +++ b/app/Models/ServiceHost.php @@ -3,10 +3,14 @@ namespace App\Models; use App\Models\Service_Model as Model; +use App\Traits\NextKey; class ServiceHost extends Model { + use NextKey; + protected $table = 'ab_service__hosting'; + public $timestamps = FALSE; public function getNameAttribute() { diff --git a/app/Models/ServiceSsl.php b/app/Models/ServiceSsl.php index aff8c5a..291682c 100644 --- a/app/Models/ServiceSsl.php +++ b/app/Models/ServiceSsl.php @@ -3,10 +3,13 @@ namespace App\Models; use App\Models\Service_Model as Model; +use App\Traits\NextKey; use App\Classes\SSL; class ServiceSsl extends Model { + use NextKey; + protected $table = 'ab_service__ssl'; protected $_o = NULL; diff --git a/app/Models/ServiceVoip.php b/app/Models/ServiceVoip.php index 99536f6..24beed5 100644 --- a/app/Models/ServiceVoip.php +++ b/app/Models/ServiceVoip.php @@ -3,10 +3,13 @@ namespace App\Models; use App\Models\Service_Model as Model; +use App\Traits\NextKey; class ServiceVoip extends Model { + use NextKey; protected $table = 'ab_service__voip'; + public $timestamps = FALSE; public function getNameAttribute() { diff --git a/app/Traits/NextKey.php b/app/Traits/NextKey.php new file mode 100644 index 0000000..fba96de --- /dev/null +++ b/app/Traits/NextKey.php @@ -0,0 +1,24 @@ +id = self::NextId(); + }); + } + + public static function NextId() + { + return (new self)->max('id')+1; + } +} \ No newline at end of file diff --git a/app/Traits/OrderServiceOptions.php b/app/Traits/OrderServiceOptions.php new file mode 100644 index 0000000..08c08a3 --- /dev/null +++ b/app/Traits/OrderServiceOptions.php @@ -0,0 +1,50 @@ +[ + 'request'=>'options.input', + 'key'=>'column', + 'validation'=>'required|string:10', + 'validation_message'=>'It is a required field.', + ], + ]; + + protected $order_model = NULL; + */ + + public function orderValidation(Request $request) + { + if (! isset($this->order_attributes)) + return NULL; + + $request->validate(collect($this->order_attributes)->pluck('validation','request')->toArray()); + + if (! isset($this->order_model)) + return NULL; + + $o = new $this->order_model; + + $x = []; + foreach ($this->order_attributes as $k => $v) + { + $x[$v['key']] = $request->input($k); + } + + $o->forceFill(array_undot($x)); + + // @todo Make this automatic + $o->site_id = config('SITE_SETUP')->id; + + return $o; + } +} \ No newline at end of file diff --git a/app/User.php b/app/User.php index 3fe88b4..37ce9e2 100644 --- a/app/User.php +++ b/app/User.php @@ -9,6 +9,7 @@ use Laravel\Passport\HasApiTokens; use Leenooks\Carbon; use Leenooks\Traits\UserSwitch; use App\Notifications\ResetPasswordNotification; +use App\Models\Service; class User extends Authenticatable { @@ -159,8 +160,9 @@ class User extends Authenticatable public function getServicesActiveAttribute() { - return $this->services - ->where('active',TRUE); + return $this->services->filter(function($item) { + return $item->isActive(); + }); } public function getServicesCountHtmlAttribute() @@ -188,6 +190,11 @@ class User extends Authenticatable return sprintf('%s',$this->id,$this->user_id); } + public function sendPasswordResetNotification($token) + { + $this->notify(new ResetPasswordNotification($token)); + } + /** Scopes **/ public function scopeActive() @@ -195,6 +202,17 @@ class User extends Authenticatable return $this->where('active',TRUE); } + /** + * Determine if the user is an admin of the account with $id + * + * @param $id + * @return bool + */ + public function isAdmin($id) + { + return $id AND $this->isReseller() AND in_array($id,$this->all_accounts()->pluck('id')->toArray()); + } + /** Functions */ public function all_accounts() @@ -208,7 +226,6 @@ class User extends Authenticatable return $result->flatten(); } - public function all_clients($level=0) { $result = collect(); @@ -229,6 +246,16 @@ class User extends Authenticatable return $result->flatten(); } + public function all_client_service_movements() + { + $s = Service::active()->where('order_status','!=','ACTIVE'); + $aa = $this->all_accounts()->pluck('id')->unique()->toArray(); + + return $s->get()->filter(function($item) use ($aa) { + return in_array($item->account_id,$aa); + }); + } + // List all the agents, including agents of agents public function all_agents($level=0) { @@ -250,17 +277,6 @@ class User extends Authenticatable return $result->flatten(); } - /** - * Determine if the user is an admin of the account with $id - * - * @param $id - * @return bool - */ - public function isAdmin($id) - { - return $id AND $this->isReseller() AND in_array($id,$this->all_accounts()->pluck('id')->toArray()); - } - /** * Determine if the logged in user is a reseller or wholesaler * @@ -285,8 +301,4 @@ class User extends Authenticatable elseif (! $this->all_agents()->count() AND ! $this->all_clients()->count()) return 'customer'; } - public function sendPasswordResetNotification($token) - { - $this->notify(new ResetPasswordNotification($token)); - } } \ No newline at end of file diff --git a/composer.lock b/composer.lock index bde8f47..40bfff6 100644 --- a/composer.lock +++ b/composer.lock @@ -2463,11 +2463,11 @@ }, { "name": "leenooks/laravel", - "version": "0.2.0", + "version": "0.2.1", "source": { "type": "git", "url": "https://dev.leenooks.net/leenooks/laravel", - "reference": "98b7b9f6a80274f40c7c02f3281ba78ecfb27603" + "reference": "f8d743296580ccf17260505be0b5baee17ac82c6" }, "require": { "igaster/laravel-theme": "2.0.6", @@ -2503,7 +2503,7 @@ "laravel", "leenooks" ], - "time": "2018-08-07T14:14:48+00:00" + "time": "2018-08-11T05:11:34+00:00" }, { "name": "maximebf/debugbar", diff --git a/database/migrations/2018_07_28_234942_create_site_details.php b/database/migrations/2018_07_28_234942_create_site_details.php index a71db7e..c9a145f 100644 --- a/database/migrations/2018_07_28_234942_create_site_details.php +++ b/database/migrations/2018_07_28_234942_create_site_details.php @@ -30,6 +30,6 @@ class CreateSiteDetails extends Migration */ public function down() { - Schema::dropIfExists('site_details'); + Schema::dropIfExists('site_details'); } } diff --git a/database/migrations/2018_08_10_115648_service_add_status.php b/database/migrations/2018_08_10_115648_service_add_status.php new file mode 100644 index 0000000..9ba37e9 --- /dev/null +++ b/database/migrations/2018_08_10_115648_service_add_status.php @@ -0,0 +1,38 @@ +integer('orderby_id')->unsigned()->nullable(); + $table->string('order_status')->nullable(); + $table->json('order_info')->nullable(); + $table->foreign('orderby_id')->references('id')->on('users'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('ab_service', function (Blueprint $table) { + $table->dropForeign(['orderby_id']); + $table->dropColumn('orderby_id'); + $table->dropColumn('order_status'); + $table->dropColumn('order_info'); + }); + } +} \ No newline at end of file diff --git a/resources/theme/backend/adminlte/r/clients.blade.php b/resources/theme/backend/adminlte/r/clients.blade.php index 205ad22..93267f8 100644 --- a/resources/theme/backend/adminlte/r/clients.blade.php +++ b/resources/theme/backend/adminlte/r/clients.blade.php @@ -72,4 +72,4 @@ }); }); -@append +@append \ No newline at end of file diff --git a/resources/theme/backend/adminlte/r/home.blade.php b/resources/theme/backend/adminlte/r/home.blade.php index df7c882..45493d3 100644 --- a/resources/theme/backend/adminlte/r/home.blade.php +++ b/resources/theme/backend/adminlte/r/home.blade.php @@ -12,13 +12,47 @@ @endsection @section('main-content') -
ID | +Account | +Name | +Status | +Product | +
---|---|---|---|---|
Count {{ $user->all_client_service_movements()->count() }} | ++ |
No Service Movements
+ @endif +