Compare commits

..

4 Commits

Author SHA1 Message Date
b1067e1378 Added Passkey login, fixed password reset as a result of updating laravel
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 1m14s
Create Docker Image / Final Docker Image Manifest (push) Successful in 9s
2024-07-23 00:13:54 +10:00
45fb023d8a Moving accounting commands into an Intuit/ namespace, updates to intuit module 2024-07-14 13:49:00 +10:00
9580fb1cd9 Home page performance optimisations
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-07-09 21:09:34 +10:00
3aac3d310f Optimise Invoice 2024-07-09 20:17:30 +10:00
11 changed files with 235 additions and 139 deletions

View File

@ -2,11 +2,10 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
use App\Http\Requests\SiteEdit;
use App\Models\{Account, use App\Models\{Account,
Charge, Charge,
Invoice, Invoice,
@ -188,30 +187,47 @@ class AdminController extends Controller
* Site setup * Site setup
* *
* @note This method is protected by the routes * @note This method is protected by the routes
* @param SiteEdit $request * @param Request $request
* @return RedirectResponse * @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View|\Illuminate\Http\RedirectResponse
*/ */
public function setup(SiteEdit $request) public function setup(Request $request)
{ {
if ($request->post()) {
$validated = $request->validate([
'site_name' => 'required|string|min:2|max:255',
'site_email' => 'required|string|email|max:255',
'site_address1' => 'required|string|min:2|max:255',
'site_address2' => 'nullable|string|min:2|max:255',
'site_city' => 'required|string|min:2|max:64',
'site_state' => 'required|string|min:2|max:32',
'site_postcode' => 'required|string|min:2|max:8',
'site_description' => 'nullable|string|min:5',
'site_phone' => 'nullable|regex:/[0-9 ]+/|min:6|max:12',
'site_fax' => 'nullable|regex:/[0-9 ]+/|min:6|max:12',
'site_tax' => 'required|regex:/[0-9 ]+/|size:14',
'social' => 'nullable|array',
'top_menu' => 'nullable|array',
'site_logo' => 'nullable|image',
'email_logo' => 'nullable|image',
]);
$site = config('site'); $site = config('site');
$images = ['site_logo','email_logo'];
$validated = collect($request->validated()); // @todo - not currently rendered in the home page
$validated['social'] = [];
$validated['top_menu'] = [];
// Handle the images // Handle the images
foreach($images as $key) foreach(['site_logo','email_logo'] as $key)
if ($x=$request->validated($key)) if (array_key_exists($key,$validated))
$validated->put($key,$x->storeAs('site/'.$site->site_id,$x->getClientOriginalName(),'public')); $validated[$key] = ($x=$validated[$key])->storeAs('site/'.$site->site_id,$x->getClientOriginalName(),'public');
foreach ($site->details as $oo) foreach ($site->details as $oo)
if ($validated->has($oo->key)) { if (array_key_exists($oo->key,$validated)) {
// Dont set the following keys to null if they are null $oo->value = Arr::get($validated,$oo->key);
if (in_array($oo->key,$images) && is_null($validated->get($oo->key)))
continue;
$oo->value = $validated->get($oo->key) ?: '';
$oo->save(); $oo->save();
$validated->forget($oo->key); unset($validated[$oo->key]);
} }
// Left over values to be created. // Left over values to be created.
@ -227,4 +243,7 @@ class AdminController extends Controller
->back() ->back()
->with('success','Settings saved'); ->with('success','Settings saved');
} }
return view('theme.backend.adminlte.theme.backend.adminlte.a.setup');
}
} }

View File

@ -6,6 +6,7 @@ use Illuminate\Http\Request;
use Illuminate\Support\Facades\Schema; use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Facades\Config; use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\View; use Illuminate\Support\Facades\View;
use Closure;
use App\Models\Site; use App\Models\Site;
@ -18,13 +19,11 @@ use App\Models\Site;
class SetSite class SetSite
{ {
/** /**
* Handle an incoming request.
*
* @param Request $request * @param Request $request
* @param \Closure $next * @param Closure $next
* @return mixed * @return mixed
*/ */
public function handle(Request $request,\Closure $next): mixed public function handle(Request $request,Closure $next)
{ {
$so = new Site; $so = new Site;

View File

@ -1,43 +0,0 @@
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Gate;
class SiteEdit extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return Gate::allows('wholesaler');
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
return [
'site_name' => 'required|string|min:2|max:255',
'site_email' => 'required|string|email|max:255',
'site_address1' => 'required|string|min:2|max:255',
'site_address2' => 'nullable|string|min:2|max:255',
'site_city' => 'required|string|min:2|max:64',
'site_state' => 'required|string|min:2|max:32',
'site_postcode' => 'required|string|min:2|max:8',
'site_description' => 'nullable|string|min:5',
'site_phone' => 'nullable|regex:/[0-9 ]+/|min:6|max:12',
'site_fax' => 'nullable|regex:/[0-9 ]+/|min:6|max:12',
'site_tax' => 'required|regex:/[0-9 ]+/|size:14',
'social' => 'nullable|array',
'top_menu' => 'nullable|array',
'site_logo' => 'nullable|image',
'email_logo' => 'nullable|image',
];
}
}

View File

@ -6,11 +6,9 @@ use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Arr; use Illuminate\Support\Arr;
use Leenooks\Traits\CompositeKeys; use Leenooks\Traits\CompositeKeys;
use App\Traits\SiteID;
class SiteDetail extends Model class SiteDetail extends Model
{ {
use CompositeKeys,SiteID; use CompositeKeys;
protected $casts = [ protected $casts = [
'social' => 'array', 'social' => 'array',
@ -57,6 +55,13 @@ class SiteDetail extends Model
public $timestamps = FALSE; public $timestamps = FALSE;
/* RELATIONS */
public function site()
{
return $this->belongsTo(Site::class);
}
/* ATTRIBUTES */ /* ATTRIBUTES */
/** /**
@ -77,11 +82,12 @@ class SiteDetail extends Model
/** /**
* Set our value, casting it if required * Set our value, casting it if required
* *
* @param $key
* @param $value * @param $value
* @return void * @return string
* @throws \Exception * @throws \Exception
*/ */
public function setValueAttribute($value): void public function setValueAttribute($value)
{ {
// Check that the value can be set // Check that the value can be set
if (! $this->key) if (! $this->key)
@ -105,7 +111,7 @@ class SiteDetail extends Model
* @return mixed * @return mixed
* @throws \Exception * @throws \Exception
*/ */
public static function sample($key): mixed public static function sample($key)
{ {
return Arr::get(self::sampleData,$key); return Arr::get(self::sampleData,$key);
} }

View File

@ -14,7 +14,6 @@ use Leenooks\Traits\UserSwitch;
use App\Interfaces\IDs; use App\Interfaces\IDs;
use App\Notifications\ResetPassword as ResetPasswordNotification; use App\Notifications\ResetPassword as ResetPasswordNotification;
use App\Traits\SiteID;
/** /**
* Class User * Class User
@ -24,7 +23,7 @@ use App\Traits\SiteID;
*/ */
class User extends Authenticatable implements IDs class User extends Authenticatable implements IDs
{ {
use HasFactory,HasApiTokens,Notifiable,UserSwitch,ScopeActive,SiteID; use HasFactory,HasApiTokens,Notifiable,UserSwitch,ScopeActive;
private const CACHE_TIME = 3600; private const CACHE_TIME = 3600;

View File

@ -20,13 +20,15 @@ class ResetPassword extends ResetPasswordNotification implements ShouldQueue
*/ */
public function toMail($notifiable): MailMessage public function toMail($notifiable): MailMessage
{ {
$site = Site::findOrFail($notifiable->site_id);
if (static::$toMailCallback) { if (static::$toMailCallback) {
return call_user_func(static::$toMailCallback, $notifiable, $this->token); return call_user_func(static::$toMailCallback, $notifiable, $this->token);
} }
return (new MailMessage) return (new MailMessage)
->markdown('email.user.passwordreset',[ ->markdown('email.user.passwordreset',[
'site'=>$notifiable->site, 'site'=>$site,
'user'=>$notifiable, 'user'=>$notifiable,
'reset_link'=>route('password.reset',$this->token,true), 'reset_link'=>route('password.reset',$this->token,true),
]); ]);

22
composer.lock generated
View File

@ -2288,16 +2288,16 @@
}, },
{ {
"name": "league/commonmark", "name": "league/commonmark",
"version": "2.5.0", "version": "2.4.3",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/thephpleague/commonmark.git", "url": "https://github.com/thephpleague/commonmark.git",
"reference": "0026475f5c9a104410ae824cb5a4d63fa3bdb1df" "reference": "b7a7af3a23a818dcc5836e62e93e4b9ce4704481"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/thephpleague/commonmark/zipball/0026475f5c9a104410ae824cb5a4d63fa3bdb1df", "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/b7a7af3a23a818dcc5836e62e93e4b9ce4704481",
"reference": "0026475f5c9a104410ae824cb5a4d63fa3bdb1df", "reference": "b7a7af3a23a818dcc5836e62e93e4b9ce4704481",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -2310,8 +2310,8 @@
}, },
"require-dev": { "require-dev": {
"cebe/markdown": "^1.0", "cebe/markdown": "^1.0",
"commonmark/cmark": "0.31.0", "commonmark/cmark": "0.30.3",
"commonmark/commonmark.js": "0.31.0", "commonmark/commonmark.js": "0.30.0",
"composer/package-versions-deprecated": "^1.8", "composer/package-versions-deprecated": "^1.8",
"embed/embed": "^4.4", "embed/embed": "^4.4",
"erusev/parsedown": "^1.0", "erusev/parsedown": "^1.0",
@ -2333,7 +2333,7 @@
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-main": "2.6-dev" "dev-main": "2.5-dev"
} }
}, },
"autoload": { "autoload": {
@ -2390,7 +2390,7 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2024-07-22T18:18:14+00:00" "time": "2024-07-22T12:31:21+00:00"
}, },
{ {
"name": "league/config", "name": "league/config",
@ -3056,11 +3056,11 @@
}, },
{ {
"name": "leenooks/laravel", "name": "leenooks/laravel",
"version": "11.1.2", "version": "11.1.1",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://gitea.dege.au/laravel/leenooks.git", "url": "https://gitea.dege.au/laravel/leenooks.git",
"reference": "f32c29fa8c4b189add48bde26b7b7115be49355f" "reference": "b9a3cd564759218fd6b3bc1ebf58e798833f014e"
}, },
"type": "library", "type": "library",
"extra": { "extra": {
@ -3093,7 +3093,7 @@
"laravel", "laravel",
"leenooks" "leenooks"
], ],
"time": "2024-07-23T08:47:36+00:00" "time": "2024-07-22T14:11:06+00:00"
}, },
{ {
"name": "leenooks/passkey", "name": "leenooks/passkey",

View File

@ -6,8 +6,8 @@ A new invoice has been generated on your account. A summary of that invoice is b
@component('mail::table') @component('mail::table')
| # | ID | Name | Amount | | # | ID | Name | Amount |
| -: | - |:-----| ------:| | -: | - |:-----| ------:|
@foreach ($invoice->summary_products() as $item) @foreach ($invoice->products() as $po)
| {{ $item['services']->count() }} | {{ $item['product']->lid }} | {{ $item['product']->name }} | ${{ number_format($item['total'],2) }} | | {{ $po->count }} | {{ $po->product_id }} | {{ $po->name }} | ${{ number_format($invoice->items->filter(function($item) use ($po) {return $item->product_id == $po->id; })->sum('total'),2) }} |
@endforeach @endforeach
||| Sub Total | ${{ number_format($invoice->sub_total,2) }} | ||| Sub Total | ${{ number_format($invoice->sub_total,2) }} |
||| Tax | ${{ number_format($invoice->tax_total,2) }} | ||| Tax | ${{ number_format($invoice->tax_total,2) }} |

View File

@ -21,7 +21,9 @@
<div class="card card-dark"> <div class="card card-dark">
<div class="card-header"> <div class="card-header">
<h1 class="card-title">Setup Configuration</h1> <h1 class="card-title">Setup Configuration</h1>
<x-leenooks::button.success class="float-right"/> @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>
<div class="card-body"> <div class="card-body">
@ -29,69 +31,182 @@
@csrf @csrf
<div class="row"> <div class="row">
<div class="col-12 col-sm-6 col-lg-4"> <div class="col-4">
<div class="row"> <div class="row">
<div class="col"> <div class="col-12">
<x-leenooks::form.text name="site_name" label="Organisation Name" helper="System Name used everywhere." feedback="Organisation Name is required!" :value="$site->site_name" required/> <div class="form-group has-validation">
<label for="site_name">Organisation Name</label>
<input type="text" class="form-control form-control-border @error('site_name') is-invalid @enderror" id="site_name" name="site_name" placeholder="Site Name..." value="{{ old('site_name',$site->site_name) }}" required>
<span class="invalid-feedback" role="alert">
@error('site_name')
{{ $message }}
@else
Organisation Name is required.
@enderror
</span>
<span class="input-helper">System Name used everywhere.</span>
</div>
</div> </div>
</div> </div>
<div class="row"> <div class="row">
<div class="col"> <div class="col-10">
<x-leenooks::form.text name="site_address1" label="Address" placeholder="Address..." :value="$site->site_address1" required/> <div class="form-group has-validation">
<x-leenooks::form.text name="site_address2" helper="At Least 1 address line required" :value="$site->site_address2"/> <label for="site_logo">Site Logo</label>
<input type="file" class="form-control @error('site_logo') is-invalid @enderror" id="site_logo" name="site_logo"><img class="col-12" src="{{ asset($site->site_logo) }}">
<span class="invalid-feedback" role="alert">
@error('site_logo')
{{ $message }}
@enderror
</span>
</div>
</div> </div>
</div> </div>
<div class="row"> <div class="row">
<div class="col-12 col-md-8"> <div class="col-10">
<x-leenooks::form.text name="site_city" label="City" feedback="City is required" :value="$site->site_city" required/> <div class="form-group has-validation">
<label for="email_logo">Email Logo</label>
<input type="file" class="form-control @error('email_logo') is-invalid @enderror" id="email_logo" name="email_logo"><img class="col-12" src="{{ asset($site->email_logo) }}">
<span class="invalid-feedback" role="alert">
@error('email_logo')
{{ $message }}
@enderror
</span>
</div>
</div>
</div> </div>
</div> </div>
<div class="col-4">
<div class="row"> <div class="row">
<div class="col-12 col-md-4"> <div class="col-12">
<x-leenooks::form.text name="site_state" label="State" feedback="State is required" :value="$site->site_state" required/> <div class="form-group has-validation">
</div> <label for="site_address1">Address Lines</label>
<div class="col-12 col-md-4"> <input type="text" class="form-control form-control-border @error('site_address1') is-invalid @enderror" id="site_address1" name="site_address1" placeholder="Address1" value="{{ old('site_address1',$site->site_address1) }}" required>
<x-leenooks::form.text name="site_postcode" label="Post Code" feedback="Postcode is required" :value="$site->site_postcode" required/> <input type="text" class="form-control form-control-border" id="site_address2" name="site_address2" placeholder="Address2" value="{{ old('site_address2',$site->site_address2) }}">
</div> <span class="invalid-feedback" role="alert">
</div> @error('site_address1')
</div> {{ $message }}
@else
<div class="col-12 col-sm-6 col-lg-4"> Atleast 1 address line required.
<x-leenooks::form.text name="site_phone" label="Phone" :value="$site->site_phone"/> @enderror
<x-leenooks::form.text name="site_fax" label="Fax" :value="$site->site_fax"/> </span>
<x-leenooks::form.email name="site_email" label="Email" feedback="Email is required" :value="$site->site_email" required/>
<x-leenooks::form.text name="site_tax" label="Tax Number" prepend="<small>ABN</small>" feedback="Tax Number is required" :value="$site->site_tax" required/>
</div>
<div class="col-12 col-sm-6 col-lg-4">
<div class="row">
<div class="col">
<x-leenooks::form.file name="site_logo" label="Site Logo" helper="Choose a file" :value="$site->site_logo"><img class="col-12 p-2 border mb-4" src="{{ asset($site->site_logo) }}"></x-leenooks::form.file>
</div>
</div>
<div class="row">
<div class="col">
<x-leenooks::form.file name="email_logo" label="Email Logo" helper="Choose a file" :value="$site->email_logo"><img class="col-12 p-2 border mb-4" src="{{ asset($site->email_logo) }}"></x-leenooks::form.file>
</div>
</div> </div>
</div> </div>
</div> </div>
<div class="row"> <div class="row">
<div class="col-12"> <div class="col-12">
<x-leenooks::form.textarea name="site_description" label="Organisation Description" placeholder="Site Description..." helper="Brief description of site." :value="$site->site_description"/> <div class="form-group has-validation">
<label for="site_city">City</label>
<input type="text" class="form-control form-control-border @error('site_city') is-invalid @enderror" id="site_city" name="site_city" placeholder="City" value="{{ old('site_city',$site->site_city) }}">
<span class="invalid-feedback" role="alert">
@error('site_city')
{{ $message }}
@else
City is required.
@enderror
</span>
</div>
</div>
</div>
<div class="row">
<div class="col-12" style="display: inline-flex;">
<div class="form-group has-validation">
<label for="site_state">State</label>
<input type="text" class="form-control form-control-border @error('site_state') is-invalid @enderror col-5" id="site_state" name="site_state" placeholder="State" value="{{ old('site_state',$site->site_state) }}">
<span class="invalid-feedback" role="alert">
@error('site_state')
{{ $message }}
@else
State is required.
@enderror
</span>
</div>
<div class="form-group has-validation">
<label for="site_postcode">Postal Code</label>
<input type="text" class="form-control form-control-border @error('site_postcode') is-invalid @enderror col-5" id="site_postcode" name="site_postcode" placeholder="Postal Code" value="{{ old('site_postcode',$site->site_postcode) }}">
<span class="invalid-feedback" role="alert">
@error('site_postcode')
{{ $message }}
@else
Postcode is required.
@enderror
</span>
</div>
</div>
</div>
</div>
<div class="col-4">
<div class="form-group has-validation">
<label for="site_phone">Phone</label>
<input type="text" class="form-control form-control-border @error('site_phone') is-invalid @enderror" id="site_phone" name="site_phone" placeholder="Site Phone" value="{{ old('site_phone',$site->site_phone) }}">
<span class="invalid-feedback" role="alert">
@error('site_phone')
{{ $message }}
@enderror
</span>
</div>
<div class="form-group has-validation">
<label for="site_fax">Fax</label>
<input type="text" class="form-control form-control-border @error('site_fax') is-invalid @enderror" id="site_fax" name="site_fax" placeholder="Site Fax" value="{{ old('site_fax',$site->site_fax) }}">
<span class="invalid-feedback" role="alert">
@error('site_fax')
{{ $message }}
@enderror
</span>
</div>
<div class="form-group has-validation">
<label for="site_email">Email</label>
<input type="email" class="form-control form-control-border @error('site_email') is-invalid @enderror" id="site_email" name="site_email" placeholder="Site Email" value="{{ old('site_email',$site->site_email) }}" required>
<span class="invalid-feedback" role="alert">
@error('site_email')
{{ $message }}
@else
Email required.
@enderror
</span>
</div>
<div class="form-group has-validation">
<label for="site_tax">Tax Number</label>
<span class="input-group-prepend">ABN</span>
<input type="text" class="form-control form-control-border @error('site_tax') is-invalid @enderror" id="site_tax" name="site_tax" placeholder="Site Tax" value="{{ old('site_tax',$site->site_tax) }}" required>
<span class="invalid-feedback" role="alert">
@error('site_tax')
{{ $message }}
@enderror
</span>
</div>
</div> </div>
</div> </div>
<div class="row"> <div class="row">
<div class="col-12"> <div class="col-12">
<x-leenooks::button.cancel/> <div class="form-group has-validation">
<label for="site_description">Organisation Description</label>
<textarea class="form-control @error('site_description') is-invalid @enderror" id="site_description" name="site_description" placeholder="Site Description...">{{ old('site_description',$site->site_description) }}</textarea>
<span class="input-helper">Brief description of site.</span>
<span class="invalid-feedback" role="alert">
@error('site_description')
{{ $message }}
@enderror
</span>
</div>
</div>
</div>
<div class="row">
<div class="col-12">
<a href="{{ url('/home') }}" class="btn btn-danger">Cancel</a>
@can('wholesaler') @can('wholesaler')
<x-leenooks::button.submit class="float-right">Save</x-leenooks::button.submit> <button type="submit" name="submit" class="btn btn-success mr-0 float-right">@if ($site->exists)Save @else Add @endif</button>
@endcan @endcan
</div> </div>
</div> </div>

View File

@ -22,7 +22,7 @@
<div class="card"> <div class="card">
<div class="card-header"> <div class="card-header">
<div class="card-title">Update Settings</div> <div class="card-title">Update Settings</div>
<x-leenooks::button.success class="float-right"/> @session('success')<x-leenooks::success class="float-right">{{ $value }}</x-leenooks::success>@endsession
</div> </div>
<form method="POST" action="{{ url(request()->path(),[($o??$user)->id]) }}"> <form method="POST" action="{{ url(request()->path(),[($o??$user)->id]) }}">
@ -45,7 +45,7 @@
<div class="row"> <div class="row">
<div class="col"> <div class="col">
<x-leenooks::form.password id="password_confirm" name="password_confirmation" icon="fa-lock" label="Password Confirm" helper="Verify Password"/> <x-leenooks::form.password id="password_confirm" name="password_confirmation" icon="fa-lock" label="Password Confirm"/>
</div> </div>
</div> </div>
@ -101,8 +101,8 @@
<div class="row pt-3"> <div class="row pt-3">
<div class="col"> <div class="col">
<x-leenooks::button.cancel/> <button type="reset" name="cancel" class="btn btn-danger">Cancel</button>
<x-leenooks::button.submit class="float-right">Save</x-leenooks::button.submit> <button type="submit" name="submit" class="btn btn-success float-right">Save</button>
</div> </div>
</div> </div>
</div> </div>

View File

@ -63,8 +63,7 @@ Route::get('admin/switch/stop',[SwitchUserController::class,'switch_stop'])
// Our Admin Routes - for wholesalers // Our Admin Routes - for wholesalers
Route::group(['middleware'=>['auth','role:wholesaler'],'prefix'=>'a'],function() { Route::group(['middleware'=>['auth','role:wholesaler'],'prefix'=>'a'],function() {
// Site Setup // Site Setup
Route::view('setup','theme.backend.adminlte.a.setup'); Route::match(['get','post'],'setup',[AdminController::class,'setup']);
Route::post('setup',[AdminController::class,'setup']);
// Checkout Setup (Payments) // Checkout Setup (Payments)
Route::get('checkout',[CheckoutController::class,'home']); Route::get('checkout',[CheckoutController::class,'home']);