diff --git a/app/Http/Controllers/AdminController.php b/app/Http/Controllers/AdminController.php index f145d30..c2387c7 100644 --- a/app/Http/Controllers/AdminController.php +++ b/app/Http/Controllers/AdminController.php @@ -30,58 +30,6 @@ class AdminController extends Controller ->with('o',$o); } - // @todo Move to reseller - public function charge_addedit(Request $request,Charge $o) - { - if ($request->post()) { - $request->validate([ - 'account_id' => 'required|exists:accounts,id', - 'charge_at' => 'required|date', - 'service_id' => 'required|exists:services,id', - 'quantity' => 'required|numeric|not_in:0', - 'amount' => 'required|numeric|min:0.01', - 'sweep_type' => 'required|numeric|in:'.implode(',',array_keys(Charge::sweep)), - 'type' => 'required|numeric|in:'.implode(',',array_keys(InvoiceItem::type)), - 'taxable' => 'nullable|boolean', - 'description' => 'nullable|string|max:128', - ]); - - if (! $o->exists) { - $o->site_id = config('site')->site_id; - $o->user_id = Auth::id(); - $o->active = TRUE; - } - - $o->forceFill($request->only(['account_id','charge_at','service_id','quantity','amount','sweep_type','type','taxable','description'])); - $o->save(); - - return redirect() - ->back() - ->with('success','Charge recorded: '.$o->id); - } - - return view('theme.backend.adminlte.a.charge.addedit') - ->with('o',$o); - } - - // @todo Move to reseller - public function charge_pending_account(Request $request,Account $o) - { - return view('theme.backend.adminlte.a.charge.widgets.pending') - ->with('list',$o->charges->where('active',TRUE)->where('processed',NULL)->except($request->exclude)); - } - - /** - * List unprocessed charges - * - * @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View - */ - // @todo Move to reseller - public function charge_unprocessed() - { - return view('theme.backend.adminlte.a.charge.unprocessed'); - } - /** * Record payments on an account. * diff --git a/app/Http/Controllers/ServiceController.php b/app/Http/Controllers/ServiceController.php index 69ed5c5..0dc5260 100644 --- a/app/Http/Controllers/ServiceController.php +++ b/app/Http/Controllers/ServiceController.php @@ -8,6 +8,7 @@ use Illuminate\Http\Request; use Illuminate\Support\Arr; use Illuminate\Support\Collection; use Illuminate\Support\Facades\Auth; +use Illuminate\Support\Facades\Gate; use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Mail; use Illuminate\Support\Facades\Session; @@ -17,7 +18,7 @@ use Illuminate\Validation\ValidationException; use Illuminate\View\View; use Symfony\Component\HttpKernel\Exception\HttpException; -use App\Http\Requests\ServiceChangeRequest; +use App\Http\Requests\{ChargeAdd,ServiceChangeRequest}; use App\Mail\{CancelRequest,ChangeRequest}; use App\Models\{Charge,Invoice,Product,Service}; @@ -265,6 +266,49 @@ class ServiceController extends Controller } } + /** + * Add a charge to a service/account + * + * @param ChargeAdd $request + * @return RedirectResponse + */ + public function charge_addedit(ChargeAdd $request): RedirectResponse + { + $o = Charge::findOrNew(Arr::get($request->validated(),'id')); + + // Dont update processed charges + if ($o->processed) + abort(403); + + $o->forceFill(array_merge(Arr::except($request->validated(),['id']),['active'=>TRUE])); + $o->save(); + + return redirect() + ->back() + ->with('success',sprintf('Charge %s #%d',$o->wasRecentlyCreated ? 'Created' : 'Updated',$o->id)); + } + + /** + * Add a charge to a service/account + * + * @param Request $request + * @return View + */ + public function charge_edit(Request $request): View + { + $o = Charge::where('processed',FALSE) + ->where('id',$request->id) + ->firstOrFail(); + + if (Gate::allows('update',$o)) { + return view('theme.backend.adminlte.charge.widget.addedit') + ->with('o',$o) + ->with('so',$o->service); + } + + abort(403); + } + /** * List all the domains managed by the user * diff --git a/app/Http/Requests/ChargeAdd.php b/app/Http/Requests/ChargeAdd.php new file mode 100644 index 0000000..19709fe --- /dev/null +++ b/app/Http/Requests/ChargeAdd.php @@ -0,0 +1,46 @@ +accounts_all + ->contains(request()->post('account_id')); + } + + /** + * Get the validation rules that apply to the request. + * + * @return array|string> + */ + public function rules(): array + { + Session::put('charge_add',true); + + return [ + 'id' => 'sometimes|exists:charges,id', + 'account_id' => 'required|exists:accounts,id', + 'charge_at' => 'required|date', + 'service_id' => 'required|exists:services,id', + 'site_id' => 'required|exists:sites,id', + 'quantity' => 'required|numeric|not_in:0', + 'amount' => 'required|numeric|min:0.01', + 'sweep_type' => 'required|numeric|in:'.implode(',',array_keys(Charge::sweep)), + 'type' => 'required|numeric|in:'.implode(',',array_keys(InvoiceItem::type)), + 'taxable' => 'nullable|boolean', + 'description' => 'nullable|string|min:5|max:128', + ]; + } +} \ No newline at end of file diff --git a/app/Models/Charge.php b/app/Models/Charge.php index e3849be..1f47581 100644 --- a/app/Models/Charge.php +++ b/app/Models/Charge.php @@ -4,29 +4,24 @@ namespace App\Models; use Illuminate\Database\Eloquent\Model; use Illuminate\Support\Arr; +use Illuminate\Support\Facades\Log; +use Leenooks\Traits\ScopeActive; use App\Casts\CollectionOrNull; -use App\Traits\SiteID; /** * CLEANUP NOTES: - * + Charge Date should not be null - * + Attributes should be a collection array - * + type should not be null * + It would be useful, given an array of Charges to call a function that renders them into invoice format. This may provide consistence and be the single view of how charges do look on an invoice. */ class Charge extends Model { - use SiteID; + use ScopeActive; protected $casts = [ 'attributes' => CollectionOrNull::class, - ]; - - protected $dates = [ - 'start_at', - 'stop_at', - 'charge_at', // The date the charge applies - since it can be different to created_at + 'start_at' => 'datetime:Y-m-d', + 'stop_at' => 'datetime:Y-m-d', + 'charge_at' => 'datetime:Y-m-d', // The date the charge applies - since it can be different to created_at ]; public const sweep = [ @@ -61,6 +56,7 @@ class Charge extends Model /** @deprecated use pending */ public function scopeUnprocessed($query) { + Log::alert('UMO:! Deprecated function scopeUnprocessed()'); return $this->scopePending(); } @@ -76,7 +72,21 @@ class Charge extends Model public function getNameAttribute() { - return sprintf('%s %s',$this->description,$this->getAttribute('attributes') ? join('|',unserialize($this->getAttribute('attributes'))) : ''); + return sprintf('%s %s', + $this->description, + $this->getAttribute('attributes') + ? join('|',unserialize($this->getAttribute('attributes'))) + : ''); + } + + public function getSubTotalAttribute(): float + { + return $this->quantity*$this->amount; + } + + public function getTotalAttribute(): float + { + return $this->account->taxed($this->getSubTotalAttribute()); } public function getTypeNameAttribute(): string diff --git a/app/Models/Policies/ChargePolicy.php b/app/Models/Policies/ChargePolicy.php new file mode 100644 index 0000000..b858553 --- /dev/null +++ b/app/Models/Policies/ChargePolicy.php @@ -0,0 +1,41 @@ +isReseller() + && $user + ->accounts_all + ->contains($account->id); + } + + /** + * Determine whether the user can delete the model. + */ + public function delete(User $user,Charge $charge): bool + { + return $user->isReseller() + && $user + ->accounts_all + ->contains($charge->account_id); + } + + /** + * Determine whether the user can update the model. + */ + public function update(User $user,Charge $charge): bool + { + return $user->isReseller() + && $user + ->accounts_all + ->contains($charge->account_id); + } +} \ No newline at end of file diff --git a/composer.lock b/composer.lock index 4c28a9a..49e6114 100644 --- a/composer.lock +++ b/composer.lock @@ -975,16 +975,16 @@ }, { "name": "guzzlehttp/guzzle", - "version": "7.9.1", + "version": "7.9.2", "source": { "type": "git", "url": "https://github.com/guzzle/guzzle.git", - "reference": "a629e5b69db96eb4939c1b34114130077dd4c6fc" + "reference": "d281ed313b989f213357e3be1a179f02196ac99b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/guzzle/zipball/a629e5b69db96eb4939c1b34114130077dd4c6fc", - "reference": "a629e5b69db96eb4939c1b34114130077dd4c6fc", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/d281ed313b989f213357e3be1a179f02196ac99b", + "reference": "d281ed313b989f213357e3be1a179f02196ac99b", "shasum": "" }, "require": { @@ -1081,7 +1081,7 @@ ], "support": { "issues": "https://github.com/guzzle/guzzle/issues", - "source": "https://github.com/guzzle/guzzle/tree/7.9.1" + "source": "https://github.com/guzzle/guzzle/tree/7.9.2" }, "funding": [ { @@ -1097,7 +1097,7 @@ "type": "tidelift" } ], - "time": "2024-07-19T16:19:57+00:00" + "time": "2024-07-24T11:22:20+00:00" }, { "name": "guzzlehttp/promises", @@ -2288,16 +2288,16 @@ }, { "name": "league/commonmark", - "version": "2.5.0", + "version": "2.5.1", "source": { "type": "git", "url": "https://github.com/thephpleague/commonmark.git", - "reference": "0026475f5c9a104410ae824cb5a4d63fa3bdb1df" + "reference": "ac815920de0eff6de947eac0a6a94e5ed0fb147c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/0026475f5c9a104410ae824cb5a4d63fa3bdb1df", - "reference": "0026475f5c9a104410ae824cb5a4d63fa3bdb1df", + "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/ac815920de0eff6de947eac0a6a94e5ed0fb147c", + "reference": "ac815920de0eff6de947eac0a6a94e5ed0fb147c", "shasum": "" }, "require": { @@ -2390,7 +2390,7 @@ "type": "tidelift" } ], - "time": "2024-07-22T18:18:14+00:00" + "time": "2024-07-24T12:52:09+00:00" }, { "name": "league/config", @@ -3056,11 +3056,11 @@ }, { "name": "leenooks/laravel", - "version": "11.1.4", + "version": "11.1.5", "source": { "type": "git", "url": "https://gitea.dege.au/laravel/leenooks.git", - "reference": "f393813311b912f77e4a7082498ed7511482b531" + "reference": "4a4cf3c5bf32f50dcdc8fdc6c3ff680d7f15d90d" }, "type": "library", "extra": { @@ -3093,7 +3093,7 @@ "laravel", "leenooks" ], - "time": "2024-07-24T04:08:04+00:00" + "time": "2024-07-25T03:52:29+00:00" }, { "name": "leenooks/passkey", diff --git a/database/migrations/0210-create_charges.php b/database/migrations/0210-create_charges.php index 4d9a146..c99ddf1 100644 --- a/database/migrations/0210-create_charges.php +++ b/database/migrations/0210-create_charges.php @@ -22,9 +22,9 @@ return new class extends Migration $table->boolean('processed')->default(false); $table->integer('sweep_type')->nullable(); - $table->integer('type')->nullable(); - $table->float('amount', 10, 0)->nullable(); - $table->float('quantity', 10, 0)->nullable(); + $table->integer('type'); + $table->float('amount', 10, 0); + $table->float('quantity', 10, 0); $table->boolean('taxable')->default(true); $table->jsonb('attributes')->nullable(); $table->string('description', 128)->nullable(); diff --git a/database/migrations/2024_07_25_123853_charge_type_not_null.php b/database/migrations/2024_07_25_123853_charge_type_not_null.php new file mode 100644 index 0000000..4142bcc --- /dev/null +++ b/database/migrations/2024_07_25_123853_charge_type_not_null.php @@ -0,0 +1,27 @@ +id ? '#'. $o->id : '' }} -@endsection -@section('page_title') - Charge -@endsection - -@section('contentheader_title') - Record Charge -@endsection -@section('contentheader_description') -@endsection - -@section('main-content') -
-
-
-
-

Record Charge {{ $o->id ? '#'. $o->id : '' }}

- @if(session()->has('success')) - {{ session()->get('success') }} - @endif -
- -
-
- @csrf - -
- -
-
- -
-
- -
- - - @error('charge_at') - {{ $message }} - @else - Charge Date is required. - @enderror - -
- Date of Charge -
-
- - -
-
- -
-
- -
- - - @error('quantity') - {{ $message }} - @else - Quantity is required. - @enderror - -
-
-
-
- -
- -
-
- -
-
- -
- - - - @error('account_id') - {{ $message }} - @else - Account is required. - @enderror - -
- Account to add charge to. -
-
- - -
-
- -
-
- -
- - - @error('sweep_type') - {{ $message }} - @else - Sweep Type is required. - @enderror - -
- When to add the charge to an invoice. -
-
- - -
-
- -
- exists ? $o->taxable : 1) ? 'checked' : '' }}> -
-
-
- - -
-
- -
-
- -
- - - @error('amount') - {{ $message }} - @else - Amount is required. - @enderror - -
- Amount (ex Tax). -
-
-
- -
- -
-
- -
-
- -
- - - - - @error('service_id') - {{ $message }} - @else - Service is required. - @enderror - -
- {{-- - - **Service inactive. - --}} -
-
- - -
-
- -
-
- -
- - - @error('type') - {{ $message }} - @else - Type is required. - @enderror - -
- Charge type. -
-
- - -
- -
-
- -
- -
- Total (ex Tax). -
-
- -
- -
-
- -
-
- -
- - - - @error('description') - {{ $message }} - @enderror - -
-
-
-
- -
-
- Cancel - @can('wholesaler') - - @endcan -
-
-
-
-
-
- -
-
-
-
-@endsection - -@section('page-scripts') - @css(select2) - @js(select2,autofocus) - - -@append diff --git a/resources/views/theme/backend/adminlte/a/charge/unprocessed.blade.php b/resources/views/theme/backend/adminlte/charge/pending.blade.php similarity index 79% rename from resources/views/theme/backend/adminlte/a/charge/unprocessed.blade.php rename to resources/views/theme/backend/adminlte/charge/pending.blade.php index 95fd19d..1306b71 100644 --- a/resources/views/theme/backend/adminlte/a/charge/unprocessed.blade.php +++ b/resources/views/theme/backend/adminlte/charge/pending.blade.php @@ -1,3 +1,5 @@ +@use(App\Models\Charge) + @extends('adminlte::layouts.app') @section('htmlheader_title') @@ -32,17 +34,21 @@ - @foreach(\App\Models\Charge::unprocessed()->with(['account.user','service'])->get() as $o) + @forelse(Charge::pending()->with(['account.user','service'])->get() as $o) - {{ $o->id }} + {{ $o->id }} {{ $o->charge_at->format('Y-m-d') }} {{ $o->created_at->format('Y-m-d') }} {{ $o->account->name }} - {{ $o->service->name_short }} + {{ $o->service->name_short }} {{ $o->description }} {{ number_format($o->quantity*$o->amount,2) }} - @endforeach + @empty + + Not charges unprocessed + + @endforelse diff --git a/resources/views/theme/backend/adminlte/charge/widget/addedit.blade.php b/resources/views/theme/backend/adminlte/charge/widget/addedit.blade.php new file mode 100644 index 0000000..cd0b9ee --- /dev/null +++ b/resources/views/theme/backend/adminlte/charge/widget/addedit.blade.php @@ -0,0 +1,107 @@ + +@use(Carbon\Carbon) +@use(App\Models\Charge) +@use(App\Models\InvoiceItem) + +
+ @csrf + + + + @if($id=old('id',isset($o) ? $o->id : FALSE)) + + @endif + +
+
+

Charge @if($id)Update #{{ $id }}@else Add @endif + + @error('id') + + @enderror +

+
+
+
+ +
+
+
+ +
+ +
+
+ +
+ +
+ +
+
+ +
+ +
+ +
+
+
+ +
+
+ +
+ +
+
+ +
+ +
+ +
+ + +
+ +
+
+ +
+ +
+ +
+
+ +
+
+ +
+ +
+ +
+
+ +
+
+ + @if($id)Update @else Add @endif +
+
+
+ +@section('page-scripts') + +@append \ No newline at end of file diff --git a/resources/views/theme/backend/adminlte/service/home.blade.php b/resources/views/theme/backend/adminlte/service/home.blade.php index 771a3d7..c45f6d7 100644 --- a/resources/views/theme/backend/adminlte/service/home.blade.php +++ b/resources/views/theme/backend/adminlte/service/home.blade.php @@ -29,17 +29,20 @@
@@ -59,16 +62,16 @@ @endcan
-
+
@if($x=! ($o->suspend_billing || $o->external_billing)) -
! session()->has('service_update')]) id="pending_items"> +
! (session()->has('service_update') || session()->has('charge_add'))]) id="pending_items"> @include('theme.backend.adminlte.service.widget.invoice')
@endif @if($o->product->hasUsage()) -
! ($x || session()->has('service_update'))]) id="traffic"> +
! ($x || (session()->has('service_update') || session()->has('charge_add')))]) id="traffic"> @if($o->type->usage(30)->count()) @include('theme.backend.adminlte.service.widget.'.$o->product->category.'.usagegraph',['o'=>$o->type]) @endif @@ -88,9 +91,15 @@ @endif
-
session()->pull('service_update')]) id="update"> +
session()->pull('service_update'),'p-2']) id="update"> @include('theme.backend.adminlte.service.widget.update')
+ + @if($o->active || $o->isPending()) +
session()->pull('charge_add')]) id="charge"> + @include('theme.backend.adminlte.service.widget.charge') +
+ @endif @endcan
diff --git a/resources/views/theme/backend/adminlte/service/widget/charge.blade.php b/resources/views/theme/backend/adminlte/service/widget/charge.blade.php new file mode 100644 index 0000000..569f0a1 --- /dev/null +++ b/resources/views/theme/backend/adminlte/service/widget/charge.blade.php @@ -0,0 +1,102 @@ +@use(App\Models\Charge) + +
+
+ +
+ +
+
+
+ @include('theme.backend.adminlte.charge.widget.addedit',['o'=>NULL,'so'=>$o]) +
+ +
+ + + + + + + + + + + + + + + @forelse(Charge::where('service_id',$o->id)->active()->orderBy('charge_at','DESC')->with(['account'])->get() as $oo) + + + + + + + + + + @empty + + + + @endforelse + +
IDCreatedChargeTypeQTotalProcessed
{{ $oo->id }}{{ $oo->created_at->format('Y-m-d') }}{{ $oo->charge_at?->format('Y-m-d') }}{{ $oo->type_name }}{{ number_format($oo->quantity) }}{{ number_format($oo->total,2) }} + {{ $oo->processed ? 'YES' : 'NO' }} + @if(! $oo->processed) + + + + + @endif +
No Charges for this Service
+
+
+
+
+ + + +@section('page-scripts') + +@append \ No newline at end of file diff --git a/resources/views/vendor/adminlte/layouts/partials/footer.blade.php b/resources/views/vendor/adminlte/layouts/partials/footer.blade.php index 600a32e..2ee22c1 100644 --- a/resources/views/vendor/adminlte/layouts/partials/footer.blade.php +++ b/resources/views/vendor/adminlte/layouts/partials/footer.blade.php @@ -6,5 +6,5 @@
- © {{ \Carbon\Carbon::now()->year }} Leenooks. All rights reserved. [#{{ $site->site_id }}] + © {{ \Carbon\Carbon::now()->year }} Leenooks. All rights reserved. @isset($site)[#{{ $site->site_id }}]@endisset \ No newline at end of file diff --git a/resources/views/vendor/adminlte/layouts/partials/sidebarmenu.blade.php b/resources/views/vendor/adminlte/layouts/partials/sidebarmenu.blade.php index 3d6750e..9432209 100644 --- a/resources/views/vendor/adminlte/layouts/partials/sidebarmenu.blade.php +++ b/resources/views/vendor/adminlte/layouts/partials/sidebarmenu.blade.php @@ -1,21 +1,21 @@ -
  • $x=preg_match('#^order#',$path),'menu-closed'=>! $x])> + $path === 'order'])>

    Orders