diff --git a/app/Console/Commands/SupplierAccountSync.php b/app/Console/Commands/SupplierAccountSync.php new file mode 100644 index 0000000..9f1f996 --- /dev/null +++ b/app/Console/Commands/SupplierAccountSync.php @@ -0,0 +1,67 @@ +argument('siteid'))); + $so = Supplier::where('name',$this->argument('supplier'))->singleOrFail(); + + foreach ($so->API($this->option('forceprod'))->getCustomers(['fetchall'=>true]) as $customer) { + // Check if we have this customer already (by ID) + if ($so->users->where('pivot.id',$customer->id)->count()) { + $this->info(sprintf('User already linked (%s:%s)',$customer->id,$customer->email)); + + } elseif ($x=User::where('email',$customer->email)->single()) { + //dump($x->suppliers->first()); + if ($x->suppliers->count()) { + $this->alert(sprintf('User [%d:%s] already linked to this supplier with ID (%s)',$customer->id,$customer->email,$x->suppliers->first()->pivot->id)); + + } else { + $this->warn(sprintf('User [%d:%s] has same email (%s:%s) - Linked',$x->id,$x->email,$customer->id,$customer->email)); + + $so->users()->syncWithoutDetaching([ + $x->id => [ + 'id'=>$customer->id, + 'site_id'=>$x->site_id, // @todo See if we can have this handled automatically + 'created_at'=>Carbon::create($customer->date_added), + ] + ]); + } + + } else { + $this->error(sprintf('User doesnt exist with email (%s)',$customer->email)); + } + } + } +} \ No newline at end of file diff --git a/app/Http/Controllers/SearchController.php b/app/Http/Controllers/SearchController.php index 1334568..d0d6773 100644 --- a/app/Http/Controllers/SearchController.php +++ b/app/Http/Controllers/SearchController.php @@ -7,7 +7,7 @@ use Illuminate\Support\Collection; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Gate; -use App\Models\{Account,Invoice,Payment,Service,Service\Broadband,Service\Phone,User}; +use App\Models\{Account,Invoice,Payment,Service,Supplier,User}; class SearchController extends Controller { @@ -39,9 +39,20 @@ class SearchController extends Controller ->orderBy('firstname') ->limit(10)->get() as $o) { - $result->push(['name'=>sprintf('%s (%s)',$o->name,$o->lid),'value'=>'/u/home/'.$o->id,'category'=>'Users']); + $result->push(['name'=>sprintf('%s (%s) - %s',$o->name,$o->lid,$o->email),'value'=>'/u/home/'.$o->id,'category'=>'Users']); } + # Look for User by Supplier + if (is_numeric($request->input('term'))) + foreach (Supplier::Search($request->input('term')) + ->whereIN('user_id',$user_ids) + ->orderBy('name') + ->limit(10)->get() as $o) + { + $oo = $o->users->filter(function($item) use ($request) { return str_contains($item->pivot->id,$request->input('term')); })->pop(); + $result->push(['name'=>sprintf('%s (%s:%s)',$oo->name,$o->name,$oo->pivot->id),'value'=>'/u/home/'.$oo->id,'category'=>'Suppliers']); + } + # Look for Account foreach (Account::Search($request->input('term')) ->whereIN('user_id',$user_ids) diff --git a/app/Http/Controllers/UserController.php b/app/Http/Controllers/UserController.php new file mode 100644 index 0000000..07a8a2e --- /dev/null +++ b/app/Http/Controllers/UserController.php @@ -0,0 +1,55 @@ +validate([ + 'id'=>'required|string', + 'supplier_id'=>'required|exists:suppliers,id', + ]); + + $o->suppliers()->attach([ + $validated['supplier_id'] => [ + 'id'=>$validated['id'], + 'site_id'=>$o->site_id, + 'created_at'=>Carbon::now(), + ] + ]); + + return redirect()->back()->with('success','Supplier Added'); + } + + /** + * Remove a supplier from a user's profile + * + * @param User $o + * @param Supplier $so + * @return \Illuminate\Http\RedirectResponse + */ + public function supplier_delete(User $o,Supplier $so) + { + Session::put('supplier_update',true); + + $o->suppliers()->detach([$so->id]); + + return redirect()->back()->with('success','Supplier Deleted'); + } +} \ No newline at end of file diff --git a/app/Models/Supplier.php b/app/Models/Supplier.php index 64d38cc..edb260c 100644 --- a/app/Models/Supplier.php +++ b/app/Models/Supplier.php @@ -8,6 +8,7 @@ use Illuminate\Support\Arr; use Illuminate\Support\Collection; use Leenooks\Traits\ScopeActive; +use App\Models\Scopes\SiteScope; use App\Models\Supplier\{Broadband,Domain,Email,Ethernet,Generic,Host,HSPA,Phone,SSL,Type}; class Supplier extends Model @@ -98,17 +99,72 @@ class Supplier extends Model /* RELATIONS */ - // @todo Need to put in an integrity constraint to support the hasOne() - // @todo Some suppliers have multiple different configuration urls/passwords and contacts for different types of services, perhaps this should be hasMany()? - // EG: Crazy Domains, "domains" and "hosting". + /** + * Supplier Detail Configuration + * + * @return \Illuminate\Database\Eloquent\Relations\HasOne + */ public function detail() { return $this->hasOne(SupplierDetail::class) - ->withoutGlobalScope(\App\Models\Scopes\SiteScope::class); + ->withoutGlobalScope(SiteScope::class); + } + + public function users() + { + return $this->belongsToMany(User::class) + ->withPivot('id','created_at'); + } + + /** + * Search for a record + * + * @param $query + * @param string $term + */ + public function scopeSearch($query,string $term) + { + if (is_numeric($term)) { + $query->select('suppliers.*') + ->join('supplier_user',['supplier_user.supplier_id'=>'suppliers.id']) + ->orWhere('supplier_user.id','like','%'.$term.'%'); + } + + return $query; } /* METHODS */ + public function api_class(): ?string + { + return config('services.supplier.'.strtolower($this->name).'.api'); + } + + private function api_key(): string + { + return Arr::get($this->detail->connections,'api_key'); + } + + private function api_secret(): string + { + return Arr::get($this->detail->connections,'api_secret'); + } + + public function API(bool $forceprod=FALSE): mixed + { + return $this->hasAPIdetails() ? new ($this->api_class())($this->api_key(),$this->api_secret(),$forceprod) : NULL; + } + + /** + * Do we have API details for this supplier + * + * @return bool + */ + public function hasAPIdetails(): bool + { + return $this->api_class() && (collect(['api_key','api_secret'])->diff($this->detail->connections->keys())->count() === 0); + } + /** * Return the traffic records, that were not matched to a service. * diff --git a/app/Models/User.php b/app/Models/User.php index 419e2f1..b356752 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -161,6 +161,13 @@ class User extends Authenticatable implements IDs ->active(); } + public function suppliers() + { + return $this->belongsToMany(Supplier::class) + ->where('supplier_user.site_id',$this->site_id) + ->withPivot('id','created_at'); + } + /* ATTRIBUTES */ /** @@ -231,7 +238,7 @@ class User extends Authenticatable implements IDs }); } elseif (is_numeric($term)) { - $query->where('id','like','%'.$term.'%'); + $query->where('users.id','like','%'.$term.'%'); } elseif (preg_match('/\@/',$term)) { $query->where('email','like','%'.$term.'%'); diff --git a/database/migrations/2022_08_06_140410_supplier_constraint.php b/database/migrations/2022_08_06_140410_supplier_constraint.php new file mode 100644 index 0000000..3005acd --- /dev/null +++ b/database/migrations/2022_08_06_140410_supplier_constraint.php @@ -0,0 +1,46 @@ +foreign(['supplier_id'])->references(['id'])->on('suppliers'); + }); + + Schema::create('supplier_user', function (Blueprint $table) { + $table->integer('site_id')->unsigned(); + $table->integer('supplier_id')->unsigned(); + $table->integer('user_id')->unsigned(); + $table->string('id'); + $table->dateTime('created_at'); + + $table->unique(['supplier_id','user_id']); + $table->foreign(['supplier_id'])->references(['id'])->on('suppliers'); + $table->foreign(['user_id','site_id'])->references(['id','site_id'])->on('users'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('supplier_details', function (Blueprint $table) { + $table->dropForeign(['supplier_id']); + }); + + Schema::dropIfExists('supplier_user'); + } +}; diff --git a/resources/views/theme/backend/adminlte/home.blade.php b/resources/views/theme/backend/adminlte/home.blade.php index 94ddb05..f34486a 100644 --- a/resources/views/theme/backend/adminlte/home.blade.php +++ b/resources/views/theme/backend/adminlte/home.blade.php @@ -24,7 +24,7 @@
-
+
@include('service.widget.active') @@ -83,6 +86,11 @@
@endcanany @endif + @canany('reseller','wholesaler') +
+ @include('user.widget.supplier') +
+ @endcanany
diff --git a/resources/views/theme/backend/adminlte/supplier/widget/detail.blade.php b/resources/views/theme/backend/adminlte/supplier/widget/detail.blade.php index 35b0343..956b4ca 100644 --- a/resources/views/theme/backend/adminlte/supplier/widget/detail.blade.php +++ b/resources/views/theme/backend/adminlte/supplier/widget/detail.blade.php @@ -128,9 +128,9 @@
- @if($x=config('services.supplier.'.$o->name.'.api')) + @if($o->api_class())
-

{{ $x }}

+

{{ $o->api_class() }}

diff --git a/resources/views/theme/backend/adminlte/user/widget/supplier.blade.php b/resources/views/theme/backend/adminlte/user/widget/supplier.blade.php new file mode 100644 index 0000000..6bddb5b --- /dev/null +++ b/resources/views/theme/backend/adminlte/user/widget/supplier.blade.php @@ -0,0 +1,70 @@ + + +
+
+
+
+ @include('adminlte::widget.success_button') + + + + + + + + + + + @foreach ($o->suppliers as $so) + + + + + + @endforeach + + + @if(($x=\App\Models\Supplier::active()->whereNotIn('id',$o->suppliers->pluck('id'))->orderBy('name')->get())->count()) + + + + + + @endif +
SupplierIDAdded
{{ $so->name }}{{ $so->pivot->id }}{{ $so->pivot->created_at }}
+
+ @csrf + +
+
+ @include('adminlte::widget.form_select',[ + 'label'=>'Add Supplier', + 'icon'=>'fas fa-handshake', + 'id'=>'supplier_id', + 'old'=>'supplier_id', + 'options'=>$x->transform(function($item) { return ['id'=>$item->id,'value'=>$item->name]; }), + 'value'=>'', + ]) +
+
+ @include('adminlte::widget.form_text',[ + 'label'=>'ID', + 'icon'=>'fas fa-hashtag', + 'id'=>'id', + 'old'=>'id', + 'name'=>'id', + 'value'=>'', + ]) +
+
+
+ +
+
+
+
+
+
+
+
+
\ No newline at end of file diff --git a/routes/web.php b/routes/web.php index 9c0c472..a23ff20 100644 --- a/routes/web.php +++ b/routes/web.php @@ -14,6 +14,7 @@ use App\Http\Controllers\{AdminController, SearchController, ServiceController, SupplierController, + UserController, WelcomeController, Wholesale\ReportController}; use App\Models\Supplier; @@ -109,6 +110,13 @@ Route::group(['middleware'=>['theme:adminlte-be','auth','role:wholesaler'],'pref Route::post('service/update/{o}',[ServiceController::class,'update']) ->where('o','[0-9]+'); + // Linking supplier to user + Route::post('user/supplier/add/{o}',[UserController::class,'supplier_addedit']) + ->where('o','[0-9]+'); + Route::get('user/supplier/delete/{o}/{so}',[UserController::class,'supplier_delete']) + ->where('o','[0-9]+') + ->where('so','[0-9]+'); + //@deprecated // Route::get('service/{o}','AdminHomeController@service'); // Route::post('service/{o}','AdminHomeController@service_update');