Update checkout, enable editing of checkout, show details on invoices

This commit is contained in:
Deon George 2022-07-29 16:06:19 +10:00
parent 4f7a27dd8d
commit 39ded93a42
17 changed files with 434 additions and 52 deletions

View File

@ -96,7 +96,7 @@ class AdminController extends Controller
$validation = $request->validate([
'account_id' => 'required|exists:accounts,id',
'paid_at' => 'required|date',
'checkout_id' => 'required|exists:ab_checkout,id',
'checkout_id' => 'required|exists:checkouts,id',
'total_amt' => 'required|numeric|min:0.01',
'fees_amt' => 'nullable|numeric|lt:total_amt',
'source_id' => 'nullable|exists:accounts,id',

View File

@ -2,13 +2,41 @@
namespace App\Http\Controllers;
use App\Models\Invoice;
use Illuminate\Http\Request;
use Illuminate\View\View;
use App\Models\Checkout;
use App\Http\Requests\CheckoutAddEdit;
use App\Models\{Checkout,Invoice};
class CheckoutController extends Controller
{
/**
* Update a suppliers details
*
* @param CheckoutAddEdit $request
* @param Checkout $o
* @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View|\Illuminate\Http\RedirectResponse
*/
public function addedit(CheckoutAddEdit $request,Checkout $o)
{
$this->middleware(['auth','wholesaler']);
foreach ($request->except(['_token','active','submit']) as $key => $item)
$o->{$key} = $item;
$o->active = (bool)$request->active;
try {
$o->save();
} catch (\Exception $e) {
return redirect()->back()->withErrors($e->getMessage())->withInput();
}
return redirect()->back()
->with('success','Payment saved');
}
public function cart_invoice(Request $request,Invoice $o=NULL)
{
if ($o) {
@ -27,8 +55,29 @@ class CheckoutController extends Controller
return $o->fee($request->post('total',0));
}
/**
* Render a specific invoice for the user
*
* @return View
*/
public function home(): View
{
return View('payment.home');
}
public function pay(Request $request,Checkout $o)
{
return redirect('pay/paypal/authorise');
}
/**
* View details on a specific payment method
*
* @param Checkout $o
* @return View
*/
public function view(Checkout $o): View
{
return View('payment.view',['o'=>$o]);
}
}

View File

@ -8,7 +8,6 @@ use Illuminate\Contracts\View\Factory;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Log;
use Illuminate\View\View;
use Barryvdh\Snappy\Facades\SnappyPdf as PDF;
use App\Models\{Invoice,Service,User};
@ -37,28 +36,6 @@ class HomeController extends Controller
return View('u.home',['o'=>$o]);
}
/**
* Render a specific invoice for the user
*
* @param Invoice $o
* @return View
*/
public function invoice(Invoice $o): View
{
return View('u.invoice.home',['o'=>$o]);
}
/**
* Return the invoice in PDF format, ready to download
*
* @param Invoice $o
* @return mixed
*/
public function invoice_pdf(Invoice $o)
{
return PDF::loadView('u.invoice.home',['o'=>$o])->stream(sprintf('%s.pdf',$o->sid));
}
/**
* Enable the user to down an invoice by providing a link in email
*

View File

@ -0,0 +1,41 @@
<?php
namespace App\Http\Controllers;
use Illuminate\View\View;
use Barryvdh\Snappy\Facades\SnappyPdf as PDF;
use App\Models\Invoice;
/**
* Class InvoiceController
* This controller manages invoices
*
* The methods to this controller should be projected by the route
*
* @package App\Http\Controllers
*/
class InvoiceController extends Controller
{
/**
* Return the invoice in PDF format, ready to download
*
* @param Invoice $o
* @return mixed
*/
public function pdf(Invoice $o)
{
return PDF::loadView('u.invoice.home',['o'=>$o])->stream(sprintf('%s.pdf',$o->sid));
}
/**
* Render a specific invoice for the user
*
* @param Invoice $o
* @return View
*/
public function view(Invoice $o): View
{
return View('invoice.view',['o'=>$o]);
}
}

View File

@ -0,0 +1,36 @@
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Auth;
/**
* Editing Suppliers
*/
class CheckoutAddEdit extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return Auth::user()->isWholesaler();
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, mixed>
*/
public function rules()
{
return [
'name' => 'required|string|min:2|max:255',
'active' => 'sometimes|accepted',
'description' => 'nullable|string|min:2|max:255',
];
}
}

View File

@ -19,7 +19,6 @@ class InvoiceEmail extends Mailable
* Create a new message instance.
*
* @param Invoice $o
* @param string $notes
*/
public function __construct(Invoice $o)
{

View File

@ -2,33 +2,43 @@
namespace App\Models;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Model;
use Leenooks\Traits\ScopeActive;
class Checkout extends Model
{
protected $table = 'ab_checkout';
public $timestamps = FALSE;
use ScopeActive;
protected $casts = [
'plugin_data'=>'json',
];
/* RELATIONS */
public function payments()
{
return $this->hasMany(Payment::class);
}
/** SCOPES **/
/* STATIC METHODS */
/**
* Search for a record
*
* @param $query
* @param string $term
* @return
*/
public function scopeActive($query)
public static function available(): Collection
{
return $query->where('active',TRUE);
return self::active()->get();
}
/** FUNCTIONS **/
/* ATTRIBUTES */
public function getIconAttribute(): string
{
switch(strtolower($this->name)) {
case 'paypal': return 'fab fa-cc-paypal';
default: return 'fas fa-money-bill-alt';
}
}
/* METHODS */
public function fee(float $amt,int $items=1): float
{

View File

@ -109,7 +109,7 @@ class Invoice extends Model implements IDs
private int $_total = 0;
private int $_total_tax = 0;
/* STATIC */
/* STATIC METHODS */
/**
* This works out what multiplier to use to change billing periods

View File

@ -17,7 +17,7 @@ class SSL extends Type
protected $public_key = NULL;
protected $crt_parse = NULL;
/* STATIC */
/* STATIC METHODS */
public static function boot()
{

View File

@ -0,0 +1,53 @@
<?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()
{
DB::statement('RENAME TABLE ab_checkout TO checkouts');
DB::statement('ALTER TABLE checkouts MODIFY active tinyint(1) NOT NULL,MODIFY fee_passon tinyint(1) DEFAULT NULL');
Schema::table('checkouts', function (Blueprint $table) {
$table->datetime('created_at')->nullable()->after('id');
$table->datetime('updated_at')->nullable()->after('created_at');
$table->dropForeign('ab_checkout_site_id_foreign');
$table->dropIndex('ab_checkout_id_site_id_index');
$table->foreign(['site_id'])->references(['id'])->on('sites');
});
Schema::table('payments', function (Blueprint $table) {
$table->foreign(['checkout_id','site_id'])->references(['id','site_id'])->on('checkouts');
});
foreach (\App\Models\Checkout::withoutGlobalScope(\App\Models\Scopes\SiteScope::class)->cursor() as $o) {
if ($x=$o->getRawOriginal('plugin_data')) {
Config::set('site',$o->site);
$o->plugin_data = unserialize($x);
}
$o->save();
}
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
abort(500,'Cant go back');
}
};

View File

@ -1,3 +1,4 @@
<!-- $o = Invoice::class -->
@extends('adminlte::layouts.app')
@section('htmlheader_title')
@ -140,14 +141,19 @@
<!-- accepted payments column -->
<div class="col-6">
<p class="lead">Payment Methods:</p>
{{--
<img src="../../dist/img/credit/visa.png" alt="Visa">
<img src="../../dist/img/credit/mastercard.png" alt="Mastercard">
<img src="../../dist/img/credit/american-express.png" alt="American Express">
<img src="../../dist/img/credit/paypal2.png" alt="Paypal">
--}}
<p class="text-muted well well-sm no-shadow" style="margin-top: 10px;">
<table class="table table-borderless">
@foreach (\App\Models\Checkout::available() as $cho)
<tr>
<td style="width: 50px;"><i class="fa-2x fa-fw {{ $cho->icon }}"></i></td>
<td>{{ $cho->name }}</td>
<td>{{ $cho->description }}</td>
<td class="w-25">@includeIf('payment.widget.plugin.'.strtolower($cho->plugin),['o'=>$cho])</td>
</tr>
@endforeach
</table>
<p class="text-muted well well-sm no-shadow" style="position: absolute;bottom: 0;left: 0;">
{!! $o->invoice_text !!}
</p>
</div>

View File

@ -0,0 +1,73 @@
@extends('adminlte::layouts.app')
@section('htmlheader_title')
Payment
@endsection
@section('page_title')
Payment
@endsection
@section('contentheader_title')
Payment
@endsection
@section('contentheader_description')
@endsection
@section('main-content')
<div class="row">
<div class="col-12">
<div class="card card-dark">
<div class="card-header">
<h1 class="card-title">Payment Configuration</h1>
</div>
<div class="card-body">
<form class="g-0 needs-validation" method="POST" enctype="multipart/form-data" role="form">
@csrf
<div class="row">
<div class="col-4">
<div class="form-group has-validation">
<label for="name">Payment Name</label>
<select class="form-control form-control-border" id="name" name="checkout_id">
<option value=""></option>
<option value="">Add New</option>
@foreach(\App\Models\Checkout::orderBy('active','DESC')->orderBy('name')->get()->groupBy('active') as $o)
<optgroup label="{{ $o->first()->active ? 'Active' : 'Not Active' }}">
@foreach($o as $oo)
<option value="{{ $oo->id }}">{{ $oo->name }}</option>
@endforeach
</optgroup>
@endforeach
</select>
<span class="invalid-feedback" role="alert">
@error('name')
{{ $message }}
@else
Payment Name is required.
@enderror
</span>
<span class="input-helper">Payment Name</span>
</div>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
@endsection
@section('page-scripts')
@css(select2)
@js(select2,autofocus)
<script type="text/javascript">
$(document).ready(function() {
$('#name').select2()
.on('change',function(item) {
window.location.href = '{{ url('a/checkout') }}'+(item.target.value ? '/'+item.target.value : '');
});
});
</script>
@endsection

View File

@ -0,0 +1,38 @@
<!-- $o = Checkout::class -->
@extends('adminlte::layouts.app')
@section('htmlheader_title')
{{ $o->name ?: 'New Payment' }}
@endsection
@section('page_title')
{{ $o->name ?: 'New Payment' }}
@endsection
@section('contentheader_title')
{{ $o->name ?: 'New Payment' }}
@endsection
@section('contentheader_description')
@include('adminlte::widget.status')
@endsection
@section('main-content')
<div class="row">
<div class="col-12">
<div class="card">
<div class="card-header bg-dark d-flex p-0">
<ul class="nav nav-pills w-100 p-2">
<li class="nav-item"><a class="nav-link active" href="#details" data-toggle="tab">Detail</a></li>
</ul>
</div>
<div class="card-body">
<div class="tab-content">
<div class="tab-pane fade active show" id="details" role="tabpanel">
@include('payment.widget.detail')
</div>
</div>
</div>
</div>
</div>
</div>
@endsection

View File

@ -0,0 +1,70 @@
<!-- $o = Checkout::class -->
<div class="row">
<div class="col-12">
<h3>Checkout Details</h3>
<hr>
@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
<form class="g-0 needs-validation" method="POST" enctype="multipart/form-data" role="form">
@csrf
<div class="row">
<div class="col-6">
<div class="row">
<!-- Checkout Name -->
<div class="col-9">
<div class="form-group has-validation">
<label for="name">Checkout Name</label>
<input type="text" class="form-control form-control-border @error('name') is-invalid @enderror" id="name" name="name" placeholder="Supplier Name" value="{{ old('name',$o->name) }}" required>
<span class="invalid-feedback" role="alert">
@error('name')
{{ $message }}
@else
Payment Name required.
@enderror
</span>
</div>
</div>
<!-- Checkout Active -->
<div class="col-3">
<div class="form-group">
<div class="custom-control custom-switch custom-switch-off-danger custom-switch-on-success">
<input type="checkbox" class="custom-control-input" id="active" name="active" {{ old('active',$o->active) ? 'checked' : '' }}>
<label class="custom-control-label" for="active">Active</label>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<!-- Description -->
<div class="col-12">
<div class="form-group has-validation">
<label for="description">Description</label>
<input type="text" class="form-control form-control-border @error('description') is-invalid @enderror" id="address1" name="description" placeholder="description" value="{{ old('description',$o->description) }}">
<span class="invalid-feedback" role="alert">
@error('description')
{{ $message }}
@enderror
</span>
</div>
</div>
</div>
<div class="row">
<!-- Buttons -->
<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 ($o->exists)Save @else Add @endif</button>
@endcan
</div>
</div>
</form>
</div>
</div>

View File

@ -0,0 +1,15 @@
<!-- $o=Checkout::class -->
<table class="table table-borderless table-sm">
<tr>
<td>Bank</td><th>{{ Arr::get($o->plugin_data,'bankname') }}</th>
</tr>
<tr>
<td>Branch</td><th>{{ Arr::get($o->plugin_data,'bankbranch') }}</th>
</tr>
<tr>
<td>BSB</td><th>{{ Arr::get($o->plugin_data,'bankbsb') }}</th>
</tr>
<tr>
<td>ACC</td><th>{{ Arr::get($o->plugin_data,'bankaccount') }}</th>
</tr>
</table>

View File

@ -77,6 +77,14 @@
</a>
</li>
<!-- CHECKOUT (PAYMENTS) -->
<li class="nav-item">
<a href="{{ url('a/checkout') }}" class="nav-link @if(preg_match('#^a/checkout#',$path)) active @endif">
<i class="nav-icon fas fa-money-check-alt"></i> <p>Payments</p>
</a>
</li>
<!-- PRODUCTS -->
<li class="nav-item">
<a href="{{ url('a/product') }}" class="nav-link @if(preg_match('#^a/product#',$path)) active @endif">
<i class="nav-icon fas fa-barcode"></i> <p>Products</p>
@ -105,7 +113,6 @@
</ul>
</li>
<li class="nav-item has-treeview @if(preg_match('#^a/report/(products)#',$path))menu-open @else menu-closed @endif">
<a href="#" class="nav-link @if(preg_match('#^a/report/(products)#',$path)) active @endif">
<i class="nav-icon fas fa-list"></i> <p>REPORT<i class="fas fa-angle-left right"></i></p>

View File

@ -6,6 +6,7 @@ use App\Http\Controllers\{AdminController,
Auth\SocialLoginController,
CheckoutController,
HomeController,
InvoiceController,
MediaController,
OrderController,
PaypalController,
@ -60,6 +61,13 @@ Route::group(['middleware'=>['theme:adminlte-be','auth','role:wholesaler'],'pref
// Site Setup
Route::match(['get','post'],'setup',[AdminController::class,'setup']);
// Checkout Setup (Payments)
Route::get('checkout',[CheckoutController::class,'home']);
Route::get('checkout/{o?}',[CheckoutController::class,'view'])
->where('o','[0-9]+');
Route::post('checkout/{o?}',[CheckoutController::class,'addedit'])
->where('o','[0-9]+');
// Product Setup
Route::match(['get'],'product',[ProductController::class,'home']);
Route::match(['get','post'],'product/details/{o?}',[ProductController::class,'details'])
@ -140,10 +148,10 @@ Route::group(['middleware'=>['theme:adminlte-be','auth'],'prefix'=>'u'],function
// ->where('o','[0-9]+')
// ->middleware('can:view,o');
Route::post('checkout/pay',[CheckoutController::class,'pay']);
Route::get('invoice/{o}',[HomeController::class,'invoice'])
Route::get('invoice/{o}',[InvoiceController::class,'view'])
->where('o','[0-9]+')
->middleware('can:view,o');
Route::get('invoice/{o}/pdf',[HomeController::class,'invoice_pdf'])
Route::get('invoice/{o}/pdf',[InvoiceController::class,'pdf'])
->where('o','[0-9]+')
->middleware('can:view,o');
Route::get('invoice/cart',[CheckoutController::class,'cart_invoice']);