Optimise charge table, implemented charge recording, optimised payment recording
This commit is contained in:
parent
c0ad46ba65
commit
7c5369203c
@ -4,8 +4,9 @@ namespace App\Http\Controllers;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
use App\Models\{Account,Payment,PaymentItem,Service,SiteDetail};
|
||||
use App\Models\{Account,Charge,InvoiceItem,Payment,PaymentItem,Service,SiteDetail};
|
||||
|
||||
class AdminController extends Controller
|
||||
{
|
||||
@ -14,6 +15,54 @@ class AdminController extends Controller
|
||||
return View('a.service',['o'=>$o]);
|
||||
}
|
||||
|
||||
public function charge_addedit(Request $request,Charge $o)
|
||||
{
|
||||
if ($request->post()) {
|
||||
$request->validate([
|
||||
'account_id' => 'required|exists:accounts,id',
|
||||
'charge_date' => 'required|date',
|
||||
'service_id' => 'required|exists:ab_service,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_date','service_id','quantity','amount','sweep_type','type','taxable','description']));
|
||||
$o->save();
|
||||
|
||||
return redirect()->back()
|
||||
->with('success','Charge recorded: '.$o->id);
|
||||
}
|
||||
|
||||
return view('a.charge.addedit')
|
||||
->with('o',$o);
|
||||
}
|
||||
|
||||
public function charge_pending_account(Request $request,Account $o)
|
||||
{
|
||||
return view('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
|
||||
*/
|
||||
public function charge_unprocessed()
|
||||
{
|
||||
return view('a.charge.unprocessed');
|
||||
}
|
||||
|
||||
/**
|
||||
* Record payments on an account.
|
||||
*
|
||||
@ -64,13 +113,13 @@ class AdminController extends Controller
|
||||
$oo->invoice_id = $id;
|
||||
}
|
||||
|
||||
$oo->alloc_amt = $amount;
|
||||
$oo->alloc_amt = ($oo->invoice->due >= 0) && ($oo->invoice->due-$amount >= 0) ? $amount : 0;
|
||||
$oo->site_id = config('SITE')->site_id;
|
||||
$o->items()->save($oo);
|
||||
}
|
||||
|
||||
return redirect()->back()
|
||||
->with('success','Payment recorded');
|
||||
->with('success','Payment recorded: '.$o->id);
|
||||
}
|
||||
|
||||
return view('a.payment.addedit')
|
||||
|
@ -2,7 +2,10 @@
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Auth;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
use App\Models\Account;
|
||||
|
||||
class ResellerServicesController extends Controller
|
||||
{
|
||||
@ -21,6 +24,14 @@ class ResellerServicesController extends Controller
|
||||
return ['data'=>Auth::user()->all_clients()->values()];
|
||||
}
|
||||
|
||||
public function services(Request $request,Account $o)
|
||||
{
|
||||
return $o->services
|
||||
->filter(function($item) use ($request) {
|
||||
return $item->active || ($item->id == $request->include);
|
||||
});
|
||||
}
|
||||
|
||||
public function service_inactive()
|
||||
{
|
||||
return ['data'=>Auth::user()->all_client_service_inactive()->values()];
|
||||
|
@ -16,6 +16,7 @@ use App\Traits\NextKey;
|
||||
* Attributes for accounts:
|
||||
* + lid: : Local ID for account
|
||||
* + sid: : System ID for account
|
||||
* + name: : Account Name
|
||||
*
|
||||
* @package App\Models
|
||||
*/
|
||||
@ -48,6 +49,11 @@ class Account extends Model implements IDs
|
||||
|
||||
/* RELATIONS */
|
||||
|
||||
public function charges()
|
||||
{
|
||||
return $this->hasMany(Charge::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the country the user belongs to
|
||||
*/
|
||||
|
@ -3,15 +3,67 @@
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Arr;
|
||||
|
||||
/**
|
||||
* CLEANUP NOTES:
|
||||
* + Charge Date should not be null
|
||||
* + Attributes should be a collection array
|
||||
* + type should not be null
|
||||
*/
|
||||
class Charge extends Model
|
||||
{
|
||||
protected $table = 'ab_charge';
|
||||
protected $dates = ['date_charge'];
|
||||
const CREATED_AT = 'date_orig';
|
||||
const UPDATED_AT = 'date_last';
|
||||
|
||||
protected $dates = ['charge_date'];
|
||||
public $dateFormat = 'U';
|
||||
|
||||
public const sweep = [
|
||||
// 0 => 'Daily',
|
||||
// 1 => 'Weekly',
|
||||
// 2 => 'Monthly',
|
||||
// 3 => 'Quarterly',
|
||||
// 4 => 'Semi-Annually',
|
||||
// 5 => 'Annually',
|
||||
6 => 'Service Rebill',
|
||||
];
|
||||
|
||||
/* RELATIONS */
|
||||
|
||||
public function account()
|
||||
{
|
||||
return $this->belongsTo(Account::class);
|
||||
}
|
||||
|
||||
public function service()
|
||||
{
|
||||
return $this->belongsTo(Service::class);
|
||||
}
|
||||
|
||||
/* SCOPES */
|
||||
|
||||
public function scopeUnprocessed($query)
|
||||
{
|
||||
return $query
|
||||
->where('active',TRUE)
|
||||
->whereNotNull('charge_date')
|
||||
->whereNotNull('type')
|
||||
->where(function($q) {
|
||||
return $q->where('processed',FALSE)
|
||||
->orWhereNull('processed');
|
||||
});
|
||||
}
|
||||
|
||||
/* ATTRIBUTES */
|
||||
|
||||
public function getNameAttribute()
|
||||
{
|
||||
return sprintf('%s %s',$this->description,$this->getAttribute('attributes') ? join('|',unserialize($this->getAttribute('attributes'))) : '');
|
||||
}
|
||||
|
||||
public function getTypeAttribute($value)
|
||||
{
|
||||
return Arr::get(InvoiceItem::type,$value);
|
||||
}
|
||||
}
|
@ -37,6 +37,22 @@ class InvoiceItem extends Model
|
||||
// Array of items that can be updated with PushNew
|
||||
protected $pushable = ['taxes'];
|
||||
|
||||
public const type = [
|
||||
1 => 'Hardware', // *
|
||||
2 => 'Service Relocation Fee', // * Must have corresponding SERVICE_ID
|
||||
3 => 'Service Change', // * Must have corresponding SERVICE_ID
|
||||
4 => 'Service Connection', // * Must have corresponding SERVICE_ID
|
||||
6 => 'Service Cancellation', // * Must have corresponding SERVICE_ID
|
||||
7 => 'Extra Product/Service Charge', // * Service Billing in advance, Must have corresponding SERVICE_ID
|
||||
8 => 'Product Addition', // * Additional Product Customisation, Must have corresponding SERVICE_ID
|
||||
120 => 'Credit/Debit Transfer', // * SERVICE_ID is NULL, MODULE_ID is NULL, MODULE_REF is NULL : INVOICE_ID is NOT NULL
|
||||
123 => 'Shipping',
|
||||
124 => 'Late Payment Fee', // * SERVICE_ID is NULL, MODULE_ID = CHECKOUT MODULE,
|
||||
125 => 'Payment Fee', // * SERVICE_ID is NULL, MODULE_ID = CHECKOUT MODULE, MODULE_REF = CHECKOUT NAME
|
||||
126 => 'Other', // * MODEL_ID should be a module
|
||||
127 => 'Rounding', // * SERVICE_ID is NULL, MODULE_ID is NULL, MODULE_REF is NULL
|
||||
];
|
||||
|
||||
/* RELATIONS */
|
||||
|
||||
public function invoice()
|
||||
@ -98,6 +114,7 @@ class InvoiceItem extends Model
|
||||
|
||||
public function getItemTypeNameAttribute()
|
||||
{
|
||||
// @todo use self::type
|
||||
$types = [
|
||||
1=>'Hardware', // *
|
||||
2=>'Service Relocation Fee', // * Must have corresponding SERVICE_ID
|
||||
@ -118,8 +135,11 @@ class InvoiceItem extends Model
|
||||
{
|
||||
// * Line Charge Topic on Invoice.
|
||||
case 0:
|
||||
return sprintf('%s [%s]','Product/Service',
|
||||
$this->date_start == $this->date_stop ? $this->date_start->format('Y-m-d') : sprintf('%s -> %s',$this->date_start->format('Y-m-d'),$this->date_stop->format('Y-m-d')));
|
||||
if ($this->date_start)
|
||||
return sprintf('%s [%s]','Product/Service',
|
||||
(($this->date_start == $this->date_stop) || (! $this->date_stop)) ? $this->date_start->format('Y-m-d') : sprintf('%s -> %s',$this->date_start->format('Y-m-d'),$this->date_stop->format('Y-m-d')));
|
||||
else
|
||||
return 'Product/Service';
|
||||
|
||||
// * Excess Service Item, of item 0, must have corresponding SERVICE_ID
|
||||
case 5:
|
||||
|
@ -3,11 +3,11 @@
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
use App\Interfaces\IDs;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Leenooks\Traits\ScopeActive;
|
||||
use App\Traits\{NextKey,PushNew};
|
||||
|
||||
use App\Interfaces\IDs;
|
||||
use App\Traits\PushNew;
|
||||
|
||||
/**
|
||||
* Class Payment
|
||||
@ -74,7 +74,6 @@ class Payment extends Model implements IDs
|
||||
|
||||
public function scopeUnapplied($query)
|
||||
{
|
||||
//DB::enableQueryLog();
|
||||
return $query
|
||||
->select(['payments.id','payment_date','account_id','checkout_id','total_amt',DB::raw("SUM(alloc_amt) as allocated")])
|
||||
->leftJoin('payment_items',['payment_items.payment_id'=>'payments.id'])
|
||||
|
@ -17,6 +17,11 @@ class PaymentItem extends Model
|
||||
|
||||
/* RELATIONS */
|
||||
|
||||
public function invoice()
|
||||
{
|
||||
return $this->belongsTo(Invoice::class);
|
||||
}
|
||||
|
||||
public function payment() {
|
||||
return $this->belongsTo(Payment::class);
|
||||
}
|
||||
|
49
database/migrations/2021_09_30_132207_rework_charges.php
Normal file
49
database/migrations/2021_09_30_132207_rework_charges.php
Normal file
@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class ReworkCharges extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('ab_charge', function (Blueprint $table) {
|
||||
$table->dropForeign('fk_chg_acc');
|
||||
$table->dropForeign('fk_chg_pdt');
|
||||
$table->dropForeign('fk_chg_svc');
|
||||
$table->dropIndex('fk_chg_acc_idx');
|
||||
$table->dropIndex('fk_chg_svc_idx');
|
||||
$table->dropIndex('fk_chg_pdt_idx');
|
||||
$table->dropPrimary(['id','account_id','site_id']);
|
||||
});
|
||||
|
||||
DB::statement('ALTER TABLE ab_charge RENAME TO charges');
|
||||
DB::statement('ALTER TABLE charges RENAME COLUMN date_charge TO charge_date');
|
||||
DB::statement('ALTER TABLE charges MODIFY COLUMN id INT auto_increment');
|
||||
|
||||
Schema::table('charges', function (Blueprint $table) {
|
||||
$table->unique(['id','account_id','site_id']);
|
||||
$table->foreign(['account_id','site_id'])->references(['id','site_id'])->on('accounts');
|
||||
$table->foreign(['service_id','site_id'])->references(['id','site_id'])->on('ab_service');
|
||||
$table->foreign(['product_id','site_id'])->references(['id','site_id'])->on('ab_product');
|
||||
$table->integer('user_id')->unsigned()->nullable();
|
||||
$table->foreign(['user_id','site_id'])->references(['id','site_id'])->on('users');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
abort(500,'cant go back');
|
||||
}
|
||||
}
|
@ -0,0 +1,345 @@
|
||||
@extends('adminlte::layouts.app')
|
||||
|
||||
@section('htmlheader_title')
|
||||
Charge {{ $o->id ? '#'. $o->id : '' }}
|
||||
@endsection
|
||||
@section('page_title')
|
||||
Charge
|
||||
@endsection
|
||||
|
||||
@section('contentheader_title')
|
||||
Record Charge
|
||||
@endsection
|
||||
@section('contentheader_description')
|
||||
@endsection
|
||||
|
||||
@section('main-content')
|
||||
<div class="row">
|
||||
<div class="col-6">
|
||||
<div class="card card-dark">
|
||||
<div class="card-header">
|
||||
<h1 class="card-title">Record Charge {{ $o->id ? '#'. $o->id : '' }}</h1>
|
||||
@if(session()->has('success'))
|
||||
<span class="ml-3 pt-0 pb-0 pr-1 pl-1 btn btn-outline-success"><small>{{ session()->get('success') }}</small></span>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<div class="card-body">
|
||||
<form class="g-0 needs-validation" method="POST" role="form">
|
||||
@csrf
|
||||
|
||||
<div class="row">
|
||||
<!-- DATE CHARGE -->
|
||||
<div class="col-4">
|
||||
<div class="form-group has-validation">
|
||||
<label for="charge_date">Date Charge</label>
|
||||
<div class="input-group">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text"><i class="fas fa-fw fa-calendar"></i></span>
|
||||
</div>
|
||||
<input type="date" class="form-control @error('charge_date') is-invalid @enderror" id="charge_date" name="charge_date" value="{{ old('charge_date',($o->exists ? $o->charge_date : \Carbon\Carbon::now())->format('Y-m-d')) }}" required>
|
||||
<span class="invalid-feedback" role="alert">
|
||||
@error('charge_date')
|
||||
{{ $message }}
|
||||
@else
|
||||
Charge Date is required.
|
||||
@enderror
|
||||
</span>
|
||||
</div>
|
||||
<span class="input-helper">Date Payment Received.</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- QUANTITY -->
|
||||
<div class="offset-6 col-2">
|
||||
<div class="form-group has-validation">
|
||||
<label class="float-right" for="quantity">Quantity</label>
|
||||
<div class="input-group">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text"><i class="fas fa-fw fa-hashtag"></i></span>
|
||||
</div>
|
||||
<input type="text" class="text-right form-control @error('quantity') is-invalid @enderror" id="quantity" name="quantity" value="{{ old('quantity',$o->exists ? $o->quantity : 1) }}" required>
|
||||
<span class="invalid-feedback" role="alert">
|
||||
@error('quantity')
|
||||
{{ $message }}
|
||||
@else
|
||||
Quantity is required.
|
||||
@enderror
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<!-- ACCOUNTS -->
|
||||
<div class="col-4">
|
||||
<div class="form-group has-validation">
|
||||
<label for="account_id">Account</label>
|
||||
<div class="input-group">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text"><i class="fas fa-fw fa-user"></i></span>
|
||||
</div>
|
||||
<select class="form-control @error('account_id') is-invalid @enderror" id="account_id" name="account_id" required>
|
||||
<option value=""></option>
|
||||
@foreach (\App\Models\Account::active()->with(['user'])->get()->sortBy('name') as $ao)
|
||||
<option value="{{ $ao->id }}" {{ $ao->id == old('account_id',$o->exists ? $o->account_id : NULL) ? 'selected' : '' }}>{{ $ao->name }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
|
||||
<span class="invalid-feedback" role="alert">
|
||||
@error('account_id')
|
||||
{{ $message }}
|
||||
@else
|
||||
Account is required.
|
||||
@enderror
|
||||
</span>
|
||||
</div>
|
||||
<span class="input-helper">Account to add charge to.</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- SWEEP TYPE -->
|
||||
<div class="offset-1 col-4">
|
||||
<div class="form-group has-validation">
|
||||
<label for="sweep_type">Sweep</label>
|
||||
<div class="input-group">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text"><i class="fas fa-fw fa-dollar-sign"></i></span>
|
||||
</div>
|
||||
<select class="form-control @error('sweep_type') is-invalid @enderror" id="sweep_type" name="sweep_type" required>
|
||||
@foreach (\App\Models\Charge::sweep as $k=>$v)
|
||||
<option value="{{ $k }}" {{ $k == old('sweep_type',$o->exists ? $o->sweep_type : NULL) ? 'selected' : '' }}>{{ $v }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
<span class="invalid-feedback" role="alert">
|
||||
@error('sweep_type')
|
||||
{{ $message }}
|
||||
@else
|
||||
Sweep Type is required.
|
||||
@enderror
|
||||
</span>
|
||||
</div>
|
||||
<span class="input-helper">When to add the charge to an invoice.</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- TAXABLE -->
|
||||
<div class="col-1">
|
||||
<div class="form-check has-validation">
|
||||
<label for="taxable">Taxable</label>
|
||||
<div class="form-check text-right">
|
||||
<input type="checkbox" class="form-check-input @error('taxable') is-invalid @enderror" id="taxable" name="taxable" value="1" {{ old('taxable',$o->exists ? $o->taxable : 1) ? 'checked' : '' }}>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- AMOUNT -->
|
||||
<div class="col-2">
|
||||
<div class="form-group has-validation">
|
||||
<label class="float-right" for="amount">Amount</label>
|
||||
<div class="input-group">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text"><i class="fas fa-fw fa-dollar-sign"></i></span>
|
||||
</div>
|
||||
<input type="text" class="text-right form-control @error('amount') is-invalid @enderror" id="amount" name="amount" value="{{ number_format(old('amount',$o->exists ? $o->amount : 0),2) }}">
|
||||
<span class="invalid-feedback" role="alert">
|
||||
@error('amount')
|
||||
{{ $message }}
|
||||
@else
|
||||
Amount is required.
|
||||
@enderror
|
||||
</span>
|
||||
</div>
|
||||
<span class="input-helper">Amount (ex Tax).</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<!-- SERVICES -->
|
||||
<div class="col-4">
|
||||
<div class="form-group has-validation">
|
||||
<label for="service_id">Services</label>
|
||||
<div class="input-group">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text"><i class="fas fa-fw fa-bolt"></i></span>
|
||||
</div>
|
||||
<select class="form-control @error('service_id') is-invalid @enderror" id="service_id" name="service_id" required>
|
||||
</select>
|
||||
<span class="ml-2 pt-2"><i class="fas fa-spinner d-none"></i></span>
|
||||
|
||||
<span class="invalid-feedback" role="alert">
|
||||
@error('service_id')
|
||||
{{ $message }}
|
||||
@else
|
||||
Service is required.
|
||||
@enderror
|
||||
</span>
|
||||
</div>
|
||||
<span class="input-helper"><sup>**</sup>Service inactive.</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- CHARGE TYPE -->
|
||||
<div class="offset-1 col-4">
|
||||
<div class="form-group has-validation">
|
||||
<label for="type">Type</label>
|
||||
<div class="input-group">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text"><i class="fas fa-fw fa-dollar-sign"></i></span>
|
||||
</div>
|
||||
<select class="form-control @error('type') is-invalid @enderror" id="type" name="type" required>
|
||||
@foreach (collect(\App\Models\InvoiceItem::type)->sort() as $k=>$v)
|
||||
<option value="{{ $k }}" {{ $k == old('type',$o->exists ? $o->type : NULL) ? 'selected' : '' }}>{{ $v }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
<span class="invalid-feedback" role="alert">
|
||||
@error('type')
|
||||
{{ $message }}
|
||||
@else
|
||||
Type is required.
|
||||
@enderror
|
||||
</span>
|
||||
</div>
|
||||
<span class="input-helper">Charge type.</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- TOTAL -->
|
||||
<div class="offset-1 col-2">
|
||||
<label class="float-right" for="fees_amt">Total</label>
|
||||
<div class="input-group">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text"><i class="fas fa-fw fa-dollar-sign"></i></span>
|
||||
</div>
|
||||
<input type="text" class="text-right form-control" id="total" value="{{ number_format($o->exists ? $o->quantity*$o->amount : 0,2) }}" disabled>
|
||||
</div>
|
||||
<span class="input-helper">Total (ex Tax).</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<!-- DESCRIPTION -->
|
||||
<div class="col-12">
|
||||
<div class="form-group has-validation">
|
||||
<label for="description">Description</label>
|
||||
<div class="input-group">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text"><i class="fas fa-fw fa-file-alt"></i></span>
|
||||
</div>
|
||||
<input type="text" class="form-control @error('description') is-invalid @enderror" id="description" name="description" value="{{ old('description',$o->exists ? $o->description : '') }}">
|
||||
|
||||
<span class="invalid-feedback" role="alert">
|
||||
@error('description')
|
||||
{{ $message }}
|
||||
@enderror
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<a href="{{ url('/home') }}" class="btn btn-danger">Cancel</a>
|
||||
@can('wholesaler')
|
||||
<button type="submit" name="submit" class="btn btn-success mr-0 float-right">@if ($site->exists)Save @else Add @endif</button>
|
||||
@endcan
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-5">
|
||||
<div id="pending"></div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@section('page-scripts')
|
||||
@css('//cdnjs.cloudflare.com/ajax/libs/select2/4.0.13/css/select2.min.css','select-css')
|
||||
@js('//cdnjs.cloudflare.com/ajax/libs/select2/4.0.13/js/select2.min.js','select-js')
|
||||
@js('/select2/fix-autofocus.js','select-fix-js','select-js')
|
||||
@css('/select2/fix-select-height.css','select-fix-css','select-css')
|
||||
|
||||
<script type="text/javascript">
|
||||
function populate(account,spinner) {
|
||||
spinner.toggleClass('d-none').toggleClass('fa-spin');
|
||||
|
||||
$.ajax({
|
||||
type: 'GET',
|
||||
dataType: 'json',
|
||||
cache: false,
|
||||
url: '{{ url('api/r/services') }}'+'/'+account,
|
||||
data: {include: {{ $o->service_id ?: 'null' }} },
|
||||
timeout: 2000,
|
||||
error: function(x) {
|
||||
spinner.toggleClass('d-none').toggleClass('fa-spin');
|
||||
alert('Failed to submit');
|
||||
},
|
||||
success: function(data) {
|
||||
$("select[name=service_id]").empty();
|
||||
$.each(data,function(i,j) {
|
||||
var row = '<option value="' + j.id + '" '+(j.id == {{ $o->service_id ?: 'null' }} ? 'selected' : '')+'>' + j.id + ': ' + j.product_name + ' ' + j.name_short + ((! j.active) ? ' **' : '') +'</option>';
|
||||
$(row).appendTo("select[name=service_id]");
|
||||
});
|
||||
|
||||
spinner.toggleClass('d-none').toggleClass('fa-spin');
|
||||
}
|
||||
});
|
||||
|
||||
$.ajax({
|
||||
type: 'GET',
|
||||
dataType: 'html',
|
||||
data: {exclude: {{ $o->id ?: 'null' }}},
|
||||
cache: false,
|
||||
url: '{{ url('r/charges') }}'+'/'+account,
|
||||
timeout: 2000,
|
||||
error: function(x) {
|
||||
spinner.toggleClass('d-none').toggleClass('fa-spin');
|
||||
alert('Failed to submit');
|
||||
},
|
||||
success: function(data) {
|
||||
$("div[id=pending]").empty().append(data);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function total() {
|
||||
$('#total').val(($('#quantity').val()*$('#amount').val()).toFixed(2));
|
||||
}
|
||||
|
||||
$(document).ready(function() {
|
||||
var spinner = $('#service_id').parent().find('i.fas.fa-spinner');
|
||||
|
||||
if ($('#account_id').val()) {
|
||||
populate($('#account_id').val(),spinner);
|
||||
}
|
||||
|
||||
$('#account_id').select2({
|
||||
sorter: data => data.sort((a, b) => a.text.localeCompare(b.text)),
|
||||
})
|
||||
.on('change',function(e) {
|
||||
$("select[id=service_id]").empty();
|
||||
|
||||
if (! $(this).val()) {
|
||||
return;
|
||||
}
|
||||
|
||||
populate($(this).val(),spinner);
|
||||
});
|
||||
|
||||
$('#service_id').select2();
|
||||
|
||||
if ($('#quantity').val() && $('#amount').val()) {
|
||||
total();
|
||||
}
|
||||
|
||||
$('#quantity').on('change',total);
|
||||
$('#amount').on('change',total);
|
||||
});
|
||||
</script>
|
||||
@append
|
@ -0,0 +1,66 @@
|
||||
@extends('adminlte::layouts.app')
|
||||
|
||||
@section('htmlheader_title')
|
||||
Unprocessed Charges
|
||||
@endsection
|
||||
@section('page_title')
|
||||
Unprocessed
|
||||
@endsection
|
||||
|
||||
@section('contentheader_title')
|
||||
Unprocessed Charges
|
||||
@endsection
|
||||
@section('contentheader_description')
|
||||
@endsection
|
||||
|
||||
@section('main-content')
|
||||
<div class="row">
|
||||
<div class="col-9">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<table class="table table-striped table-hover" id="unprocessed_charges">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Date Created</th>
|
||||
<th>Date Charge</th>
|
||||
<th>Account</th>
|
||||
<th>Service</th>
|
||||
<th>Description</th>
|
||||
<th class="text-right">Total</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
@foreach(\App\Models\Charge::unprocessed()->with(['account.user','service'])->get() as $o)
|
||||
<tr>
|
||||
<td><a href="{{ url('a/charge/addedit',$o->id) }}">{{ $o->id }}</td>
|
||||
<td>{{ $o->charge_date->format('Y-m-d') }}</td>
|
||||
<td>{{ $o->date_orig->format('Y-m-d') }}</td>
|
||||
<td>{{ $o->account->name }}</td>
|
||||
<td>{{ $o->service->name_short }}</td>
|
||||
<td>{{ $o->description }}</td>
|
||||
<td class="text-right">{{ number_format($o->quantity*$o->amount,2) }}</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@section('page-scripts')
|
||||
@css('//cdn.datatables.net/1.10.25/css/dataTables.bootstrap4.min.css','jq-dt-css')
|
||||
@js('//cdn.datatables.net/1.10.25/js/jquery.dataTables.min.js','jq-dt-js')
|
||||
@js('//cdn.datatables.net/1.10.25/js/dataTables.bootstrap4.min.js','jq-dt-bs5-js','jq-dt-js')
|
||||
|
||||
<script type="text/javascript">
|
||||
$(document).ready(function() {
|
||||
$('#unprocessed_charges').DataTable( {
|
||||
order: [1,'desc'],
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@append
|
@ -0,0 +1,37 @@
|
||||
@if(($x=$list)->count())
|
||||
<div class="card card-light">
|
||||
<div class="card-header">
|
||||
<h1 class="card-title">Pending Charges</h1>
|
||||
</div>
|
||||
|
||||
<div class="card-body">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Date Created</th>
|
||||
<th>Date Charge</th>
|
||||
<th>Service</th>
|
||||
<th>Type</th>
|
||||
<th>Description</th>
|
||||
<th class="text-right">Total</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
@foreach ($x as $co)
|
||||
<tr>
|
||||
<td><a href="{{ url('a/charge/addedit',[$co->id]) }}">{{ $co->id }}</a></td>
|
||||
<td>{{ $co->date_orig->format('Y-m-d') }}</td>
|
||||
<td>{{ $co->charge_date->format('Y-m-d') }}</td>
|
||||
<td>{{ $co->service->sid }}</td>
|
||||
<td>{{ $co->type }}</td>
|
||||
<td>{{ $co->description }}</td>
|
||||
<td class="text-right">{{ number_format($co->quantity*$co->amount,2) }}</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
@ -30,6 +30,7 @@
|
||||
@csrf
|
||||
|
||||
<div class="row">
|
||||
<!-- DATE RECEIVED -->
|
||||
<div class="col-4">
|
||||
<div class="form-group has-validation">
|
||||
<label for="payment_date">Date Received</label>
|
||||
@ -50,6 +51,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- AMOUNT -->
|
||||
<div class="offset-4 col-4">
|
||||
<div class="form-group has-validation">
|
||||
<label for="total_amt">Amount</label>
|
||||
@ -72,6 +74,7 @@
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<!-- METHOD -->
|
||||
<div class="col-4">
|
||||
<div class="form-group has-validation">
|
||||
<label for="checkout_id">Payment Method</label>
|
||||
@ -97,6 +100,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- PAYMENT FEE -->
|
||||
<div class="offset-4 col-4">
|
||||
<div class="form-group has-validation">
|
||||
<label for="fees_amt">Fee</label>
|
||||
@ -119,6 +123,7 @@
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<!-- ACCOUNT -->
|
||||
<div class="col-6">
|
||||
<div class="form-group has-validation">
|
||||
<label for="account_id">Account</label>
|
||||
@ -129,7 +134,7 @@
|
||||
<!-- @todo Only show active accounts or accounts with outstanding invoices -->
|
||||
<select class="form-control @error('account_id') is-invalid @enderror" id="account_id" name="account_id" required>
|
||||
<option value=""></option>
|
||||
@foreach (\App\Models\Account::active()->with(['user'])->get() as $ao)
|
||||
@foreach (\App\Models\Account::active()->with(['user'])->get()->sortBy('name') as $ao)
|
||||
<option value="{{ $ao->id }}" {{ $ao->id == old('account_id',$o->exists ? $o->account_id : NULL) ? 'selected' : '' }}>{{ $ao->name }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
@ -147,13 +152,14 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- BALANCE -->
|
||||
<div class="offset-2 col-4">
|
||||
<label for="fees_amt">Balance</label>
|
||||
<div class="input-group">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text"><i class="fas fa-fw fa-dollar-sign"></i></span>
|
||||
</div>
|
||||
<input type="text" class="text-right form-control @error('fees_amt') is-invalid @enderror" value="{{ number_format($o->exists ? $o->balance : 0,2) }}" disabled>
|
||||
<input type="text" class="text-right form-control @error('fees_amt') is-invalid @enderror" id="balance" value="{{ number_format($o->exists ? $o->balance : 0,2) }}" disabled>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -185,14 +191,10 @@
|
||||
@endsection
|
||||
|
||||
@section('page-scripts')
|
||||
@css('//cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css','s2-css')
|
||||
@js('//cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js','s2-js','jquery')
|
||||
|
||||
<style>
|
||||
.select2-selection.select2-selection--single {
|
||||
height: calc(2.25rem + 2px) !important;
|
||||
}
|
||||
</style>
|
||||
@css('//cdnjs.cloudflare.com/ajax/libs/select2/4.0.13/css/select2.min.css','select-css')
|
||||
@js('//cdnjs.cloudflare.com/ajax/libs/select2/4.0.13/js/select2.min.js','select-js')
|
||||
@js('/select2/fix-autofocus.js','select-fix-js','select-js')
|
||||
@css('/select2/fix-select-height.css','select-fix-css','select-css')
|
||||
|
||||
<script type="text/javascript">
|
||||
function populate(account,spinner) {
|
||||
@ -203,7 +205,7 @@
|
||||
dataType: 'html',
|
||||
cache: false,
|
||||
url: '{{ url('api/r/invoices') }}'+'/'+account,
|
||||
data: {pid:{{ $o->id }}},
|
||||
data: {pid:{{ $o->id ?: 'null' }}},
|
||||
timeout: 2000,
|
||||
error: function(x) {
|
||||
spinner.toggleClass('d-none').toggleClass('fa-spin');
|
||||
@ -216,6 +218,16 @@
|
||||
});
|
||||
}
|
||||
|
||||
function balance() {
|
||||
var alloc = 0;
|
||||
|
||||
$('.invoice').each(function() {
|
||||
alloc += $(this).val();
|
||||
})
|
||||
|
||||
$('#balance').val(($('#total_amt').val()-alloc).toFixed(2))
|
||||
}
|
||||
|
||||
$(document).ready(function() {
|
||||
if ($('#account_id').val()) {
|
||||
var spinner = $('#account_id').parent().parent().find('i.fas.fa-spinner');
|
||||
@ -236,6 +248,10 @@
|
||||
|
||||
populate($(this).val(),spinner);
|
||||
});
|
||||
|
||||
$('#total_amt').on('change',balance);
|
||||
});
|
||||
|
||||
$(document).on('change','.invoice',balance);
|
||||
</script>
|
||||
@append
|
||||
|
@ -61,11 +61,6 @@
|
||||
$(document).ready(function() {
|
||||
$('#unapplied_payments').DataTable( {
|
||||
order: [1,'desc'],
|
||||
orderFixed: [1,'desc']
|
||||
});
|
||||
|
||||
$('#unapplied_payments tbody').on('click','tr', function () {
|
||||
$(this).toggleClass('selected');
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
@ -27,7 +27,7 @@
|
||||
<td>{{ number_format($io->total,2) }}</td>
|
||||
<td>{{ number_format($io->due,2) }}</td>
|
||||
<td class="text-right">
|
||||
<input type="text" class="text-right" name="invoices[{{ $io->id }}]" value="{{ number_format(($x=$io->paymentitems->filter(function($item) use ($pid) { return $item->payment_id == $pid; })) ? $x->sum('alloc_amt') : 0,2) }}">
|
||||
<input type="text" class="text-right invoice" name="invoices[{{ $io->id }}]" value="{{ number_format(($x=$io->paymentitems->filter(function($item) use ($pid) { return $item->payment_id == $pid; })) ? $x->sum('alloc_amt') : 0,2) }}">
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
|
@ -23,27 +23,49 @@
|
||||
</li>
|
||||
|
||||
@can('wholesaler')
|
||||
<!-- PAYMENTS -->
|
||||
<li class="nav-item has-treeview @if(preg_match('#^payment/#',Route::current()->uri())) menu-open @endif">
|
||||
<a href="#" class="nav-link @if(preg_match('#^payment/#',Route::current()->uri())) active @endif">
|
||||
<i class="nav-icon fas fa-receipt"></i>
|
||||
<p>Payments <i class="fas fa-angle-left right"></i></p>
|
||||
</a>
|
||||
<!-- CHARGES -->
|
||||
<li class="nav-item has-treeview @if(preg_match('#^charge/#',Route::current()->uri())) menu-open @endif">
|
||||
<a href="#" class="nav-link @if(preg_match('#^charge/#',Route::current()->uri())) active @endif">
|
||||
<i class="nav-icon fas fa-plus"></i>
|
||||
<p>Charges <i class="fas fa-angle-left right"></i></p>
|
||||
</a>
|
||||
|
||||
<ul class="nav nav-treeview">
|
||||
<li class="nav-item">
|
||||
<a href="{{ url('a/payment/addedit') }}" class="nav-link @if(Route::current()->uri() == 'payment/addedit') active @endif">
|
||||
<i class="fas fa-money-bill nav-icon"></i> <p>New Payment</p>
|
||||
</a>
|
||||
</li>
|
||||
<ul class="nav nav-treeview">
|
||||
<li class="nav-item">
|
||||
<a href="{{ url('a/charge/addedit') }}" class="nav-link @if(Route::current()->uri() == 'charge/addedit') active @endif">
|
||||
<i class="fas fa-cart-plus nav-icon"></i> <p>New Charge</p>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li class="nav-item">
|
||||
<a href="{{ url('a/payment/unapplied') }}" class="nav-link @if(Route::current()->uri() == 'payment/unapplied') active @endif">
|
||||
<i class="fas fa-receipt nav-icon"></i> <p>Unapplied</p>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="{{ url('a/charge/unprocessed') }}" class="nav-link @if(Route::current()->uri() == 'charge/unprocessed') active @endif">
|
||||
<i class="fas fa-list nav-icon"></i> <p>Unprocessed</p>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
<!-- PAYMENTS -->
|
||||
<li class="nav-item has-treeview @if(preg_match('#^payment/#',Route::current()->uri())) menu-open @endif">
|
||||
<a href="#" class="nav-link @if(preg_match('#^payment/#',Route::current()->uri())) active @endif">
|
||||
<i class="nav-icon fas fa-receipt"></i>
|
||||
<p>Payments <i class="fas fa-angle-left right"></i></p>
|
||||
</a>
|
||||
|
||||
<ul class="nav nav-treeview">
|
||||
<li class="nav-item">
|
||||
<a href="{{ url('a/payment/addedit') }}" class="nav-link @if(Route::current()->uri() == 'payment/addedit') active @endif">
|
||||
<i class="fas fa-money-bill nav-icon"></i> <p>New Payment</p>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li class="nav-item">
|
||||
<a href="{{ url('a/payment/unapplied') }}" class="nav-link @if(Route::current()->uri() == 'payment/unapplied') active @endif">
|
||||
<i class="fas fa-receipt nav-icon"></i> <p>Unapplied</p>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
@endcan
|
||||
|
||||
@can('wholesaler')
|
||||
|
@ -18,6 +18,8 @@ use App\Http\Controllers\{AdminController,
|
||||
Route::group(['middleware'=>['auth:api','role:reseller']], function() {
|
||||
// Route::get('/r/agents','ResellerServicesController@agents');
|
||||
Route::get('/r/accounts',[ResellerServicesController::class,'accounts']);
|
||||
Route::get('/r/services/{o}',[ResellerServicesController::class,'services'])
|
||||
->where('o','[0-9]+');
|
||||
// Route::get('/r/clients','ResellerServicesController@clients');
|
||||
// Route::get('/r/service_inactive','ResellerServicesController@service_inactive');
|
||||
Route::post('r/invoices/{o}',[AdminController::class,'pay_invoices'])
|
||||
|
@ -44,6 +44,11 @@ Route::group(['middleware'=>['theme:adminlte-be','auth','role:wholesaler'],'pref
|
||||
// Route::post('service/{o}','AdminHomeController@service_update');
|
||||
// Route::get('report/products','Wholesale\ReportController@products');
|
||||
|
||||
// Charges
|
||||
Route::match(['get','post'],'charge/addedit/{o?}',[AdminController::class,'charge_addedit']);
|
||||
Route::get('charge/unprocessed',[AdminController::class,'charge_unprocessed']);
|
||||
|
||||
// Payments
|
||||
Route::match(['get','post'],'payment/addedit/{o?}',[AdminController::class,'pay_addedit']);
|
||||
Route::get('payment/unapplied',[AdminController::class,'pay_unapplied']);
|
||||
|
||||
@ -65,6 +70,10 @@ Route::group(['middleware'=>['theme:adminlte-be','auth','role:reseller'],'prefix
|
||||
Route::group(['middleware'=>['theme:adminlte-be','auth','role:reseller'],'prefix'=>'report'],function() {
|
||||
Route::get('domain',[ServiceController::class,'domain_list']);
|
||||
});
|
||||
|
||||
// Charges on an account
|
||||
Route::get('charges/{o}',[AdminController::class,'charge_pending_account'])
|
||||
->where('o','[0-9]+');
|
||||
});
|
||||
|
||||
// Our User Routes
|
||||
|
Loading…
Reference in New Issue
Block a user