Add supplier linking

This commit is contained in:
Deon George 2022-08-07 12:17:20 +10:00
parent a1fd36aa6f
commit 2722c92bcf
10 changed files with 339 additions and 11 deletions

View File

@ -0,0 +1,67 @@
<?php
namespace App\Console\Commands;
use Carbon\Carbon;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Config;
use App\Models\{Site,Supplier,User};
class SupplierAccountSync extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'supplier:account:sync'
.' {siteid : Site ID}'
.' {supplier : Supplier Name}'
.' {--f|forceprod : Force Prod API on dev environment}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Sync accounts with a supplier';
/**
* Execute the console command.
*
* @return int
*/
public function handle()
{
Config::set('site',Site::findOrFail($this->argument('siteid')));
$so = Supplier::where('name',$this->argument('supplier'))->singleOrFail();
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));
}
}
}
}

View File

@ -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,7 +39,18 @@ 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

View File

@ -0,0 +1,55 @@
<?php
namespace App\Http\Controllers;
use Carbon\Carbon;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Session;
use App\Models\{Supplier,User};
class UserController extends Controller
{
/**
* Add a supplier to a user's profile
*
* @param Request $request
* @param User $o
* @return \Illuminate\Http\RedirectResponse
*/
public function supplier_addedit(Request $request,User $o)
{
Session::put('supplier_update',true);
$validated = $request->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');
}
}

View File

@ -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.
*

View File

@ -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.'%');

View File

@ -0,0 +1,46 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('supplier_details', function (Blueprint $table) {
$table->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');
}
};

View File

@ -24,7 +24,7 @@
<div class="col-12">
<div class="card-header bg-white">
<ul class="nav nav-pills">
<li class="nav-item"><a class="nav-link active" href="#tab-services" data-toggle="tab">Services</a></li>
<li class="nav-item"><a class="nav-link {{ (! session()->has('supplier_update')) ? 'active' : '' }}" href="#tab-services" data-toggle="tab">Services</a></li>
{{--
<!-- @todo this is not working -->
<li class="nav-item"><a class="nav-link" href="#tab-nextinvoice" data-toggle="tab">Next Invoice</a></li>
@ -36,12 +36,15 @@
<li class="nav-item ml-auto"><a class="nav-link" href="#tab-reseller" data-toggle="tab">Reseller</a></li>
@endcanany
@endif
@canany('reseller','wholesaler')
<li class="nav-item ml-auto"><a class="nav-link {{ session()->has('supplier_update') ? 'active' : '' }}" href="#tab-supplier" data-toggle="tab">Supplier</a></li>
@endcanany
</ul>
</div>
<div class="card-body pl-0 pr-0">
<div class="tab-content">
<div class="active tab-pane" id="tab-services">
<div class="tab-pane {{ (! session()->has('supplier_update')) ? 'active' : '' }}" id="tab-services">
<div class="row">
<div class="col-12 col-xl-7">
@include('service.widget.active')
@ -83,6 +86,11 @@
</div>
@endcanany
@endif
@canany('reseller','wholesaler')
<div class="tab-pane {{ session()->pull('supplier_update') ? 'active' : '' }}" id="tab-supplier">
@include('user.widget.supplier')
</div>
@endcanany
</div>
</div>
</div>

View File

@ -128,9 +128,9 @@
</div>
</div>
@if($x=config('services.supplier.'.$o->name.'.api'))
@if($o->api_class())
<hr>
<p class="h6"><i class="fa-fw fas fa-sitemap"></i> <strong>{{ $x }}</strong></p>
<p class="h6"><i class="fa-fw fas fa-sitemap"></i> <strong>{{ $o->api_class() }}</strong></p>
<div class="row">
<!-- API Details -->

View File

@ -0,0 +1,70 @@
<!-- $o=User::class -->
<!-- Suppliers Configuration for this User -->
<div class="row">
<div class="col-6">
<div class="card">
<div class="card-body">
@include('adminlte::widget.success_button')
<table class="table">
<thead>
<tr>
<th>Supplier</th>
<th>ID</th>
<th>Added</th>
</tr>
</thead>
<tbody>
@foreach ($o->suppliers as $so)
<tr>
<td>{{ $so->name }}</td>
<td>{{ $so->pivot->id }}</td>
<td>{{ $so->pivot->created_at }} <a class="float-right" href="{{ url('a/user/supplier/delete',[$o->id,$so->id]) }}"><i class=" fa-fw fas fa-trash"></i></a></td>
</tr>
@endforeach
</tbody>
@if(($x=\App\Models\Supplier::active()->whereNotIn('id',$o->suppliers->pluck('id'))->orderBy('name')->get())->count())
<tfoot>
<tr>
<td colspan="3">
<form class="g-0 needs-validation" method="POST" action="{{ url('a/user/supplier/add',[$o->id]) }}" enctype="multipart/form-data" role="form">
@csrf
<div class="row">
<div class="col-6">
@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'=>'',
])
</div>
<div class="col-4">
@include('adminlte::widget.form_text',[
'label'=>'ID',
'icon'=>'fas fa-hashtag',
'id'=>'id',
'old'=>'id',
'name'=>'id',
'value'=>'',
])
</div>
<div class="col-2">
<div class="form-group">
<button type="submit" class="mt-4 float-right btn btn-sm btn-success">Add</button>
</div>
</div>
</div>
</form>
</td>
</tr>
</tfoot>
@endif
</table>
</div>
</div>
</div>
</div>

View File

@ -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');