Added payment recording, minor CSS fixes, enabled Search

This commit is contained in:
Deon George 2021-07-02 14:35:43 +10:00
parent b89e8d18d5
commit 1bba21dcef
No known key found for this signature in database
GPG Key ID: 7670E8DC27415254
13 changed files with 362 additions and 12 deletions

View File

@ -5,7 +5,7 @@ namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Arr;
use App\Models\{Service,SiteDetail};
use App\Models\{Account,Payment,PaymentItem,Service,SiteDetail};
class AdminController extends Controller
{
@ -14,6 +14,66 @@ class AdminController extends Controller
return View('a.service',['o'=>$o]);
}
/**
* Record payments on an account.
*
* @param Request $request
* @param Payment $o
* @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View|\Illuminate\Http\RedirectResponse
*/
public function pay_add(Request $request,Payment $o)
{
if ($request->post()) {
$validation = $request->validate([
'account_id' => 'required|exists:ab_account,id',
'date_payment' => 'required|date',
'checkout_id' => 'required|exists:ab_checkout,id',
'total_amt' => 'required|numeric|min:0.01',
'fees_amt' => 'nullable|numeric|lt:total_amt',
'source_id' => 'nullable|exists:ab_account,id',
'pending' => 'nullable|boolean',
'notes' => 'nullable|string',
'ip' => 'nullable|ip',
'invoices' => ['nullable','array',function ($attribute,$value,$fail) use ($request) {
if (collect($value)->sum() > $request->post('total_amt'))
$fail('Allocation is greater than payment total.');
}],
'invoices.*.id' => 'nullable|exists:ab_invoice,id',
]);
$oo = new Payment;
$oo->forceFill($request->only(['account_id','date_payment','checkout_id','checkout_id','total_amt','fees_amt','source_id','pending','notes','ip']));
$oo->site_id = config('SITE')->site_id;
$oo->save();
foreach ($validation['invoices'] as $id => $amount) {
$ooo = new PaymentItem;
$ooo->invoice_id = $id;
$ooo->alloc_amt = $amount;
$ooo->site_id = config('SITE')->site_id;
$oo->items()->save($ooo);
}
return redirect()->back()
->with('success','Payment recorded');
}
return view('a.payment.add')
->with('o',$o);
}
/**
* Show a list of invoices to apply payments to
*
* @param Account $o
* @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View
*/
public function pay_invoices(Account $o)
{
return view('a.payment.widgets.invoices')
->with('o',$o);
}
/**
* Site setup
*

View File

@ -60,7 +60,7 @@ class SearchController extends Controller
->orderBy('id')
->limit(10)->get() as $o)
{
$result->push(['name'=>sprintf('%s #%s',$o->account->name,$o->invoice_id),'value'=>'/u/invoice/'.$o->id,'category'=>'Invoices']);
$result->push(['name'=>sprintf('%s: %s',$o->sid,$o->account->name),'value'=>'/u/invoice/'.$o->id,'category'=>'Invoices']);
}
# Look for an ADSL/NBN Service

View File

@ -185,7 +185,7 @@ class Account extends Model implements IDs
public function getNameAttribute()
{
return $this->company ?: $this->user->SurFirstName;
return $this->company ?: ($this->user_id ? $this->user->SurFirstName : 'AID:'.$this->id);
}
public function getServicesCountHtmlAttribute()

View File

@ -48,11 +48,13 @@ class Invoice extends Model implements IDs
// Array of items that can be updated with PushNew
protected $pushable = ['items'];
/*
protected $with = [
'account.country.currency',
'items.taxes',
'paymentitems'
];
*/
// Caching variables
private int $_paid = 0;

View File

@ -32,7 +32,7 @@ class Payment extends Model implements IDs
protected $table = 'ab_payment';
protected $dates = ['date_payment'];
protected $dateFormat = 'U';
protected $with = ['account.country.currency','items'];
//protected $with = ['account.country.currency','items'];
// Array of items that can be updated with PushNew
protected $pushable = ['items'];

View File

@ -4,8 +4,7 @@ namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use App\Traits\NextKey;
use App\Traits\PushNew;
use App\Traits\{NextKey,PushNew};
class PaymentItem extends Model
{
@ -13,11 +12,14 @@ class PaymentItem extends Model
const RECORD_ID = 'payment_item';
public $incrementing = FALSE;
protected $dateFormat = 'U';
const CREATED_AT = 'date_orig';
const UPDATED_AT = 'date_last';
protected $table = 'ab_payment_item';
/* RELATIONS */
public function payment() {
return $this->belongsTo(Payment::class);
}

View File

@ -3,6 +3,6 @@ hr.d-print-block {
padding-bottom: 20px;
}
.table.table-striped tbody tr:last-child td {
.table:not(.table-borderless) tbody tr:last-child td {
border-bottom: 2px solid #dee2e6;
}

View File

@ -0,0 +1,230 @@
@extends('adminlte::layouts.app')
@section('htmlheader_title')
Payment
@endsection
@section('page_title')
Payment
@endsection
@section('contentheader_title')
Record Payment
@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 Payment</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">
<div class="col-4">
<div class="form-group has-validation">
<label for="date_payment">Date Received</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('date_payment') is-invalid @enderror" id="date_payment" name="date_payment" value="{{ old('date_payment',$o->exists ? $o->date_payment : \Carbon\Carbon::now()->format('Y-m-d')) }}" required>
<span class="invalid-feedback" role="alert">
@error('date_payment')
{{ $message }}
@else
Payment Date is required.
@enderror
</span>
</div>
<span class="input-helper">Date Payment Received.</span>
</div>
</div>
<div class="offset-1 col-4">
<div class="form-group has-validation">
<label for="total_amt">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="form-control @error('total_amt') is-invalid @enderror" id="total_amt" name="total_amt" value="{{ old('total_amt',$o->exists ? $o->total_amt : 0) }}" required>
<span class="invalid-feedback" role="alert">
@error('total_amt')
{{ $message }}
@else
Payment Amount is required.
@enderror
</span>
</div>
<span class="input-helper">Amount Received.</span>
</div>
</div>
</div>
<div class="row">
<div class="col-4">
<div class="form-group has-validation">
<label for="checkout_id">Payment Method</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('checkout_id') is-invalid @enderror" id="checkout_id" name="checkout_id" required>
<option value=""></option>
@foreach (\App\Models\Checkout::orderBy('name')->get() as $co)
<option value="{{ $co->id }}" {{ $co->id == old('checkout_id',$o->exists ? $o->checkout_id : NULL) ? 'selected' : '' }}>{{ $co->name }}</option>
@endforeach
</select>
<span class="invalid-feedback" role="alert">
@error('checkout_id')
{{ $message }}
@else
Payment Method is required.
@enderror
</span>
</div>
<span class="input-helper">Payment Method.</span>
</div>
</div>
<div class="offset-1 col-4">
<div class="form-group has-validation">
<label for="fees_amt">Fee</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="form-control @error('fees_amt') is-invalid @enderror" id="fees_amt" name="fees_amt" value="{{ old('fees_amt',$o->exists ? $o->fees_amt : 0) }}" required>
<span class="invalid-feedback" role="alert">
@error('fees_amt')
{{ $message }}
@else
Payment Amount is required.
@enderror
</span>
</div>
<span class="input-helper">Amount Received.</span>
</div>
</div>
</div>
<div class="row">
<div class="col-6">
<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>
<!-- @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)
<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 payment to.</span>
<i class="fas fa-spinner d-none"></i>
</div>
</div>
</div>
<div class="row mb-3">
<div class="col-6">
<div id="invoices"></div>
@error('invoices')
<span class="invalid-feedback d-block mt-2 mb-2" role="alert">
{{ $message }}
</span>
@enderror
</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>
@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>
<script type="text/javascript">
function populate(account,spinner) {
spinner.toggleClass('d-none').toggleClass('fa-spin');
$.ajax({
type: 'POST',
dataType: 'html',
cache: false,
url: '{{ url('api/r/invoices') }}'+'/'+account,
timeout: 2000,
error: function(x) {
spinner.toggleClass('d-none').toggleClass('fa-spin');
alert('Failed to submit');
},
success: function(data) {
spinner.toggleClass('d-none').toggleClass('fa-spin');
$("div[id=invoices]").empty().append(data);
}
});
}
$(document).ready(function() {
if ($('#account_id').val()) {
var spinner = $('#account_id').parent().parent().find('i.fas.fa-spinner');
populate($('#account_id').val(),spinner);
}
$('#account_id').select2({
sorter: data => data.sort((a, b) => a.text.localeCompare(b.text)),
})
.on('change',function(e) {
if (! $(this).val()) {
$("div[id=invoices]").empty();
return;
}
var spinner = $(this).parent().parent().find('i.fas.fa-spinner');
populate($(this).val(),spinner);
});
});
</script>
@append

View File

@ -0,0 +1,32 @@
<div class="form-group mb-0">
<label for="checkout_id">Invoices Due</label>
@if(($x=$o->invoices()->where('active',TRUE)->orderBy('due_date')->with(['items.taxes','paymentitems.payment','account'])->get()->where('due','>',0))->count())
<table class="table table-hover">
<thead>
<tr>
<th>ID</th>
<th>Date Issue</th>
<th>Date Due</th>
<th>Total</th>
<th>Due</th>
<th>Apply</th>
</tr>
</thead>
<tbody>
@foreach ($x as $io)
<tr>
<td><a href="{{ url('u/invoice',[$io->id]) }}">{{ $io->sid }}</a></td>
<td>{{ $io->invoice_date->format('Y-m-d') }}</td>
<td>{{ $io->due_date->format('Y-m-d') }}</td>
<td>{{ number_format($io->total,2) }}</td>
<td>{{ number_format($io->due,2) }}</td>
<td><input type="text" name="invoices[{{ $io->id }}]"></td>
</tr>
@endforeach
</tbody>
</table>
@else
<p>No invoices due.</p>
@endif
</div>

View File

@ -204,9 +204,9 @@
<div class="row">
<div class="col-12">
<a href="{{ url('/') }}" class="btn btn-danger">Cancel</a>
<a href="{{ url('/home') }}" class="btn btn-danger">Cancel</a>
@can('wholesaler')
<button type="submit" name="submit" class="btn btn-success mr-0 float-end">@if ($site->exists)Save @else Add @endif</button>
<button type="submit" name="submit" class="btn btn-success mr-0 float-right">@if ($site->exists)Save @else Add @endif</button>
@endcan
</div>
</div>

View File

@ -22,6 +22,24 @@
</ul>
</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>
<ul class="nav nav-treeview">
<li class="nav-item">
<a href="{{ url('a/payment/add') }}" class="nav-link @if(Route::current()->uri() == 'payment/add') active @endif">
<i class="fas fa-money-bill nav-icon"></i> <p>New Payment</p>
</a>
</li>
</ul>
</li>
@endcan
@can('wholesaler')
<li class="nav-header">ADMIN</li>

View File

@ -1,6 +1,8 @@
<?php
use App\Http\Controllers\{CheckoutController,ResellerServicesController};
use App\Http\Controllers\{AdminController,
CheckoutController,
ResellerServicesController};
/*
|--------------------------------------------------------------------------
@ -18,6 +20,9 @@ Route::group(['middleware'=>['auth:api','role:reseller']], function() {
Route::get('/r/accounts',[ResellerServicesController::class,'accounts']);
Route::get('/r/clients','ResellerServicesController@clients');
Route::get('/r/service_inactive','ResellerServicesController@service_inactive');
Route::post('r/invoices/{o}',[AdminController::class,'pay_invoices'])
->where('o','[0-9]+')
->middleware(['theme:adminlte-be','role:wholesaler']);
});
Route::group(['middleware'=>'auth:api'], function() {

View File

@ -6,6 +6,7 @@ use App\Http\Controllers\{AdminController,
HomeController,
MediaController,
PaypalController,
SearchController,
WelcomeController};
/*
@ -38,7 +39,7 @@ Route::group(['middleware'=>['theme:adminlte-be','auth','role:wholesaler'],'pref
Route::get('service/{o}','AdminHomeController@service');
Route::post('service/{o}','AdminHomeController@service_update');
Route::get('report/products','Wholesale\ReportController@products');
Route::get('payment/add','PaymentController@add');
Route::match(['get','post'],'payment/add',[AdminController::class,'pay_add']);
//Route::get('accounting/connect','AccountingController@connect');
});
@ -105,7 +106,7 @@ Route::get('product_order/{o}','OrderController@product_order');
Route::get('product_info/{o}','OrderController@product_info');
Route::redirect('home','u/home');
Route::get('search','SearchController@search');
Route::get('search',[SearchController::class,'search']);
Route::get('pay/paypal/authorise',[PaypalController::class,'authorise']);
Route::get('pay/paypal/cancel',[PaypalController::class,'cancel']);