Compare commits

..

6 Commits

Author SHA1 Message Date
f60727f5fb Framework updates
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 35s
Create Docker Image / Final Docker Image Manifest (push) Successful in 9s
2024-08-10 22:26:41 +10:00
222fd5092e Move setup out of resources/a 2024-08-10 22:21:38 +10:00
f1dd68a737 Fixes for cart and payment/paypal processing 2024-08-10 22:17:21 +10:00
efbb3d091f Separated Checkout and Payment controllers, updates to checkout and payments 2024-08-10 10:14:47 +10:00
06f25d5d4d Resize and compress web homepage images 2024-08-09 19:44:27 +10:00
c54b0fdc79 Remove redundant service controller method and view 2024-08-03 21:45:47 +10:00
41 changed files with 830 additions and 919 deletions

View File

@ -3,18 +3,9 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use Illuminate\Http\RedirectResponse; use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use App\Http\Requests\SiteEdit; use App\Http\Requests\SiteEdit;
use App\Models\{Account, use App\Models\SiteDetail;
Charge,
Invoice,
InvoiceItem,
Payment,
PaymentItem,
Service,
SiteDetail};
/** /**
* The AdminController governs all routes that are prefixed with 'a/'. * The AdminController governs all routes that are prefixed with 'a/'.
@ -23,115 +14,6 @@ use App\Models\{Account,
*/ */
class AdminController extends Controller class AdminController extends Controller
{ {
// @todo Move to reseller
public function service(Service $o)
{
return view('theme.backend.adminlte.a.service')
->with('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
*/
// @todo Move to reseller
public function pay_addedit(Request $request,Payment $o)
{
if ($request->post()) {
$validation = $request->validate([
'account_id' => 'required|exists:accounts,id',
'paid_at' => 'required|date',
'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',
'pending' => 'nullable|boolean',
'notes' => 'nullable|string',
'ip' => 'nullable|ip',
'invoices' => ['required','array',function ($attribute,$value,$fail) use ($request) {
if (collect($value)->sum('id') > $request->post('total_amt'))
$fail('Allocation is greater than payment total.');
}],
'invoices.*.id' => ['required',function ($attribute,$value,$fail) {
if (! Invoice::exists(str_replace(str_replace($attribute,'invoice\.','',),'.id','')))
$fail('Invoice doesnt exist in DB');
}],
]);
if (! $o->exists) {
$o->site_id = config('site')->site_id;
$o->active = TRUE;
}
$o->forceFill($request->only(['account_id','paid_at','checkout_id','total_amt','fees_amt','source_id','pending','notes','ip']));
$o->save();
foreach ($validation['invoices'] as $id => $amount) {
// See if we already have a payment item that we need to update
$items = $o->items->filter(function($item) use ($id) { return $item->invoice_id == $id; });
if ($items->count() == 1) {
$oo = $items->pop();
if ($amount['id'] == 0) {
$oo->delete();
continue;
}
} else {
$oo = new PaymentItem;
$oo->invoice_id = $id;
}
$oo->amount = ($oo->invoice->due >= 0) && ($oo->invoice->due-$amount['id'] >= 0) ? $amount['id'] : 0;
// If the amount is empty, ignore it.
if (! $oo->amount)
continue;
$oo->site_id = config('site')->site_id;
$oo->active = TRUE;
$o->items()->save($oo);
}
return redirect()
->back()
->with('success','Payment recorded: '.$o->id);
}
return view('theme.backend.adminlte.a.payment.addedit')
->with('o',$o);
}
/**
* List unapplied payments
*
* @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View
*/
// @todo Move to reseller
public function pay_unapplied()
{
return view('theme.backend.adminlte.a.payment.unapplied');
}
/**
* Show a list of invoices to apply payments to
*
* @param Request $request
* @param Account $o
* @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View
*/
// @todo Move to reseller
public function pay_invoices(Request $request,Account $o)
{
return view('theme.backend.adminlte.a.payment.widgets.invoices')
->with('pid',$request->pid)
->with('o',$o);
}
/** /**
* Site setup * Site setup
* *

View File

@ -2,8 +2,8 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\View\View;
use App\Http\Requests\CheckoutAddEdit; use App\Http\Requests\CheckoutAddEdit;
use App\Models\{Checkout,Invoice}; use App\Models\{Checkout,Invoice};
@ -11,75 +11,84 @@ use App\Models\{Checkout,Invoice};
class CheckoutController extends Controller class CheckoutController extends Controller
{ {
/** /**
* Update a suppliers details * Update a checkout details
* *
* @param CheckoutAddEdit $request * @param CheckoutAddEdit $request
* @param Checkout $o * @param Checkout $o
* @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View|\Illuminate\Http\RedirectResponse * @return RedirectResponse
*/ */
public function addedit(CheckoutAddEdit $request,Checkout $o) public function addedit(CheckoutAddEdit $request,Checkout $o): RedirectResponse
{ {
$this->middleware(['auth','wholesaler']); foreach ($request->validated() as $key => $item)
foreach ($request->except(['_token','active','submit']) as $key => $item)
$o->{$key} = $item; $o->{$key} = $item;
$o->active = (bool)$request->active; $o->active = (bool)$request->validated('active',FALSE);
try { try {
$o->save(); $o->save();
} catch (\Exception $e) { } catch (\Exception $e) {
return redirect()->back()->withErrors($e->getMessage())->withInput();
}
return redirect() return redirect()
->back() ->back()
->with('success','Payment saved'); ->withErrors($e->getMessage())->withInput();
} }
public function cart_invoice(Request $request,Invoice $o=NULL) return $o->wasRecentlyCreated
? redirect()
->to('a/checkout/'.$o->id)
->with('success','Checkout added')
: redirect()
->back()
->with('success','Checkout saved');
}
/**
* Add an invoice to the cart
*
* @param Request $request
* @param Invoice $o
* @return \Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View|\Illuminate\Foundation\Application
* @note The route validates that the user can see the invoice
*/
public function cart_invoice(Request $request,Invoice $o)
{ {
if ($o) {
$request->session()->put('invoice.cart.'.$o->id,$o->id); $request->session()->put('invoice.cart.'.$o->id,$o->id);
return view('theme.backend.adminlte.checkout.cart');
} }
if (! $request->session()->get('invoice.cart')) /**
* Remove an item from the cart
*
* @param Request $request
* @return string
*/
public function cart_remove(Request $request): string
{
if ($id=$request->post('id')) {
$cart = $request->session()->pull('invoice.cart');
unset($cart[$id]);
$request->session()->put('invoice.cart',$cart);
}
return '';
}
public function fee(Request $request): float
{
if ((! $request->post('checkout_id') || (! $request->post('total'))))
return 0;
$co = Checkout::findOrFail($request->post('checkout_id'));
return $co->fee($request->post('total'));
}
public function pay()
{
// @todo Currently sending all payments to paypal
return redirect() return redirect()
->to('u/home'); ->action([PaypalController::class,'authorise']);
return view('theme.backend.adminlte.u.invoice.cart')
->with('invoices',Invoice::find(array_values($request->session()->get('invoice.cart'))));
}
public function fee(Request $request,Checkout $o): float
{
return $o->fee($request->post('total',0));
}
/**
* Render a specific invoice for the user
*
* @return View
*/
public function home(): View
{
return view('theme.backend.adminlte.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('theme.backend.adminlte.payment.view',['o'=>$o]);
} }
} }

View File

@ -4,10 +4,11 @@ namespace App\Http\Controllers;
use Clarkeash\Doorman\Exceptions\{ExpiredInviteCode,InvalidInviteCode,NotYourInviteCode}; use Clarkeash\Doorman\Exceptions\{ExpiredInviteCode,InvalidInviteCode,NotYourInviteCode};
use Clarkeash\Doorman\Facades\Doorman; use Clarkeash\Doorman\Facades\Doorman;
use Illuminate\Http\Request;
use Illuminate\View\View; use Illuminate\View\View;
use Barryvdh\Snappy\Facades\SnappyPdf as PDF; use Barryvdh\Snappy\Facades\SnappyPdf as PDF;
use App\Models\Invoice; use App\Models\{Account,Invoice};
/** /**
* Class InvoiceController * Class InvoiceController
@ -19,6 +20,21 @@ use App\Models\Invoice;
*/ */
class InvoiceController extends Controller class InvoiceController extends Controller
{ {
/**
* Show a list of invoices to apply payments to
*
* @param Request $request
* @return \Illuminate\Contracts\View\View
*/
public function api_account_invoices(Request $request): \Illuminate\Contracts\View\View
{
session()->flashInput($request->post('old',[]));
return view('theme.backend.adminlte.payment.widget.invoices')
->with('pid',$request->post('pid'))
->with('o',Account::findOrFail($request->post('aid')));
}
/** /**
* Return the invoice in PDF format, ready to download * Return the invoice in PDF format, ready to download
* *

View File

@ -0,0 +1,73 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\RedirectResponse;
use Illuminate\Support\Arr;
use App\Http\Requests\PaymentAddEdit;
use App\Models\{Payment,PaymentItem};
class PaymentController extends Controller
{
/**
* Record payments on an account.
*
* @param PaymentAddEdit $request
* @param Payment $o
* @return RedirectResponse
*/
public function addedit(PaymentAddEdit $request,Payment $o): RedirectResponse
{
foreach (Arr::except($request->validated(),'invoices') as $k=>$v)
$o->{$k} = $v;
foreach ($request->validated('invoices',[]) as $id => $amount) {
// See if we already have a payment item that we need to update
$items = $o->items
->filter(fn($item)=>$item->invoice_id == $id);
if ($items->count() === 1) {
$oo = $items->pop();
if ($amount == 0) {
$oo->delete();
continue;
}
} else {
$oo = new PaymentItem;
$oo->invoice_id = $id;
}
$oo->amount = ($oo->invoice->due >= 0) && ($oo->invoice->due-$amount >= 0)
? $amount
: 0;
// If the amount is empty, ignore it.
if (! $oo->amount)
continue;
$oo->site_id = config('site')->site_id;
$oo->active = TRUE;
$o->items->push($oo);
}
try {
$o->pushNew();
} catch (\Exception $e) {
return redirect()
->back()
->withErrors($e->getMessage())->withInput();
}
return $o->wasRecentlyCreated
? redirect()
->to('r/payment/'.$o->id)
->with('success','Payment added')
: redirect()
->back()
->with('success','Payment saved');
}
}

View File

@ -2,8 +2,8 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use App\Models\PaymentItem;
use Carbon\Carbon; use Carbon\Carbon;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
use PayPalCheckoutSdk\Core\PayPalHttpClient; use PayPalCheckoutSdk\Core\PayPalHttpClient;
@ -13,14 +13,13 @@ use PayPalCheckoutSdk\Orders\OrdersCreateRequest;
use PayPalCheckoutSdk\Orders\OrdersCaptureRequest; use PayPalCheckoutSdk\Orders\OrdersCaptureRequest;
use PayPalHttp\HttpException; use PayPalHttp\HttpException;
use App\Models\Checkout; use App\Models\{Checkout,Invoice,Payment,PaymentItem};
use App\Models\Invoice;
use App\Models\Payment;
class PaypalController extends Controller class PaypalController extends Controller
{ {
private $client; private PayPalHttpClient $client;
private $o = NULL;
protected const cart_url = 'u/checkout/cart';
// Create a new instance with our paypal credentials // Create a new instance with our paypal credentials
public function __construct() public function __construct()
@ -31,27 +30,30 @@ class PaypalController extends Controller
$environment = new ProductionEnvironment(config('paypal.live_client_id'),config('paypal.live_secret')); $environment = new ProductionEnvironment(config('paypal.live_client_id'),config('paypal.live_secret'));
$this->client = new PayPalHttpClient($environment); $this->client = new PayPalHttpClient($environment);
$this->o = Checkout::where('name','paypal')->firstOrFail();
} }
public function cancel(Request $request) public function cancel()
{ {
return redirect()->to('u/invoice/cart'); return redirect()
->to(self::cart_url);
} }
/** /**
* Authorize a paypal payment, and redirect the user to pay. * Authorize a paypal payment, and redirect the user to pay.
* *
* @param Request $request * @return RedirectResponse
* @return \Illuminate\Http\RedirectResponse * @throws \PayPalHttp\IOException
*/ */
public function authorise(Request $request) public function authorise()
{ {
$co = Checkout::where('name','ilike','paypal')->firstOrFail();
$currency = 'AUD'; // @todo TO determine from DB.; $currency = 'AUD'; // @todo TO determine from DB.;
$cart = $request->session()->get('invoice.cart'); $cart = request()->session()->get('invoice.cart');
if (! $cart) if (! $cart)
return redirect()->to('u/home'); return redirect()
->to('u/home');
$invoices = Invoice::find($cart); $invoices = Invoice::find($cart);
@ -61,7 +63,7 @@ class PaypalController extends Controller
// Paypal Purchase Units // Paypal Purchase Units
$items = collect(); $items = collect();
foreach ($invoices as $io) { foreach ($invoices as $io) {
$fee = $this->o->fee($io->due,count($cart)); $fee = $co->fee($io->due,count($cart));
$total = round($io->due+$fee,2); $total = round($io->due+$fee,2);
$items->push([ $items->push([
@ -100,7 +102,7 @@ class PaypalController extends Controller
$data->put('application_context',[ $data->put('application_context',[
'return_url' => url('pay/paypal/capture'), 'return_url' => url('pay/paypal/capture'),
'cancel_url' => url('u/invoice/cart'), 'cancel_url' => url(self::cart_url),
]); ]);
$paypal->body = $data->toArray(); $paypal->body = $data->toArray();
@ -111,12 +113,16 @@ class PaypalController extends Controller
} catch (HttpException $e) { } catch (HttpException $e) {
Log::error('Paypal Exception',['request'=>$paypal,'response'=>$e->getMessage()]); Log::error('Paypal Exception',['request'=>$paypal,'response'=>$e->getMessage()]);
return redirect()->to('u/invoice/cart')->withErrors('Paypal Exception: '.$e->getCode()); return redirect()
->to(self::cart_url)
->withErrors('Paypal Exception: '.$e->getCode());
} catch (\HttpException $e) { } catch (\HttpException $e) {
Log::error('HTTP Exception',['request'=>$request,'response'=>$e->getMessage()]); Log::error('HTTP Exception',['request'=>$this->client,'response'=>$e->getMessage()]);
return redirect()->to('u/invoice/cart')->withErrors('HTTP Exception: '.$e->getCode()); return redirect()
->to(self::cart_url)
->withErrors('HTTP Exception: '.$e->getCode());
} }
// Get the approval link // Get the approval link
@ -128,18 +134,21 @@ class PaypalController extends Controller
} }
} }
if ($redirect_url) { if ($redirect_url)
return redirect()->away($redirect_url); return redirect()
} ->away($redirect_url);
return redirect()->to('u/invoice/cart')->withErrors('An error occurred with Paypal?'); return redirect()
->to(self::cart_url)
->withErrors('An error occurred with Paypal?');
} }
/** /**
* Capture a paypal payment * Capture a paypal payment
* *
* @param Request $request * @param Request $request
* @return \Illuminate\Http\RedirectResponse * @return RedirectResponse
* @throws \PayPalHttp\IOException
*/ */
public function capture(Request $request) public function capture(Request $request)
{ {
@ -179,23 +188,32 @@ class PaypalController extends Controller
if ($redirect_url) { if ($redirect_url) {
Log::error('Paypal Capture: Redirect back to Paypal.'); Log::error('Paypal Capture: Redirect back to Paypal.');
return redirect()->away($redirect_url); return redirect()
->away($redirect_url);
} }
return redirect()->to('u/invoice/cart')->withErrors('An error occurred with Paypal?'); return redirect()
->to(self::cart_url)
->withErrors('An error occurred with Paypal?');
} catch (\HttpException $e) { } catch (\HttpException $e) {
Log::error('HTTP Exception',['request'=>$paypal,'response'=>$e->getMessage()]); Log::error('HTTP Exception',['request'=>$paypal,'response'=>$e->getMessage()]);
return redirect()->to('u/invoice/cart')->withErrors('HTTP Exception: '.$e->getCode()); return redirect()
->to(self::cart_url)
->withErrors('HTTP Exception: '.$e->getCode());
} }
if (! $response OR ! $response->result->purchase_units) { if ((! $response) || (! $response->result->purchase_units)) {
Log::error('Paypal Capture: No Purchase Units?'); Log::error('Paypal Capture: No Purchase Units?');
return redirect()->to('u/invoice/cart')->withErrors('Paypal Exception: NPU'); return redirect()
->to(self::cart_url)
->withErrors('Paypal Exception: NPU');
} }
$co = Checkout::where('name','ilike','paypal')->firstOrFail();
// If we got here, we got a payment // If we got here, we got a payment
foreach ($response->result->purchase_units as $pu) { foreach ($response->result->purchase_units as $pu) {
foreach ($pu->payments->captures as $cap) { foreach ($pu->payments->captures as $cap) {
@ -219,7 +237,7 @@ class PaypalController extends Controller
} }
$po->paid_at = Carbon::parse($cap->create_time); $po->paid_at = Carbon::parse($cap->create_time);
$po->checkout_id = $this->o->id; $po->checkout_id = $co->id;
$po->checkout_data = $cap->id; $po->checkout_data = $cap->id;
list($account_id,$fee) = explode(':',$cap->custom_id); list($account_id,$fee) = explode(':',$cap->custom_id);
@ -246,7 +264,11 @@ class PaypalController extends Controller
} }
$request->session()->forget('invoice.cart'); $request->session()->forget('invoice.cart');
Log::info('Paypal Payment Recorded',['po'=>$po->id]); Log::info('Paypal Payment Recorded',['po'=>$po->id]);
return redirect()->to('u/home')->with('success','Payment recorded thank you.');
return redirect()
->to('u/home')
->with('success','Payment recorded thank you.');
} }
} }

View File

@ -90,7 +90,7 @@ class SearchController extends Controller
->whereIN('account_id',$account_ids) ->whereIN('account_id',$account_ids)
->limit(10)->get() as $o) ->limit(10)->get() as $o)
{ {
$result->push(['name'=>sprintf('%s: %s $%s',$o->lid,$o->account->name,number_format($o->total,2)),'value'=>'/a/payment/addedit/'.$o->id,'category'=>'Payments']); $result->push(['name'=>sprintf('%s: %s $%s',$o->lid,$o->account->name,number_format($o->total,2)),'value'=>'/r/payment/addedit/'.$o->id,'category'=>'Payments']);
} }
} }

View File

@ -3,7 +3,8 @@
namespace App\Http\Requests; namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest; use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Gate;
use Illuminate\Validation\Rule;
/** /**
* Editing Suppliers * Editing Suppliers
@ -17,7 +18,7 @@ class CheckoutAddEdit extends FormRequest
*/ */
public function authorize() public function authorize()
{ {
return Auth::user()->isWholesaler(); return Gate::allows('wholesaler');
} }
/** /**
@ -28,7 +29,13 @@ class CheckoutAddEdit extends FormRequest
public function rules() public function rules()
{ {
return [ return [
'name' => 'required|string|min:2|max:255', 'name' => [
'required',
'string',
'min:2',
'max:255',
Rule::unique('checkouts','name')->ignore($this->route('o')?->id),
],
'active' => 'sometimes|accepted', 'active' => 'sometimes|accepted',
'description' => 'nullable|string|min:2|max:255', 'description' => 'nullable|string|min:2|max:255',
]; ];

View File

@ -0,0 +1,62 @@
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Gate;
use App\Models\Invoice;
/**
* Editing Suppliers
*/
class PaymentAddEdit extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return Gate::allows('wholesaler');
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, mixed>
*/
public function rules()
{
return [
'account_id' => 'required|exists:accounts,id',
'paid_at' => 'required|date',
'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',
'pending' => 'nullable|boolean',
'notes' => 'nullable|string',
'ip' => 'nullable|ip',
'invoices' => [
'nullable',
'array',
function($attribute,$value,$fail) {
if (($x=collect($value)->sum()) > request()->post('total_amt'))
$fail(sprintf('Allocation %3.2f is greater than payment total %3.2f.',$x,request()->post('total_amt')));
}
],
'invoices.*' => [
'nullable',
function($attribute,$value,$fail) {
if (! ($x=Invoice::where('id',$xx=str_replace('invoices.','',$attribute))->first()))
$fail(sprintf('Invoice [%d] doesnt exist in DB',$xx));
// @todo The due amount may be influenced by this payment (ie: payment edit)
elseif($x->due < $value)
$fail(sprintf('Invoice [%d] is over allocated by %3.2f',$x->id,$value-$x->due));
}
],
];
}
}

View File

@ -6,9 +6,11 @@ use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Leenooks\Traits\ScopeActive; use Leenooks\Traits\ScopeActive;
use App\Traits\SiteID;
class Checkout extends Model class Checkout extends Model
{ {
use ScopeActive; use ScopeActive,SiteID;
protected $casts = [ protected $casts = [
'plugin_data'=>'json', 'plugin_data'=>'json',

View File

@ -300,8 +300,7 @@ class Invoice extends Model implements IDs
/** /**
* @param \Leenooks\Carbon $start Start Date * @param \Leenooks\Carbon $start Start Date
* @param Carbon $end End Date * @param Carbon $end End Date
* @param int $interval Period End Date * @param Collection $period
* @param bool $strict
* @return float * @return float
* @throws \Exception * @throws \Exception
*/ */
@ -373,7 +372,7 @@ class Invoice extends Model implements IDs
public function payments() public function payments()
{ {
return $this->hasManyThrough(Payment::class,PaymentItem::class,NULL,'id',NULL,'payment_id') return $this->hasManyThrough(Payment::class,PaymentItem::class,NULL,'id',NULL,'payment_id')
->where('active',TRUE); ->where('payments.active',TRUE);
} }
/** /**

View File

@ -7,8 +7,7 @@ use Illuminate\Support\Facades\DB;
use Leenooks\Traits\ScopeActive; use Leenooks\Traits\ScopeActive;
use App\Interfaces\IDs; use App\Interfaces\IDs;
use App\Traits\ProviderRef; use App\Traits\{ProviderRef,PushNew,SiteID};
use App\Traits\PushNew;
/** /**
* Class Payment * Class Payment
@ -25,7 +24,7 @@ use App\Traits\PushNew;
*/ */
class Payment extends Model implements IDs class Payment extends Model implements IDs
{ {
use PushNew,ScopeActive,ProviderRef; use PushNew,ScopeActive,ProviderRef,SiteID;
protected $casts = [ protected $casts = [
'paid_at'=>'datetime:Y-m-d', 'paid_at'=>'datetime:Y-m-d',
@ -106,7 +105,7 @@ class Payment extends Model implements IDs
->select(['payments.id','paid_at','account_id','checkout_id','total_amt',DB::raw("SUM(ABS(amount)) as allocated")]) ->select(['payments.id','paid_at','account_id','checkout_id','total_amt',DB::raw("SUM(ABS(amount)) as allocated")])
->leftJoin('payment_items',['payment_items.payment_id'=>'payments.id']) ->leftJoin('payment_items',['payment_items.payment_id'=>'payments.id'])
->groupBy(['payments.id','paid_at','total_amt','account_id','checkout_id']) ->groupBy(['payments.id','paid_at','total_amt','account_id','checkout_id'])
->having(DB::raw('ROUND(total_amt-IFNULL(allocated,0),2)'),'>',self::threshold); ->having(DB::raw('ROUND(CAST(total_amt-COALESCE(SUM(ABS(amount)),0) AS NUMERIC),2)'),'>',self::threshold);
} }
/* ATTRIBUTES */ /* ATTRIBUTES */

105
composer.lock generated
View File

@ -1534,16 +1534,16 @@
}, },
{ {
"name": "laravel/framework", "name": "laravel/framework",
"version": "v11.19.0", "version": "v11.20.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/laravel/framework.git", "url": "https://github.com/laravel/framework.git",
"reference": "5e103d499e9ee5bcfc184412d034c4e516b87085" "reference": "3cd7593dd9b67002fc416b46616f4d4d1da3e571"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/laravel/framework/zipball/5e103d499e9ee5bcfc184412d034c4e516b87085", "url": "https://api.github.com/repos/laravel/framework/zipball/3cd7593dd9b67002fc416b46616f4d4d1da3e571",
"reference": "5e103d499e9ee5bcfc184412d034c4e516b87085", "reference": "3cd7593dd9b67002fc416b46616f4d4d1da3e571",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -1736,7 +1736,7 @@
"issues": "https://github.com/laravel/framework/issues", "issues": "https://github.com/laravel/framework/issues",
"source": "https://github.com/laravel/framework" "source": "https://github.com/laravel/framework"
}, },
"time": "2024-07-30T15:22:41+00:00" "time": "2024-08-06T14:39:21+00:00"
}, },
{ {
"name": "laravel/intuit", "name": "laravel/intuit",
@ -1777,16 +1777,16 @@
}, },
{ {
"name": "laravel/passport", "name": "laravel/passport",
"version": "v12.2.1", "version": "v12.3.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/laravel/passport.git", "url": "https://github.com/laravel/passport.git",
"reference": "795bbb406c8f10167df6062032de803bd7d686f2" "reference": "ca63a86697a4fa091c7dcabe88ebba91d97c785d"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/laravel/passport/zipball/795bbb406c8f10167df6062032de803bd7d686f2", "url": "https://api.github.com/repos/laravel/passport/zipball/ca63a86697a4fa091c7dcabe88ebba91d97c785d",
"reference": "795bbb406c8f10167df6062032de803bd7d686f2", "reference": "ca63a86697a4fa091c7dcabe88ebba91d97c785d",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -1849,7 +1849,7 @@
"issues": "https://github.com/laravel/passport/issues", "issues": "https://github.com/laravel/passport/issues",
"source": "https://github.com/laravel/passport" "source": "https://github.com/laravel/passport"
}, },
"time": "2024-07-10T19:25:36+00:00" "time": "2024-08-05T13:44:51+00:00"
}, },
{ {
"name": "laravel/prompts", "name": "laravel/prompts",
@ -1911,26 +1911,27 @@
}, },
{ {
"name": "laravel/serializable-closure", "name": "laravel/serializable-closure",
"version": "v1.3.3", "version": "v1.3.4",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/laravel/serializable-closure.git", "url": "https://github.com/laravel/serializable-closure.git",
"reference": "3dbf8a8e914634c48d389c1234552666b3d43754" "reference": "61b87392d986dc49ad5ef64e75b1ff5fee24ef81"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/laravel/serializable-closure/zipball/3dbf8a8e914634c48d389c1234552666b3d43754", "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/61b87392d986dc49ad5ef64e75b1ff5fee24ef81",
"reference": "3dbf8a8e914634c48d389c1234552666b3d43754", "reference": "61b87392d986dc49ad5ef64e75b1ff5fee24ef81",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"php": "^7.3|^8.0" "php": "^7.3|^8.0"
}, },
"require-dev": { "require-dev": {
"nesbot/carbon": "^2.61", "illuminate/support": "^8.0|^9.0|^10.0|^11.0",
"nesbot/carbon": "^2.61|^3.0",
"pestphp/pest": "^1.21.3", "pestphp/pest": "^1.21.3",
"phpstan/phpstan": "^1.8.2", "phpstan/phpstan": "^1.8.2",
"symfony/var-dumper": "^5.4.11" "symfony/var-dumper": "^5.4.11|^6.2.0|^7.0.0"
}, },
"type": "library", "type": "library",
"extra": { "extra": {
@ -1967,7 +1968,7 @@
"issues": "https://github.com/laravel/serializable-closure/issues", "issues": "https://github.com/laravel/serializable-closure/issues",
"source": "https://github.com/laravel/serializable-closure" "source": "https://github.com/laravel/serializable-closure"
}, },
"time": "2023-11-08T14:08:06+00:00" "time": "2024-08-02T07:48:17+00:00"
}, },
{ {
"name": "laravel/socialite", "name": "laravel/socialite",
@ -3056,11 +3057,11 @@
}, },
{ {
"name": "leenooks/laravel", "name": "leenooks/laravel",
"version": "11.1.9", "version": "11.1.10",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://gitea.dege.au/laravel/leenooks.git", "url": "https://gitea.dege.au/laravel/leenooks.git",
"reference": "293d9913c65143f00e3051bff32d15c97644bd15" "reference": "92dd7ac3cb6598e55af3cbc95d9cf7af44318a30"
}, },
"type": "library", "type": "library",
"extra": { "extra": {
@ -3093,7 +3094,7 @@
"laravel", "laravel",
"leenooks" "leenooks"
], ],
"time": "2024-08-01T07:32:20+00:00" "time": "2024-08-10T12:25:03+00:00"
}, },
{ {
"name": "leenooks/passkey", "name": "leenooks/passkey",
@ -3474,20 +3475,20 @@
}, },
{ {
"name": "nette/utils", "name": "nette/utils",
"version": "v4.0.4", "version": "v4.0.5",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/nette/utils.git", "url": "https://github.com/nette/utils.git",
"reference": "d3ad0aa3b9f934602cb3e3902ebccf10be34d218" "reference": "736c567e257dbe0fcf6ce81b4d6dbe05c6899f96"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/nette/utils/zipball/d3ad0aa3b9f934602cb3e3902ebccf10be34d218", "url": "https://api.github.com/repos/nette/utils/zipball/736c567e257dbe0fcf6ce81b4d6dbe05c6899f96",
"reference": "d3ad0aa3b9f934602cb3e3902ebccf10be34d218", "reference": "736c567e257dbe0fcf6ce81b4d6dbe05c6899f96",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"php": ">=8.0 <8.4" "php": "8.0 - 8.4"
}, },
"conflict": { "conflict": {
"nette/finder": "<3", "nette/finder": "<3",
@ -3554,9 +3555,9 @@
], ],
"support": { "support": {
"issues": "https://github.com/nette/utils/issues", "issues": "https://github.com/nette/utils/issues",
"source": "https://github.com/nette/utils/tree/v4.0.4" "source": "https://github.com/nette/utils/tree/v4.0.5"
}, },
"time": "2024-01-17T16:50:36+00:00" "time": "2024-08-07T15:39:19+00:00"
}, },
{ {
"name": "nunomaduro/termwind", "name": "nunomaduro/termwind",
@ -8371,23 +8372,23 @@
}, },
{ {
"name": "nunomaduro/collision", "name": "nunomaduro/collision",
"version": "v8.3.0", "version": "v8.4.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/nunomaduro/collision.git", "url": "https://github.com/nunomaduro/collision.git",
"reference": "b49f5b2891ce52726adfd162841c69d4e4c84229" "reference": "e7d1aa8ed753f63fa816932bbc89678238843b4a"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/nunomaduro/collision/zipball/b49f5b2891ce52726adfd162841c69d4e4c84229", "url": "https://api.github.com/repos/nunomaduro/collision/zipball/e7d1aa8ed753f63fa816932bbc89678238843b4a",
"reference": "b49f5b2891ce52726adfd162841c69d4e4c84229", "reference": "e7d1aa8ed753f63fa816932bbc89678238843b4a",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"filp/whoops": "^2.15.4", "filp/whoops": "^2.15.4",
"nunomaduro/termwind": "^2.0.1", "nunomaduro/termwind": "^2.0.1",
"php": "^8.2.0", "php": "^8.2.0",
"symfony/console": "^7.1.2" "symfony/console": "^7.1.3"
}, },
"conflict": { "conflict": {
"laravel/framework": "<11.0.0 || >=12.0.0", "laravel/framework": "<11.0.0 || >=12.0.0",
@ -8395,13 +8396,13 @@
}, },
"require-dev": { "require-dev": {
"larastan/larastan": "^2.9.8", "larastan/larastan": "^2.9.8",
"laravel/framework": "^11.16.0", "laravel/framework": "^11.19.0",
"laravel/pint": "^1.16.2", "laravel/pint": "^1.17.1",
"laravel/sail": "^1.30.2", "laravel/sail": "^1.31.0",
"laravel/sanctum": "^4.0.2", "laravel/sanctum": "^4.0.2",
"laravel/tinker": "^2.9.0", "laravel/tinker": "^2.9.0",
"orchestra/testbench-core": "^9.2.1", "orchestra/testbench-core": "^9.2.3",
"pestphp/pest": "^2.34.9 || ^3.0.0", "pestphp/pest": "^2.35.0 || ^3.0.0",
"sebastian/environment": "^6.1.0 || ^7.0.0" "sebastian/environment": "^6.1.0 || ^7.0.0"
}, },
"type": "library", "type": "library",
@ -8464,7 +8465,7 @@
"type": "patreon" "type": "patreon"
} }
], ],
"time": "2024-07-16T22:41:01+00:00" "time": "2024-08-03T15:32:23+00:00"
}, },
{ {
"name": "phar-io/manifest", "name": "phar-io/manifest",
@ -8909,16 +8910,16 @@
}, },
{ {
"name": "phpunit/phpunit", "name": "phpunit/phpunit",
"version": "11.2.9", "version": "11.3.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git", "url": "https://github.com/sebastianbergmann/phpunit.git",
"reference": "c197bbaaca360efda351369bf1fd9cc1ca6bcbf7" "reference": "a8dce73a8938dfec7ac0daa91bdbcaae7d7188a3"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/c197bbaaca360efda351369bf1fd9cc1ca6bcbf7", "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/a8dce73a8938dfec7ac0daa91bdbcaae7d7188a3",
"reference": "c197bbaaca360efda351369bf1fd9cc1ca6bcbf7", "reference": "a8dce73a8938dfec7ac0daa91bdbcaae7d7188a3",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -8957,7 +8958,7 @@
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-main": "11.2-dev" "dev-main": "11.3-dev"
} }
}, },
"autoload": { "autoload": {
@ -8989,7 +8990,7 @@
"support": { "support": {
"issues": "https://github.com/sebastianbergmann/phpunit/issues", "issues": "https://github.com/sebastianbergmann/phpunit/issues",
"security": "https://github.com/sebastianbergmann/phpunit/security/policy", "security": "https://github.com/sebastianbergmann/phpunit/security/policy",
"source": "https://github.com/sebastianbergmann/phpunit/tree/11.2.9" "source": "https://github.com/sebastianbergmann/phpunit/tree/11.3.0"
}, },
"funding": [ "funding": [
{ {
@ -9005,7 +9006,7 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2024-07-30T11:09:23+00:00" "time": "2024-08-02T03:56:43+00:00"
}, },
{ {
"name": "sebastian/cli-parser", "name": "sebastian/cli-parser",
@ -10069,16 +10070,16 @@
}, },
{ {
"name": "spatie/flare-client-php", "name": "spatie/flare-client-php",
"version": "1.7.0", "version": "1.8.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/spatie/flare-client-php.git", "url": "https://github.com/spatie/flare-client-php.git",
"reference": "097040ff51e660e0f6fc863684ac4b02c93fa234" "reference": "180f8ca4c0d0d6fc51477bd8c53ce37ab5a96122"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/spatie/flare-client-php/zipball/097040ff51e660e0f6fc863684ac4b02c93fa234", "url": "https://api.github.com/repos/spatie/flare-client-php/zipball/180f8ca4c0d0d6fc51477bd8c53ce37ab5a96122",
"reference": "097040ff51e660e0f6fc863684ac4b02c93fa234", "reference": "180f8ca4c0d0d6fc51477bd8c53ce37ab5a96122",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -10096,7 +10097,7 @@
"phpstan/extension-installer": "^1.1", "phpstan/extension-installer": "^1.1",
"phpstan/phpstan-deprecation-rules": "^1.0", "phpstan/phpstan-deprecation-rules": "^1.0",
"phpstan/phpstan-phpunit": "^1.0", "phpstan/phpstan-phpunit": "^1.0",
"spatie/phpunit-snapshot-assertions": "^4.0|^5.0" "spatie/pest-plugin-snapshots": "^1.0|^2.0"
}, },
"type": "library", "type": "library",
"extra": { "extra": {
@ -10126,7 +10127,7 @@
], ],
"support": { "support": {
"issues": "https://github.com/spatie/flare-client-php/issues", "issues": "https://github.com/spatie/flare-client-php/issues",
"source": "https://github.com/spatie/flare-client-php/tree/1.7.0" "source": "https://github.com/spatie/flare-client-php/tree/1.8.0"
}, },
"funding": [ "funding": [
{ {
@ -10134,7 +10135,7 @@
"type": "github" "type": "github"
} }
], ],
"time": "2024-06-12T14:39:14+00:00" "time": "2024-08-01T08:27:26+00:00"
}, },
{ {
"name": "spatie/ignition", "name": "spatie/ignition",

11
public/css/fixes.css vendored
View File

@ -84,8 +84,17 @@ span.select2-selection.select2-selection--single > span.select2-selection__rende
width: 100%; width: 100%;
} }
/* Render the invalid red when a select container fails validation */ /* Render the invalid red when a select container fails vlidation */
.is-invalid + .select2-container--default .select2-selection--single, .is-invalid + .select2-container--default .select2-selection--single,
.is-invalid + .select2-container--default .select2-selection--multiple { .is-invalid + .select2-container--default .select2-selection--multiple {
border: 1px solid #dc3545; border: 1px solid #dc3545;
} }
/* When we have a hr after h3, show the line directly under the h3 */
h3:has(+hr) {
margin-bottom: 0;
}
h3 + hr {
margin-top: 0;
margin-bottom: 2em;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 MiB

After

Width:  |  Height:  |  Size: 310 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 MiB

After

Width:  |  Height:  |  Size: 181 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 186 KiB

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 MiB

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 848 KiB

After

Width:  |  Height:  |  Size: 122 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 183 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 MiB

After

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 617 KiB

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 222 KiB

After

Width:  |  Height:  |  Size: 168 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 442 KiB

After

Width:  |  Height:  |  Size: 264 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

After

Width:  |  Height:  |  Size: 436 KiB

View File

@ -1,254 +0,0 @@
@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">
<!-- DATE RECEIVED -->
<div class="col-4">
<div class="form-group has-validation">
<label for="paid_at">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('paid_at') is-invalid @enderror" id="paid_at" name="paid_at" value="{{ old('paid_at',($o->exists ? $o->paid_at : \Carbon\Carbon::now())->format('Y-m-d')) }}" required>
<span class="invalid-feedback" role="alert">
@error('paid_at')
{{ $message }}
@else
Payment Date is required.
@enderror
</span>
</div>
<span class="input-helper">Date Payment Received.</span>
</div>
</div>
<!-- AMOUNT -->
<div class="offset-4 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="text-right form-control @error('total_amt') is-invalid @enderror" id="total_amt" name="total_amt" value="{{ number_format(old('total_amt',$o->exists ? $o->total_amt : 0),2) }}" 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">
<!-- METHOD -->
<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>
<!-- PAYMENT FEE -->
<div class="offset-4 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="text-right form-control @error('fees_amt') is-invalid @enderror" id="fees_amt" name="fees_amt" value="{{ number_format(old('fees_amt',$o->exists ? $o->fees_amt : 0),2) }}">
<span class="invalid-feedback" role="alert">
@error('fees_amt')
{{ $message }}
@else
Total Fees is required.
@enderror
</span>
</div>
<span class="input-helper">Amount Received.</span>
</div>
</div>
</div>
<div class="row">
<!-- ACCOUNT -->
<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()->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 payment to.</span>
<i class="fas fa-spinner d-none"></i>
</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" id="balance" value="{{ number_format($o->exists ? $o->balance : 0,2) }}" disabled>
</div>
</div>
</div>
<div class="row mt-3 mb-3">
<div class="col-12">
<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(select2)
@js(select2,autofocus)
<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,
data: {pid:{{ $o->id ?: 'null' }}},
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);
}
});
}
function balance() {
var alloc = 0;
$('.invoice').each(function() {
alloc += parseFloat($(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');
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);
});
$('#total_amt').on('change',balance);
});
$(document).on('change','.invoice',balance);
</script>
@append

View File

@ -1,71 +0,0 @@
@extends('adminlte::layouts.app')
@section('htmlheader_title')
Service #{{ $o->id }}
@endsection
@section('contentheader_title')
Service #
@endsection
@section('contentheader_description')
{{ $o->service_id }}
@endsection
@section('main-content')
<div class="col-md-12">
<div class="box box-primary">
<form role="form" method="POST" enctype="multipart/form-data">
{{ csrf_field() }}
<div class="box-header with-border">
<h3 class="box-title">Service Information</h3>
</div>
<div class="box-body">
<div class="col-md-3">
@switch($o->order_status)
@case('ORDER-SUBMIT')
@include('theme.backend.adminlte.a.widgets.service.order.submit')
@break
@case('ORDER-HOLD')
@case('ORDER-SENT')
@case('ORDERED')
@include('theme.backend.adminlte.a.widgets.service.order.sent')
@break
@default
@include('theme.backend.adminlte.u.widgets.service.info')
@endswitch
</div>
@if($o->order_status == 'ORDER-REQUEST')
<div class="col-md-3">
@include('theme.backend.adminlte.a.widgets.service_order-request')
</div>
@endif
</div>
<div class="box-footer">
@switch($o->order_status)
@case('ORDER-SUBMIT')
<button type="submit" class="btn btn-info btn-danger" name="action" value="reject">Reject</button>
<button type="submit" class="btn btn-info btn-danger" name="action" value="hold">Hold</button>
<button type="submit" class="btn btn-info btn-warning" name="action" value="approve">Approve</button>
@break;
@case('ORDER-HOLD')
<button type="submit" class="btn btn-info btn-warning" name="action" value="release">Release</button>
@case('ORDER-SENT')
@case('ORDERED')
<button type="submit" class="btn btn-info btn-danger" name="action" value="update_reference">Update</button>
@break;
@endswitch
{{-- <button type="submit" class="btn btn-info" name="action" value="save">Back</button> --}}
</div>
</form>
</div>
</div>
@endsection

View File

@ -0,0 +1,140 @@
@use(App\Models\Checkout)
@use(App\Models\Invoice)
@extends('adminlte::layouts.app')
@section('htmlheader_title')
Payment Cart
@endsection
@section('page_title')
Payments
@endsection
@section('contentheader_title')
Payment Cart
@endsection
@section('contentheader_description')
@endsection
@section('main-content')
<div class="row">
<div class="col-4">
<div class="card card-dark">
<div class="card-header p-2">
<span class="card-title">Invoices to Pay</span>
</div>
<div class="card-body">
<form method="POST" action="{{ url('u/checkout/pay') }}">
@csrf
<input type="hidden" name="type" value="invoice">
<!-- @todo This is currently forcing only paypal -->
<x-leenooks::form.select id="checkout_id" name="checkout_id" icon="fa-credit-card" label="Payment Method" feedback="Payment Method is required" choose="true" :options="Checkout::active()->where('name','ilike','paypal')->orderBy('name')->get()->map(function ($item) { $item->value = $item->name; return $item; })"/>
<table id="invoices" class="table table-sm w-100">
<tr>
<th>Invoice</th>
<th class="text-right">Balance Due</th>
</tr>
@foreach (($invoices=Invoice::whereIn('id',session('invoice.cart',[]))->get()) as $io)
<input type="hidden" name="invoice_id[]" value="{{ $io->id }}">
<tr>
<td><a class="cart_delete text-dark" data-id="{{ $io->id }}" data-value="{{ $io->due }}" href="{{ url('/u/cart/delete',$io->id) }}"><i class="fas fa-trash-alt"></i></a> {{ $io->sid }}</td>
<td class="text-right">{{ number_format($io->due,2) }}</td>
</tr>
@endforeach
<tfoot>
<tr>
<th class="text-right">Sub Total</th>
<td class="text-right"><span id="subtotal">{{ number_format($invoices->sum('due'),2) }}</span></td>
</tr>
<tr>
<th class="text-right">Payment Fees</th>
<td class="text-right"><span id="payfee">TBA</span></td>
</tr>
<tr>
<th class="text-right">Payment Total</th>
<th class="text-right"><span id="paytotal">TBA</span></th>
</tr>
<tr>
<th colspan="2">
<!-- Buttons -->
<x-leenooks::button.cancel/>
<x-leenooks::button.submit class="float-right" disabled>Pay</x-leenooks::button.submit>
<a href="{{ url('/home') }}" class="mt-4 btn btn-sm btn-primary">Add Invoice</a>
</th>
</tr>
</tfoot>
</table>
</form>
</div>
</div>
</div>
</div>
<x-leenooks::errors/>
@endsection
@section('page-scripts')
<script type="text/javascript">
var subtotal = {{ $invoices->sum('due') }};
function fee_total() {
$.ajax({
type: 'POST',
data: {
total: subtotal,
checkout_id: $('#checkout_id').val()
},
dataType: 'json',
url: '{{ url('u/checkout/fee') }}',
timeout: 5000,
error: function() {
alert('Failed to submit, please try again...');
},
success: function(data) {
$('span[id=payfee]').html(data.toFixed(2));
$('span[id=paytotal]').html((subtotal+data).toFixed(2));
$('button[type=submit]').prop('disabled',false);
}
});
}
$(document).ready(function() {
if ($('#checkout_id').val())
fee_total();
$('#checkout_id').on('change',fee_total);
$('.cart_delete').click(function(item) {
var that = $(this);
// Delete
$.ajax({
url: '{{ url('u/checkout/cart/remove') }}',
method: 'POST',
data: {id: that.data('id')},
}).done(function(data) {
that.closest('tr').hide();
that.closest('tr').parent().find('input').attr('disabled',true);
subtotal -= that.data('value');
$('span[id=subtotal]').html(subtotal.toFixed(2));
if ($('#checkout_id').val())
fee_total();
}).fail(function(data) {
alert('Hmm, that didnt work?');
});
// Clear the data cache
that.removeData();
return false;
});
});
</script>
@append

View File

@ -0,0 +1,52 @@
@use(App\Models\Checkout)
@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">
<div class="row">
<div class="col-12 col-lg-4">
<x-leenooks::form.select id="checkout_id" name="checkout_id" icon="fa-credit-card" label="Payment Name" feedback="Payment Name is required" addnew="New Payment" groupby="active" :options="Checkout::orderBy('active','DESC')->orderBy('name')->get()->map(function ($item) { $item->value = $item->name; return $item; })"/>
</div>
</div>
</div>
</div>
</div>
</div>
@endsection
@pa(select2,autofocus)
@section('page-scripts')
<script type="text/javascript">
$(document).ready(function() {
$('#checkout_id').select2()
.on('change',function(item) {
if (! item.target.value)
return false;
window.location.href = '{{ url('a/checkout') }}'+(item.target.value ? '/'+item.target.value : '');
});
});
</script>
@append

View File

@ -0,0 +1,37 @@
<!-- $co=Checkout::class -->
@extends('adminlte::layouts.app')
@section('htmlheader_title')
{{ $co->name ?? 'New Checkout' }}
@endsection
@section('page_title')
{{ $co->name ?? 'New Checkout' }}
@endsection
@section('contentheader_title')
{{ $co->name ?? 'New Checkout' }}
@endsection
@section('contentheader_description')
@endsection
@section('main-content')
<div class="row">
<div class="col">
<div class="card">
<div class="card-header bg-dark 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('theme.backend.adminlte.checkout.widget.detail',['o'=>$co ?? NULL])
</div>
</div>
</div>
</div>
</div>
</div>
@endsection

View File

@ -0,0 +1,37 @@
<!-- $o=Checkout::class -->
<div class="row">
<div class="col">
<h3>Checkout Details <x-leenooks::button.success class="float-right"/></h3>
<hr>
<form method="POST" action="{{ url('a/checkout',$o?->id ?? '') }}">
@csrf
<div class="row">
<!-- Checkout Name -->
<div class="col-10 col-md-6">
<x-leenooks::form.text name="name" icon="fa-shopping-cart" label="Checkout Name" placeholder="{{ $o?->name ?: 'Payment Name' }}" feedback="Payment Name is required" :value="$o?->name"/>
</div>
<!-- Checkout Active -->
<div class="col-2">
<x-leenooks::form.toggle name="active" label="Active" :value="$o?->active"/>
</div>
</div>
<div class="row">
<!-- Description -->
<div class="col">
<x-leenooks::form.text name="description" label="Description" :value="$o?->description"/>
</div>
</div>
<div class="row">
<!-- Buttons -->
<div class="col">
<x-leenooks::button.reset/>
<x-leenooks::button.submit class="float-right">@if($o?->exists)Update @else Add @endif</x-leenooks::button.submit>
</div>
</div>
</form>
</div>
</div>

View File

@ -18,9 +18,7 @@
@endsection @endsection
@section('main-content') @section('main-content')
<!-- Main content -->
<div class="invoice p-3 mb-3"> <div class="invoice p-3 mb-3">
<!-- title row -->
<div class="row"> <div class="row">
<div class="col-8"> <div class="col-8">
<h2> <h2>
@ -31,10 +29,8 @@
<div class="col-4 text-right"> <div class="col-4 text-right">
<h1 class="text-uppercase">Tax Invoice</h1> <h1 class="text-uppercase">Tax Invoice</h1>
</div> </div>
<!-- /.col -->
</div> </div>
<!-- info row -->
<div class="row invoice-info"> <div class="row invoice-info">
<div class="col-4 invoice-col"> <div class="col-4 invoice-col">
<address> <address>
@ -84,12 +80,10 @@
</table> </table>
</div> </div>
</div> </div>
<!-- /.row -->
<!-- Table row -->
<div class="row"> <div class="row">
<div class="col-12 table-responsive"> <div class="col">
<table class="table table-striped table-hover"> <table class="table table-responsive table-striped table-hover">
<thead> <thead>
<tr> <tr>
<th>Qty</th> <th>Qty</th>
@ -144,15 +138,11 @@
</tbody> </tbody>
</table> </table>
</div> </div>
<!-- /.col -->
</div> </div>
<!-- /.row -->
<!-- padding -->
<div class="row pb-5"></div> <div class="row pb-5"></div>
<div class="row"> <div class="row">
<!-- accepted payments column -->
<div class="col-6"> <div class="col-6">
<p class="lead">Payment Methods:</p> <p class="lead">Payment Methods:</p>
@ -172,7 +162,6 @@
</p> </p>
</div> </div>
<!-- /.col -->
<div class="ml-auto col-4"> <div class="ml-auto col-4">
<table class="table"> <table class="table">
<tr> <tr>
@ -213,18 +202,14 @@
@endif @endif
</table> </table>
</div> </div>
<!-- /.col -->
</div> </div>
<!-- /.row -->
<!-- this row will not appear when printing --> <!-- this row will not appear when printing -->
<div class="row d-print-none"> <div class="row d-print-none">
<div class="col-12"> <div class="col-12">
<a href="javascript:window.print();" class="btn btn-default"><i class="fas fa-print"></i> Print</a> <button type="button" id="print" class="btn btn-default"><i class="fas fa-print"></i> Print</button>
@if($o->id) @if($o->id)
<a href="{{ url('u/invoice/cart',$o->id) }}" class="btn btn-success float-right"> <a href="{{ url('u/checkout/cart',$o->id) }}" class="btn btn-success float-right"><i class="fas fa-credit-card"></i> Pay</a>
<i class="fas fa-credit-card"></i> Pay
</a>
{{-- {{--
<a href="{{ url(sprintf('u/invoice/%s/pdf',$o->id)) }}" class="btn btn-primary float-right mr-2"> <a href="{{ url(sprintf('u/invoice/%s/pdf',$o->id)) }}" class="btn btn-primary float-right mr-2">
<i class="fas fa-download"></i> Download PDF <i class="fas fa-download"></i> Download PDF
@ -234,7 +219,23 @@
</div> </div>
</div> </div>
</div> </div>
<!-- /.content -->
<div class="clearfix"></div>
@endsection @endsection
@section('page-styles')
<style media="print">
/* Dont show URL and date in print output */
@page {
size: auto; /* auto is the initial value */
margin: 0; /* this affects the margin in the printer settings */
}
</style>
@append
@section('page-scripts')
<script type="text/javascript">
$(document).ready(function() {
$('#print').on('click',function() {
window.print();
})
});
</script>
@append

View File

@ -1,73 +0,0 @@
@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

@ -1,3 +1,5 @@
@use(App\Models\Payment)
@extends('adminlte::layouts.app') @extends('adminlte::layouts.app')
@section('htmlheader_title') @section('htmlheader_title')
@ -15,7 +17,7 @@
@section('main-content') @section('main-content')
<div class="row"> <div class="row">
<div class="col-6"> <div class="col">
<div class="card"> <div class="card">
<div class="card-body"> <div class="card-body">
<table class="table table-striped table-hover" id="unapplied_payments"> <table class="table table-striped table-hover" id="unapplied_payments">
@ -32,10 +34,10 @@
</thead> </thead>
<tbody> <tbody>
@foreach(\App\Models\Payment::active()->unapplied()->with(['account.user','checkout','items'])->get() as $o) @foreach(Payment::active()->unapplied()->with(['account.user','checkout','items'])->get() as $o)
@if (! $o->balance) @continue @endif @continue(! $o->balance)
<tr> <tr>
<td><a href="{{ url('a/payment/addedit',$o->id) }}">{{ $o->id }}</td> <td><a href="{{ url('r/payment',$o->id) }}">{{ $o->id }}</td>
<td>{{ $o->paid_at->format('Y-m-d') }}</td> <td>{{ $o->paid_at->format('Y-m-d') }}</td>
<td>{{ $o->account->name }}</td> <td>{{ $o->account->name }}</td>
<td>{{ $o->checkout->name }}</td> <td>{{ $o->checkout->name }}</td>
@ -52,14 +54,17 @@
</div> </div>
@endsection @endsection
@section('page-scripts') @pa(datatables,rowgroup|conditionalpaging)
@css(datatables,bootstrap4)
@js(datatables,bootstrap4)
@section('page-scripts')
<script type="text/javascript"> <script type="text/javascript">
$(document).ready(function() { $(document).ready(function() {
$('#unapplied_payments').DataTable( { $('#unapplied_payments').DataTable({
order: [1,'desc'], order: [1,'desc'],
rowGroup: {
dataSrc: 2,
},
conditionalPaging: true,
}); });
}); });
</script> </script>

View File

@ -1,38 +1,167 @@
<!-- $o = Checkout::class --> <!-- po=Payment::class -->
@use(Carbon\Carbon)
@use(App\Models\Account)
@use(App\Models\Checkout)
@extends('adminlte::layouts.app') @extends('adminlte::layouts.app')
@section('htmlheader_title') @section('htmlheader_title')
{{ $o->name ?: 'New Payment' }} Payment
@endsection @endsection
@section('page_title') @section('page_title')
{{ $o->name ?: 'New Payment' }} Payment
@endsection @endsection
@section('contentheader_title') @section('contentheader_title')
{{ $o->name ?: 'New Payment' }} Record Payment
@endsection @endsection
@section('contentheader_description') @section('contentheader_description')
@include('adminlte::widget.status')
@endsection @endsection
@section('main-content') @section('main-content')
<div class="card card-dark">
<div class="card-body">
<form method="POST" action="{{ url('r/payment',$po?->id ?? '') }}">
@csrf
<x-leenooks::button.success class="float-right" row="true"/>
<div class="row"> <div class="row">
<div class="col-12"> <div class="col-12 col-sm-6 col-lg-3">
<div class="card"> <div class="row">
<div class="card-header bg-dark d-flex p-0"> <div class="col-12 col-md-9 col-lg-12">
<ul class="nav nav-pills w-100 p-2"> <!-- Account -->
<li class="nav-item"><a class="nav-link active" href="#details" data-toggle="tab">Detail</a></li> <!-- @todo Only show active accounts or accounts with outstanding invoices -->
</ul> <x-leenooks::form.select name="account_id" icon="fa-user" label="Account" feedback="Sweep Type is required" helper="Account to add payment to." :options="Account::active()->with(['user'])->get()->sortBy('name')->map(fn($item,$key)=>['id'=>$item->id,'value'=>$item->name])" :value="$po?->account_id ?? ''"/>
</div>
</div> </div>
<div class="card-body"> <div class="row">
<div class="tab-content"> <div class="col-12 col-md-9 col-lg-12">
<div class="tab-pane fade active show" id="details" role="tabpanel"> <!-- Received -->
@include('theme.backend.adminlte.payment.widget.detail') <x-leenooks::form.date name="paid_at" icon="fa-calendar" label="Date Received" feedback="Payment Date is required" helper="Date payment received" :value="($po?->paid_at ?? Carbon::now())->format('Y-m-d')"/>
</div>
</div>
<!-- METHOD -->
<div class="row">
<div class="col-12 col-md-9 col-lg-12">
<x-leenooks::form.select name="checkout_id" icon="fa-dollar-sign" label="Payment Method" :options="Checkout::orderBy('name')->get()->map(fn($item,$key)=>['id'=>$item->id,'value'=>$item->name])" :value="$po?->checkout_id ?? ''"/>
</div>
</div>
</div>
<div class="col-12 col-sm-6 col-log-3">
<div class="row">
<div class="col-12 col-md-6">
<!-- Amount -->
<x-leenooks::form.text class="text-right" id="total_amt" name="total_amt" icon="fa-dollar-sign" label="Amount" helper="Amount received" :value="$po?->total_amt ?? 0"/>
</div>
</div>
<div class="row">
<div class="col-12 col-md-6">
<!-- Payment Fee -->
<x-leenooks::form.text class="text-right" id="fees_amt" name="fees_amt" icon="fa-dollar-sign" label="Fee" helper="Payment fees" :value="$po?->fees_amt ?? ''"/>
</div>
</div>
<div class="row">
<div class="col-12 col-md-6">
<!-- Balance -->
<x-leenooks::form.text class="text-right" id="balance" name="balance" icon="fa-dollar-sign" label="Balance" helper="Payment unallocated" :value="$po?->total ?? 0" disabled/>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div class="row mt-3 mb-3">
<div class="col-12">
<div id="invoices"></div>
@error('invoices')
<span class="invalid-feedback d-block mt-2 mb-2">
{{ $message }}
</span>
@enderror
</div> </div>
</div> </div>
<div class="row">
<!-- Buttons -->
<div class="col">
<x-leenooks::button.cancel/>
<x-leenooks::button.submit class="float-right">@if($po?->exists ?? FALSE)Update @else Add @endif</x-leenooks::button.submit>
</div>
</div>
</form>
</div>
</div>
<x-leenooks::errors/>
@endsection @endsection
@section('page-scripts')
<script type="text/javascript">
function populate(account,spinner) {
spinner.toggleClass('d-none').toggleClass('fa-spin');
$.ajax({
type: 'POST',
dataType: 'html',
cache: false,
url: '{{ url('r/account/invoices') }}',
data: {
aid: account,
pid: {{ $po?->id ?? 'null' }},
errors: {!! $errors !!},
old: {!! json_encode(old()) !!}
},
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);
}
});
}
function balance() {
var alloc = 0;
$('input[id^=invoices_]').each(function() {
alloc += parseFloat($(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');
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);
});
$('#total_amt').on('change',balance);
});
$(document).on('change','input[id^=invoices_]',balance);
</script>
@append

View File

@ -1,70 +0,0 @@
<!-- $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

@ -1,9 +1,10 @@
<div class="form-group mb-0"> <!-- $o=Account::class -->
<label for="checkout_id">Invoices Due</label> <div>
<label>Invoices Due</label>
@if(($x=$o->invoices() @if(($x=$o->invoices()
->where('active',TRUE) ->active()
->orderBy('due_at') ->orderBy('due_at')
->with(['items.taxes','payment_items.payment','account']) ->with(['items.taxes','payments','account'])
->get() ->get()
->filter(function($item) use ($pid) { return $item->due > 0 || $item->payments->search(function($item) use ($pid) { return $item->id == $pid; }) !== FALSE; }))->count()) ->filter(function($item) use ($pid) { return $item->due > 0 || $item->payments->search(function($item) use ($pid) { return $item->id == $pid; }) !== FALSE; }))->count())
<table class="table table-hover"> <table class="table table-hover">
@ -27,7 +28,7 @@
<td>{{ number_format($io->total,2) }}</td> <td>{{ number_format($io->total,2) }}</td>
<td>{{ number_format($io->due,2) }}</td> <td>{{ number_format($io->due,2) }}</td>
<td class="text-right"> <td class="text-right">
<input type="text" class="text-right invoice" name="invoices[{{ $io->id }}][id]" value="{{ number_format(($x=$io->payment_items->filter(function($item) use ($pid) { return $item->payment_id == $pid; })) ? $x->sum('amount') : 0,2) }}"> <x-leenooks::form.text class="text-right" id="invoices_{{$io->id}}" name="invoices[{{ $io->id }}]" icon="fa-dollar-sign" old="invoices.{{$io->id}}" :value="number_format(($x=$io->payment_items->filter(fn($item)=>$item->payment_id == $pid)) ? $x->sum('amount') : 0,2)"/>
</td> </td>
</tr> </tr>
@endforeach @endforeach

View File

@ -1,109 +0,0 @@
@extends('adminlte::layouts.app')
@section('htmlheader_title')
Payment Cart
@endsection
@section('page_title')
Payments
@endsection
@section('contentheader_title')
Payment Cart
@endsection
@section('contentheader_description')
@endsection
@section('main-content')
<div class="row">
<div class="col-4">
<div class="card card-dark">
<div class="card-header">
<span class="card-title">Invoices to Pay</span>
</div>
<div class="card-body">
<form method="POST" action="{{ url('u/checkout/pay') }}">
@csrf
<input type="hidden" name="type" value="invoice">
<div class="input-group mb-5">
<div class="input-group-prepend">
<span class="input-group-text">Payment Method</span>
</div>
<select class="form-control" id="paymethod" name="checkout_id[]" required>
<option></option>
@foreach (\App\Models\Checkout::active()->orderBy('name')->get() as $oo)
<option value="{{ $oo->id }}">{{ $oo->name }}</option>
@endforeach
</select>
</div>
<table id="invoices" class="table table-sm w-100">
<tr>
<th>Invoice</th>
<th class="text-right">Balance Due</th>
</tr>
@foreach ($invoices as $io)
<input type="hidden" name="invoice_id[]" value="{{ $io->id }}">
<tr>
<td>{{ $io->sid }}</td>
<td class="text-right">{{ number_format($io->due,2) }}</td>
</tr>
@endforeach
<tfoot>
<tr>
<th class="text-right">Sub Total</th>
<td class="text-right">{{ number_format($invoices->sum('due'),2) }}</td>
</tr>
<tr>
<th class="text-right">Payment Fees</th>
<td class="text-right"><span id="payfee">TBA</span></td>
</tr>
<tr>
<th class="text-right">Payment Total</th>
<th class="text-right"><span id="paytotal">TBA</span></th>
</tr>
<tr>
<th colspan="2">
<input type="submit" class="btn btn-dark mt-2" name="pay" value="Cancel">
<input type="submit" class="btn btn-success mt-2 float-right" name="pay" value="Submit" disabled>
<a href="{{ url('/home') }}" class="btn btn-danger mt-2 float-right mr-2">Add Invoice</a>
</th>
</tr>
</tfoot>
</table>
</form>
</div>
</div>
</div>
</div>
@endsection
@section('page-scripts')
<script>
$(document).ready(function() {
$('#paymethod').on('change',function(item) {
$.ajax({
type: "POST",
data: {total: {{ $invoices->sum('due') }},count: {{ $invoices->count() }} },
dataType: "json",
cache: true,
url: '{{ url('api/u/checkout/fee') }}'+'/'+$(this).val(),
timeout: 25000,
error: function(x) {
alert("Failed to submit, please try again...");
},
success: function(data) {
$("span[id=payfee]").html(data.toFixed(2));
$("span[id=paytotal]").html(({{ $invoices->sum('due') }}+data).toFixed(2));
$("input[type=submit]").prop('disabled',false);
}
});
});
});
</script>
@append

View File

@ -24,21 +24,21 @@
@can('wholesaler') @can('wholesaler')
<!-- PAYMENTS --> <!-- PAYMENTS -->
<li @class(['nav-item','has-treeview','menu-open'=>$x=preg_match('#^a/payment/#',$path),'menu-closed'=>! $x])> <li @class(['nav-item','has-treeview','menu-open'=>$x=preg_match('#^r/payment/#',$path),'menu-closed'=>! $x])>
<a href="#" @class(['nav-link','active'=>preg_match('#^a/payment/#',$path)])> <a href="#" @class(['nav-link','active'=>preg_match('#^r/payment/#',$path)])>
<i class="nav-icon fas fa-receipt"></i> <i class="nav-icon fas fa-receipt"></i>
<p>Payments <i class="fas fa-angle-left right"></i></p> <p>Payments <i class="fas fa-angle-left right"></i></p>
</a> </a>
<ul class="nav nav-treeview"> <ul class="nav nav-treeview">
<li class="nav-item"> <li class="nav-item">
<a href="{{ url('a/payment/addedit') }}" @class(['nav-link','active'=>$path === 'a/payment/addedit'])> <a href="{{ url('r/payment/new') }}" @class(['nav-link','active'=>$path === 'r/payment/new'])>
<i class="fas fa-money-bill nav-icon"></i> <p>New Payment</p> <i class="fas fa-money-bill nav-icon"></i> <p>New Payment</p>
</a> </a>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<a href="{{ url('a/payment/unapplied') }}" @class(['nav-link','active'=>$path === 'a/payment/unapplied'])> <a href="{{ url('r/payment/unapplied') }}" @class(['nav-link','active'=>$path === 'r/payment/unapplied'])>
<i class="fas fa-receipt nav-icon"></i> <p>Unapplied</p> <i class="fas fa-receipt nav-icon"></i> <p>Unapplied</p>
</a> </a>
</li> </li>
@ -64,7 +64,7 @@
<!-- CHECKOUT (PAYMENTS) --> <!-- CHECKOUT (PAYMENTS) -->
<li class="nav-item"> <li class="nav-item">
<a href="{{ url('a/checkout') }}" @class(['nav-link','active'=>preg_match('#^a/checkout#',$path)])> <a href="{{ url('a/checkout') }}" @class(['nav-link','active'=>preg_match('#^a/checkout#',$path)])>
<i class="nav-icon fas fa-money-check-alt"></i> <p>Payments</p> <i class="nav-icon fas fa-money-check-alt"></i> <p>Checkout</p>
</a> </a>
</li> </li>

View File

@ -24,17 +24,7 @@ Route::group(['middleware'=>['auth:api','role:wholesaler']], function() {
Route::get('a/supplier_products',[ProductController::class,'api_supplier_products']); Route::get('a/supplier_products',[ProductController::class,'api_supplier_products']);
}); });
// Reseller API calls
Route::group(['middleware'=>['auth:api','role:reseller']], function() {
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() { Route::group(['middleware'=>'auth:api'], function() {
Route::post('/u/checkout/fee/{o}',[CheckoutController::class,'fee'])
->where('o','[0-9]+');
Route::any('/intuit/accounting/list',[AccountingController::class,'list']); Route::any('/intuit/accounting/list',[AccountingController::class,'list']);
}); });

View File

@ -12,6 +12,7 @@ use App\Http\Controllers\{AdminController,
HomeController, HomeController,
InvoiceController, InvoiceController,
OrderController, OrderController,
PaymentController,
PaypalController, PaypalController,
ProductController, ProductController,
SearchController, SearchController,
@ -77,13 +78,15 @@ Route::group(['middleware'=>['auth','role:wholesaler'],'prefix'=>'a'],function()
->where('so','[0-9]+'); ->where('so','[0-9]+');
// Site Setup // Site Setup
Route::view('setup','theme.backend.adminlte.a.setup'); Route::view('setup','theme.backend.adminlte.setup');
Route::post('setup',[AdminController::class,'setup']); Route::post('setup',[AdminController::class,'setup']);
// Checkout Setup (Payments) // Checkout Setup (Payments)
Route::get('checkout',[CheckoutController::class,'home']); Route::model('co',\App\Models\Checkout::class);
Route::get('checkout/{o?}',[CheckoutController::class,'view']) Route::view('checkout','theme.backend.adminlte.checkout.choose');
->where('o','[0-9]+'); Route::view('checkout/new','theme.backend.adminlte.checkout.view');
Route::view('checkout/{co}','theme.backend.adminlte.checkout.view')
->where('co','[0-9]+');
Route::post('checkout/{o?}',[CheckoutController::class,'addedit']) Route::post('checkout/{o?}',[CheckoutController::class,'addedit'])
->where('o','[0-9]+'); ->where('o','[0-9]+');
@ -121,10 +124,6 @@ Route::group(['middleware'=>['auth','role:wholesaler'],'prefix'=>'a'],function()
Route::get('report/products',[ReportController::class,'products']); Route::get('report/products',[ReportController::class,'products']);
Route::view('report/services','theme.backend.adminlte.service.report'); Route::view('report/services','theme.backend.adminlte.service.report');
// Payments - @todo This should probably go to resellers
Route::match(['get','post'],'payment/addedit/{o?}',[AdminController::class,'pay_addedit']);
Route::get('payment/unapplied',[AdminController::class,'pay_unapplied']);
// Services // Services
// @todo This should probably go to resellers - implement a change audit log first // @todo This should probably go to resellers - implement a change audit log first
Route::post('service/update/{o}',[ServiceController::class,'update']) Route::post('service/update/{o}',[ServiceController::class,'update'])
@ -150,12 +149,23 @@ Route::group(['middleware'=>['auth','role:reseller'],'prefix'=>'r'],function() {
Route::get('hosting',[ServiceController::class,'hosting_list']); Route::get('hosting',[ServiceController::class,'hosting_list']);
}); });
Route::post('account/invoices',[InvoiceController::class,'api_account_invoices']);
// Charges // Charges
Route::post('charge/addedit',[ChargeController::class,'addedit']); Route::post('charge/addedit',[ChargeController::class,'addedit']);
Route::post('charge/delete/{o}',[ChargeController::class,'delete']) Route::post('charge/delete/{o}',[ChargeController::class,'delete'])
->where('o','[0-9]+'); ->where('o','[0-9]+');
Route::post('charge/edit',[ChargeController::class,'edit']); Route::post('charge/edit',[ChargeController::class,'edit']);
// Payments
Route::model('po',\App\Models\Payment::class);
Route::view('payment/new','theme.backend.adminlte.payment.view');
Route::view('payment/{po}','theme.backend.adminlte.payment.view')
->where('po','[0-9]+');
Route::post('payment/{o?}',[PaymentController::class,'addedit'])
->where('o','[0-9]+');
Route::view('payment/unapplied','theme.backend.adminlte.payment.unapplied');
// Reseller API calls // Reseller API calls
Route::post('service_change_charges/{o}',[ServiceController::class,'service_change_charges_display']) Route::post('service_change_charges/{o}',[ServiceController::class,'service_change_charges_display'])
->where('o','[0-9]+'); ->where('o','[0-9]+');
@ -170,17 +180,22 @@ Route::group(['middleware'=>['auth'],'prefix'=>'u'],function() {
Route::get('home/{o}',[HomeController::class,'home']) Route::get('home/{o}',[HomeController::class,'home'])
->where('o','[0-9]+') ->where('o','[0-9]+')
->middleware('can:view,o'); ->middleware('can:view,o');
Route::view('checkout/cart','theme.backend.adminlte.checkout.cart');
Route::get('checkout/cart/{o}',[CheckoutController::class,'cart_invoice'])
->where('o','[0-9]+')
->middleware('can:view,o');
Route::post('checkout/cart/remove',[CheckoutController::class,'cart_remove']);
Route::post('checkout/fee',[CheckoutController::class,'fee']);
Route::post('checkout/pay',[CheckoutController::class,'pay']); Route::post('checkout/pay',[CheckoutController::class,'pay']);
Route::get('invoice/{o}',[InvoiceController::class,'view']) Route::get('invoice/{o}',[InvoiceController::class,'view'])
->where('o','[0-9]+') ->where('o','[0-9]+')
->middleware('can:view,o'); ->middleware('can:view,o');
Route::get('invoice/{o}/pdf',[InvoiceController::class,'pdf']) Route::get('invoice/{o}/pdf',[InvoiceController::class,'pdf'])
->where('o','[0-9]+') ->where('o','[0-9]+')
->middleware('can:view,o'); ->middleware('can:view,o');
Route::get('invoice/cart',[CheckoutController::class,'cart_invoice']);
Route::get('invoice/cart/{o}',[CheckoutController::class,'cart_invoice'])
->where('o','[0-9]+')
->middleware('can:view,o');
Route::get('service/{o}',[ServiceController::class,'home']) Route::get('service/{o}',[ServiceController::class,'home'])
->where('o','[0-9]+') ->where('o','[0-9]+')
->middleware('can:view,o'); ->middleware('can:view,o');