Compare commits
6 Commits
b1067e1378
...
b145856ce9
Author | SHA1 | Date | |
---|---|---|---|
b145856ce9 | |||
45794ff109 | |||
c91a2fa8e5 | |||
b486a0eac4 | |||
28aa1f9dc8 | |||
f561139d45 |
@ -1,3 +1,4 @@
|
|||||||
|
APP_ADMIN=
|
||||||
APP_DEBUG=false
|
APP_DEBUG=false
|
||||||
APP_NAME=OSB
|
APP_NAME=OSB
|
||||||
APP_NAME_HTML_LONG="<b>Graytech</b>Hosting"
|
APP_NAME_HTML_LONG="<b>Graytech</b>Hosting"
|
||||||
@ -7,6 +8,8 @@ APP_KEY=
|
|||||||
APP_TIMEZONE=Australia/Melbourne
|
APP_TIMEZONE=Australia/Melbourne
|
||||||
APP_URL=https://www.graytech.net.au
|
APP_URL=https://www.graytech.net.au
|
||||||
|
|
||||||
|
AUTH_PASSWORD_RESET_TOKEN_TABLE=password_resets
|
||||||
|
|
||||||
LOG_CHANNEL=daily
|
LOG_CHANNEL=daily
|
||||||
|
|
||||||
DB_CONNECTION=pgsql
|
DB_CONNECTION=pgsql
|
||||||
|
@ -1,51 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Console\Commands;
|
|
||||||
|
|
||||||
use Illuminate\Console\Command;
|
|
||||||
use Illuminate\Support\Facades\Config;
|
|
||||||
|
|
||||||
use App\Models\{ProviderOauth,Site,User};
|
|
||||||
use App\Jobs\AccountingAccountSync as Job;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Synchronise Customers with Accounts
|
|
||||||
*/
|
|
||||||
class AccountingAccountSync extends Command
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* The name and signature of the console command.
|
|
||||||
*
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
protected $signature = 'accounting:account:sync'
|
|
||||||
.' {siteid : Site ID}'
|
|
||||||
.' {provider : Provider Name}'
|
|
||||||
.' {user : User Email}';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The console command description.
|
|
||||||
*
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
protected $description = 'Synchronise accounts with accounting system';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Execute the console command.
|
|
||||||
*
|
|
||||||
* @return int
|
|
||||||
*/
|
|
||||||
public function handle()
|
|
||||||
{
|
|
||||||
$site = Site::findOrFail($this->argument('siteid'));
|
|
||||||
Config::set('site',$site);
|
|
||||||
|
|
||||||
$uo = User::where('email',$this->argument('user'))->singleOrFail();
|
|
||||||
|
|
||||||
$so = ProviderOauth::where('name',$this->argument('provider'))->singleOrFail();
|
|
||||||
if (! ($to=$so->token($uo)))
|
|
||||||
abort(500,sprintf('Unknown Tokens for [%s]',$uo->email));
|
|
||||||
|
|
||||||
Job::dispatchSync($to);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,33 +1,32 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace App\Console\Commands;
|
namespace App\Console\Commands\Intuit;
|
||||||
|
|
||||||
use Illuminate\Console\Command;
|
use Illuminate\Console\Command;
|
||||||
use Illuminate\Support\Facades\Config;
|
|
||||||
use Intuit\Jobs\AccountingCustomerUpdate;
|
use Intuit\Jobs\AccountingCustomerUpdate;
|
||||||
use Intuit\Models\Customer as AccAccount;
|
use Intuit\Models\Customer as AccAccount;
|
||||||
|
|
||||||
use App\Models\{Account,ProviderOauth,Site,User};
|
use App\Models\{Account,ProviderOauth,User};
|
||||||
|
|
||||||
class AccountingAccountAdd extends Command
|
class AccountAdd extends Command
|
||||||
{
|
{
|
||||||
|
private const provider = 'intuit';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The name and signature of the console command.
|
* The name and signature of the console command.
|
||||||
*
|
*
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
protected $signature = 'accounting:account:add'
|
protected $signature = 'intuit:account:add'
|
||||||
.' {siteid : Site ID}'
|
.' {id : Account ID}'
|
||||||
.' {provider : Provider Name}'
|
.' {user? : User Email}';
|
||||||
.' {user : User Email}'
|
|
||||||
.' {id : Account ID}';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The console command description.
|
* The console command description.
|
||||||
*
|
*
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
protected $description = 'Add an account to the accounting provider';
|
protected $description = 'Add an account to quickbooks';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Execute the console command.
|
* Execute the console command.
|
||||||
@ -36,12 +35,9 @@ class AccountingAccountAdd extends Command
|
|||||||
*/
|
*/
|
||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
$site = Site::findOrFail($this->argument('siteid'));
|
$uo = User::where('email',$this->argument('user') ?: config('osb.admin'))->singleOrFail();
|
||||||
Config::set('site',$site);
|
|
||||||
|
|
||||||
$uo = User::where('email',$this->argument('user'))->singleOrFail();
|
$so = ProviderOauth::where('name',self::provider)->singleOrFail();
|
||||||
|
|
||||||
$so = ProviderOauth::where('name',$this->argument('provider'))->singleOrFail();
|
|
||||||
if (! ($to=$so->token($uo)))
|
if (! ($to=$so->token($uo)))
|
||||||
abort(500,sprintf('Unknown Tokens for [%s]',$uo->email));
|
abort(500,sprintf('Unknown Tokens for [%s]',$uo->email));
|
||||||
|
|
@ -1,33 +1,32 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace App\Console\Commands;
|
namespace App\Console\Commands\Intuit;
|
||||||
|
|
||||||
use GuzzleHttp\Exception\ConnectException;
|
use GuzzleHttp\Exception\ConnectException;
|
||||||
use Illuminate\Console\Command;
|
use Illuminate\Console\Command;
|
||||||
use Illuminate\Support\Facades\Config;
|
|
||||||
use Intuit\Exceptions\ConnectionIssueException;
|
use Intuit\Exceptions\ConnectionIssueException;
|
||||||
|
|
||||||
use App\Models\{ProviderOauth,Site,User};
|
use App\Models\{ProviderOauth,User};
|
||||||
|
|
||||||
class AccountingAccountGet extends Command
|
class AccountGet extends Command
|
||||||
{
|
{
|
||||||
|
private const provider = 'intuit';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The name and signature of the console command.
|
* The name and signature of the console command.
|
||||||
*
|
*
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
protected $signature = 'accounting:account:get'
|
protected $signature = 'intuit:account:get'
|
||||||
.' {siteid : Site ID}'
|
.' {id : Account ID}'
|
||||||
.' {provider : Provider Name}'
|
.' {user? : User Email}';
|
||||||
.' {user : User Email}'
|
|
||||||
.' {id : Account ID}';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The console command description.
|
* The console command description.
|
||||||
*
|
*
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
protected $description = 'Get an account from the accounting provider';
|
protected $description = 'Get an account from quickbooks';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Execute the console command.
|
* Execute the console command.
|
||||||
@ -36,12 +35,9 @@ class AccountingAccountGet extends Command
|
|||||||
*/
|
*/
|
||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
$site = Site::findOrFail($this->argument('siteid'));
|
$uo = User::where('email',$this->argument('user') ?: config('osb.admin'))->singleOrFail();
|
||||||
Config::set('site',$site);
|
|
||||||
|
|
||||||
$uo = User::where('email',$this->argument('user'))->singleOrFail();
|
$so = ProviderOauth::where('name',self::provider)->singleOrFail();
|
||||||
|
|
||||||
$so = ProviderOauth::where('name',$this->argument('provider'))->singleOrFail();
|
|
||||||
if (! ($to=$so->token($uo)))
|
if (! ($to=$so->token($uo)))
|
||||||
abort(500,sprintf('Unknown Tokens for [%s]',$uo->email));
|
abort(500,sprintf('Unknown Tokens for [%s]',$uo->email));
|
||||||
|
|
||||||
@ -52,7 +48,9 @@ class AccountingAccountGet extends Command
|
|||||||
} catch (ConnectException|ConnectionIssueException $e) {
|
} catch (ConnectException|ConnectionIssueException $e) {
|
||||||
$this->error($e->getMessage());
|
$this->error($e->getMessage());
|
||||||
|
|
||||||
return Command::FAILURE;
|
return self::FAILURE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return self::SUCCESS;
|
||||||
}
|
}
|
||||||
}
|
}
|
49
app/Console/Commands/Intuit/AccountSync.php
Normal file
49
app/Console/Commands/Intuit/AccountSync.php
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Console\Commands\Intuit;
|
||||||
|
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
|
||||||
|
use App\Models\{ProviderOauth,User};
|
||||||
|
use App\Jobs\AccountingAccountSync as Job;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Synchronise Customers with Accounts
|
||||||
|
*/
|
||||||
|
class AccountSync extends Command
|
||||||
|
{
|
||||||
|
private const provider = 'intuit';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The name and signature of the console command.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $signature = 'intuit:account:sync'
|
||||||
|
.' {user? : User Email}';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The console command description.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $description = 'Synchronise accounts with quickbooks';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the console command.
|
||||||
|
*
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
$uo = User::where('email',$this->argument('user') ?: config('osb.admin'))->singleOrFail();
|
||||||
|
|
||||||
|
$so = ProviderOauth::where('name',self::provider)->singleOrFail();
|
||||||
|
if (! ($to=$so->token($uo)))
|
||||||
|
abort(500,sprintf('Unknown Tokens for [%s]',$uo->email));
|
||||||
|
|
||||||
|
Job::dispatchSync($to);
|
||||||
|
|
||||||
|
return self::SUCCESS;
|
||||||
|
}
|
||||||
|
}
|
@ -1,26 +1,25 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace App\Console\Commands;
|
namespace App\Console\Commands\Intuit;
|
||||||
|
|
||||||
use Illuminate\Console\Command;
|
use Illuminate\Console\Command;
|
||||||
use Illuminate\Support\Facades\Config;
|
|
||||||
use Intuit\Jobs\AccountingInvoiceUpdate;
|
use Intuit\Jobs\AccountingInvoiceUpdate;
|
||||||
use Intuit\Models\Invoice as AccInvoice;
|
use Intuit\Models\Invoice as AccInvoice;
|
||||||
|
|
||||||
use App\Models\{Invoice,ProviderOauth,Site,User};
|
use App\Models\{Invoice,ProviderOauth,User};
|
||||||
|
|
||||||
class AccountingInvoiceAdd extends Command
|
class InvoiceAdd extends Command
|
||||||
{
|
{
|
||||||
|
private const provider = 'intuit';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The name and signature of the console command.
|
* The name and signature of the console command.
|
||||||
*
|
*
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
protected $signature = 'accounting:invoice:add'
|
protected $signature = 'accounting:invoice:add'
|
||||||
.' {siteid : Site ID}'
|
.' {id : Invoice ID}'
|
||||||
.' {provider : Provider Name}'
|
.' {user? : User Email}';
|
||||||
.' {user : User Email}'
|
|
||||||
.' {id : Invoice ID}';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The console command description.
|
* The console command description.
|
||||||
@ -33,15 +32,13 @@ class AccountingInvoiceAdd extends Command
|
|||||||
* Execute the console command.
|
* Execute the console command.
|
||||||
*
|
*
|
||||||
* @return int
|
* @return int
|
||||||
|
* @throws \Exception
|
||||||
*/
|
*/
|
||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
$site = Site::findOrFail($this->argument('siteid'));
|
$uo = User::where('email',$this->argument('user') ?: config('osb.admin'))->singleOrFail();
|
||||||
Config::set('site',$site);
|
|
||||||
|
|
||||||
$uo = User::where('email',$this->argument('user'))->singleOrFail();
|
$so = ProviderOauth::where('name',self::provider)->singleOrFail();
|
||||||
|
|
||||||
$so = ProviderOauth::where('name',$this->argument('provider'))->singleOrFail();
|
|
||||||
if (! ($to=$so->token($uo)))
|
if (! ($to=$so->token($uo)))
|
||||||
abort(500,sprintf('Unknown Tokens for [%s]',$uo->email));
|
abort(500,sprintf('Unknown Tokens for [%s]',$uo->email));
|
||||||
|
|
||||||
@ -78,12 +75,14 @@ class AccountingInvoiceAdd extends Command
|
|||||||
// Some validation
|
// Some validation
|
||||||
if (! ($ref=$key->product->provider_ref($so))) {
|
if (! ($ref=$key->product->provider_ref($so))) {
|
||||||
$this->error(sprintf('Accounting not defined in product [%d]',$key->product_id));
|
$this->error(sprintf('Accounting not defined in product [%d]',$key->product_id));
|
||||||
exit(1);
|
|
||||||
|
return self::FAILURE;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($key->taxes->count() !== 1) {
|
if ($key->taxes->count() !== 1) {
|
||||||
$this->error(sprintf('Cannot handle when there is not just 1 tax line [%d]',$key->id));
|
$this->error(sprintf('Cannot handle when there is not just 1 tax line [%d]',$key->id));
|
||||||
exit(1);
|
|
||||||
|
return self::FAILURE;
|
||||||
}
|
}
|
||||||
|
|
||||||
$c++;
|
$c++;
|
@ -1,33 +1,32 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace App\Console\Commands;
|
namespace App\Console\Commands\Intuit;
|
||||||
|
|
||||||
use GuzzleHttp\Exception\ConnectException;
|
use GuzzleHttp\Exception\ConnectException;
|
||||||
use Illuminate\Console\Command;
|
use Illuminate\Console\Command;
|
||||||
use Illuminate\Support\Facades\Config;
|
|
||||||
use Intuit\Exceptions\ConnectionIssueException;
|
use Intuit\Exceptions\ConnectionIssueException;
|
||||||
|
|
||||||
use App\Models\{ProviderOauth,Site,User};
|
use App\Models\{ProviderOauth,User};
|
||||||
|
|
||||||
class AccountingInvoiceGet extends Command
|
class InvoiceGet extends Command
|
||||||
{
|
{
|
||||||
|
private const provider = 'intuit';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The name and signature of the console command.
|
* The name and signature of the console command.
|
||||||
*
|
*
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
protected $signature = 'accounting:invoice:get'
|
protected $signature = 'intuit:invoice:get'
|
||||||
.' {siteid : Site ID}'
|
.' {id : Invoice ID}'
|
||||||
.' {provider : Provider Name}'
|
.' {user? : User Email}';
|
||||||
.' {user : User Email}'
|
|
||||||
.' {id : Invoice ID}';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The console command description.
|
* The console command description.
|
||||||
*
|
*
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
protected $description = 'Get an invoice from the accounting provider';
|
protected $description = 'Get an invoice from the quickbooks';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Execute the console command.
|
* Execute the console command.
|
||||||
@ -36,12 +35,9 @@ class AccountingInvoiceGet extends Command
|
|||||||
*/
|
*/
|
||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
$site = Site::findOrFail($this->argument('siteid'));
|
$uo = User::where('email',$this->argument('user') ?: config('osb.admin'))->singleOrFail();
|
||||||
Config::set('site',$site);
|
|
||||||
|
|
||||||
$uo = User::where('email',$this->argument('user'))->singleOrFail();
|
$so = ProviderOauth::where('name',self::provider)->singleOrFail();
|
||||||
|
|
||||||
$so = ProviderOauth::where('name',$this->argument('provider'))->singleOrFail();
|
|
||||||
if (! ($to=$so->token($uo)))
|
if (! ($to=$so->token($uo)))
|
||||||
abort(500,sprintf('Unknown Tokens for [%s]',$uo->email));
|
abort(500,sprintf('Unknown Tokens for [%s]',$uo->email));
|
||||||
|
|
||||||
@ -52,7 +48,9 @@ class AccountingInvoiceGet extends Command
|
|||||||
} catch (ConnectException|ConnectionIssueException $e) {
|
} catch (ConnectException|ConnectionIssueException $e) {
|
||||||
$this->error($e->getMessage());
|
$this->error($e->getMessage());
|
||||||
|
|
||||||
return Command::FAILURE;
|
return self::FAILURE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return self::SUCCESS;
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,25 +1,23 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace App\Console\Commands;
|
namespace App\Console\Commands\Intuit;
|
||||||
|
|
||||||
use Illuminate\Console\Command;
|
use Illuminate\Console\Command;
|
||||||
use Illuminate\Support\Facades\Config;
|
|
||||||
|
|
||||||
use Illuminate\Support\Facades\Log;
|
use App\Models\{Product,ProviderOauth,User};
|
||||||
use App\Models\{Product, ProviderOauth, Site, User};
|
|
||||||
use App\Jobs\AccountingItemSync as Job;
|
use App\Jobs\AccountingItemSync as Job;
|
||||||
|
|
||||||
class AccountingItemList extends Command
|
class ItemList extends Command
|
||||||
{
|
{
|
||||||
|
private const provider = 'intuit';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The name and signature of the console command.
|
* The name and signature of the console command.
|
||||||
*
|
*
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
protected $signature = 'accounting:item:list'
|
protected $signature = 'accounting:item:list'
|
||||||
.' {siteid : Site ID}'
|
.' {user? : User Email}';
|
||||||
.' {provider : Provider Name}'
|
|
||||||
.' {user : User Email}';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The console command description.
|
* The console command description.
|
||||||
@ -35,12 +33,9 @@ class AccountingItemList extends Command
|
|||||||
*/
|
*/
|
||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
$site = Site::findOrFail($this->argument('siteid'));
|
$uo = User::where('email',$this->argument('user') ?: config('osb.admin'))->singleOrFail();
|
||||||
Config::set('site',$site);
|
|
||||||
|
|
||||||
$so = ProviderOauth::where('name',$this->argument('provider'))->singleOrFail();
|
|
||||||
$uo = User::where('email',$this->argument('user'))->singleOrFail();
|
|
||||||
|
|
||||||
|
$so = ProviderOauth::where('name',self::provider)->singleOrFail();
|
||||||
if (($x=$so->tokens->where('user_id',$uo->id))->count() !== 1)
|
if (($x=$so->tokens->where('user_id',$uo->id))->count() !== 1)
|
||||||
abort(500,sprintf('Unknown Tokens for [%s]',$uo->email));
|
abort(500,sprintf('Unknown Tokens for [%s]',$uo->email));
|
||||||
|
|
||||||
@ -67,5 +62,7 @@ class AccountingItemList extends Command
|
|||||||
else
|
else
|
||||||
$this->info(sprintf('Product [%d](%s) set to accounting [%s]',$po->id,$po->name,$po->accounting));
|
$this->info(sprintf('Product [%d](%s) set to accounting [%s]',$po->id,$po->name,$po->accounting));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return self::SUCCESS;
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,27 +1,26 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace App\Console\Commands;
|
namespace App\Console\Commands\Intuit;
|
||||||
|
|
||||||
use GuzzleHttp\Exception\ConnectException;
|
use GuzzleHttp\Exception\ConnectException;
|
||||||
use Illuminate\Console\Command;
|
use Illuminate\Console\Command;
|
||||||
use Illuminate\Support\Facades\Config;
|
|
||||||
use Intuit\Exceptions\ConnectionIssueException;
|
use Intuit\Exceptions\ConnectionIssueException;
|
||||||
|
|
||||||
use App\Jobs\AccountingPaymentSync as Job;
|
use App\Jobs\AccountingPaymentSync as Job;
|
||||||
use App\Models\{ProviderOauth,Site,User};
|
use App\Models\{ProviderOauth,User};
|
||||||
|
|
||||||
class AccountingPaymentGet extends Command
|
class PaymentGet extends Command
|
||||||
{
|
{
|
||||||
|
private const provider = 'intuit';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The name and signature of the console command.
|
* The name and signature of the console command.
|
||||||
*
|
*
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
protected $signature = 'accounting:payment:get'
|
protected $signature = 'accounting:payment:get'
|
||||||
.' {siteid : Site ID}'
|
.' {id : Payment ID}'
|
||||||
.' {provider : Provider Name}'
|
.' {user? : User Email}';
|
||||||
.' {user : User Email}'
|
|
||||||
.' {id : Payment ID}';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The console command description.
|
* The console command description.
|
||||||
@ -37,12 +36,9 @@ class AccountingPaymentGet extends Command
|
|||||||
*/
|
*/
|
||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
$site = Site::findOrFail($this->argument('siteid'));
|
$uo = User::where('email',$this->argument('user') ?: config('osb.admin'))->singleOrFail();
|
||||||
Config::set('site',$site);
|
|
||||||
|
|
||||||
$uo = User::where('email',$this->argument('user'))->singleOrFail();
|
$so = ProviderOauth::where('name',self::provider)->singleOrFail();
|
||||||
|
|
||||||
$so = ProviderOauth::where('name',$this->argument('provider'))->singleOrFail();
|
|
||||||
if (! ($to=$so->token($uo)))
|
if (! ($to=$so->token($uo)))
|
||||||
abort(500,sprintf('Unknown Tokens for [%s]',$uo->email));
|
abort(500,sprintf('Unknown Tokens for [%s]',$uo->email));
|
||||||
|
|
||||||
@ -59,5 +55,7 @@ class AccountingPaymentGet extends Command
|
|||||||
|
|
||||||
if ($acc)
|
if ($acc)
|
||||||
Job::dispatchSync($to,$acc);
|
Job::dispatchSync($to,$acc);
|
||||||
|
|
||||||
|
return self::SUCCESS;
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,24 +1,23 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace App\Console\Commands;
|
namespace App\Console\Commands\Intuit;
|
||||||
|
|
||||||
use Illuminate\Console\Command;
|
use Illuminate\Console\Command;
|
||||||
use Illuminate\Support\Facades\Config;
|
|
||||||
|
|
||||||
use App\Models\{ProviderOauth,Site,User};
|
use App\Models\{ProviderOauth,User};
|
||||||
use App\Jobs\AccountingPaymentSync as Job;
|
use App\Jobs\AccountingPaymentSync as Job;
|
||||||
|
|
||||||
class AccountingPaymentSync extends Command
|
class PaymentSync extends Command
|
||||||
{
|
{
|
||||||
|
private const provider = 'intuit';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The name and signature of the console command.
|
* The name and signature of the console command.
|
||||||
*
|
*
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
protected $signature = 'accounting:payment:sync'
|
protected $signature = 'accounting:payment:sync'
|
||||||
.' {siteid : Site ID}'
|
.' {user? : User Email}';
|
||||||
.' {provider : Provider Name}'
|
|
||||||
.' {user : User Email}';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The console command description.
|
* The console command description.
|
||||||
@ -34,17 +33,16 @@ class AccountingPaymentSync extends Command
|
|||||||
*/
|
*/
|
||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
$site = Site::findOrFail($this->argument('siteid'));
|
$uo = User::where('email',$this->argument('user') ?: config('osb.admin'))->singleOrFail();
|
||||||
Config::set('site',$site);
|
|
||||||
|
|
||||||
$uo = User::where('email',$this->argument('user'))->singleOrFail();
|
$so = ProviderOauth::where('name',self::provider)->singleOrFail();
|
||||||
|
|
||||||
$so = ProviderOauth::where('name',$this->argument('provider'))->singleOrFail();
|
|
||||||
if (! ($to=$so->token($uo)))
|
if (! ($to=$so->token($uo)))
|
||||||
abort(500,sprintf('Unknown Tokens for [%s]',$uo->email));
|
abort(500,sprintf('Unknown Tokens for [%s]',$uo->email));
|
||||||
|
|
||||||
$api = $to->API();
|
$api = $to->API();
|
||||||
foreach ($api->getPayments() as $acc)
|
foreach ($api->getPayments() as $acc)
|
||||||
Job::dispatchSync($to,$acc);
|
Job::dispatchSync($to,$acc);
|
||||||
|
|
||||||
|
return self::SUCCESS;
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,27 +1,26 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace App\Console\Commands;
|
namespace App\Console\Commands\Intuit;
|
||||||
|
|
||||||
use Illuminate\Console\Command;
|
use Illuminate\Console\Command;
|
||||||
use Illuminate\Support\Facades\Config;
|
|
||||||
|
|
||||||
use App\Models\{ProviderOauth,Site,User};
|
use App\Models\{ProviderOauth,User};
|
||||||
use App\Jobs\AccountingTaxSync as Job;
|
use App\Jobs\AccountingTaxSync as Job;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Synchronise TAX ids with our taxes.
|
* Synchronise TAX ids with our taxes.
|
||||||
*/
|
*/
|
||||||
class AccountingTaxSync extends Command
|
class TaxSync extends Command
|
||||||
{
|
{
|
||||||
|
private const provider = 'intuit';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The name and signature of the console command.
|
* The name and signature of the console command.
|
||||||
*
|
*
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
protected $signature = 'accounting:tax:sync'
|
protected $signature = 'accounting:tax:sync'
|
||||||
.' {siteid : Site ID}'
|
.' {user? : User Email}';
|
||||||
.' {provider : Provider Name}'
|
|
||||||
.' {user : User Email}';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The console command description.
|
* The console command description.
|
||||||
@ -37,15 +36,14 @@ class AccountingTaxSync extends Command
|
|||||||
*/
|
*/
|
||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
$site = Site::findOrFail($this->argument('siteid'));
|
$uo = User::where('email',$this->argument('user') ?: config('osb.admin'))->singleOrFail();
|
||||||
Config::set('site',$site);
|
|
||||||
|
|
||||||
$uo = User::where('email',$this->argument('user'))->singleOrFail();
|
$so = ProviderOauth::where('name',self::provider)->singleOrFail();
|
||||||
|
|
||||||
$so = ProviderOauth::where('name',$this->argument('provider'))->singleOrFail();
|
|
||||||
if (! ($to=$so->token($uo)))
|
if (! ($to=$so->token($uo)))
|
||||||
abort(500,sprintf('Unknown Tokens for [%s]',$uo->email));
|
abort(500,sprintf('Unknown Tokens for [%s]',$uo->email));
|
||||||
|
|
||||||
Job::dispatchSync($to);
|
Job::dispatchSync($to);
|
||||||
|
|
||||||
|
return self::SUCCESS;
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -16,9 +16,8 @@ class ProviderTokenRefresh extends Command
|
|||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
protected $signature = 'provider:token:refresh'
|
protected $signature = 'provider:token:refresh'
|
||||||
.' {siteid : Site ID}'
|
|
||||||
.' {provider : Supplier Name}'
|
.' {provider : Supplier Name}'
|
||||||
.' {user : User Email}';
|
.' {user? : User Email}';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The console command description.
|
* The console command description.
|
||||||
@ -34,15 +33,14 @@ class ProviderTokenRefresh extends Command
|
|||||||
*/
|
*/
|
||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
$site = Site::findOrFail($this->argument('siteid'));
|
$uo = User::where('email',$this->argument('user') ?: config('osb.admin'))->singleOrFail();
|
||||||
Config::set('site',$site);
|
|
||||||
|
|
||||||
$so = ProviderOauth::where('name',$this->argument('provider'))->singleOrFail();
|
$so = ProviderOauth::where('name',$this->argument('provider'))->singleOrFail();
|
||||||
$uo = User::where('email',$this->argument('user'))->singleOrFail();
|
|
||||||
|
|
||||||
if (($x=$so->tokens->where('user_id',$uo->id))->count() !== 1)
|
if (($x=$so->tokens->where('user_id',$uo->id))->count() !== 1)
|
||||||
abort(500,sprintf('Unknown Tokens for [%s]',$uo->email));
|
abort(500,sprintf('Unknown Tokens for [%s]',$uo->email));
|
||||||
|
|
||||||
Job::dispatchSync($x->pop());
|
Job::dispatchSync($x->pop());
|
||||||
|
|
||||||
|
return self::SUCCESS;
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -2,10 +2,11 @@
|
|||||||
|
|
||||||
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,
|
||||||
@ -187,63 +188,43 @@ 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 Request $request
|
* @param SiteEdit $request
|
||||||
* @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View|\Illuminate\Http\RedirectResponse
|
* @return RedirectResponse
|
||||||
*/
|
*/
|
||||||
public function setup(Request $request)
|
public function setup(SiteEdit $request)
|
||||||
{
|
{
|
||||||
if ($request->post()) {
|
$site = config('site');
|
||||||
$validated = $request->validate([
|
$images = ['site_logo','email_logo'];
|
||||||
'site_name' => 'required|string|min:2|max:255',
|
$validated = collect($request->validated());
|
||||||
'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');
|
// Handle the images
|
||||||
|
foreach($images as $key)
|
||||||
|
if ($x=$request->validated($key))
|
||||||
|
$validated->put($key,$x->storeAs('site/'.$site->site_id,$x->getClientOriginalName(),'public'));
|
||||||
|
|
||||||
// @todo - not currently rendered in the home page
|
foreach ($site->details as $oo)
|
||||||
$validated['social'] = [];
|
if ($validated->has($oo->key)) {
|
||||||
$validated['top_menu'] = [];
|
// Dont set the following keys to null if they are null
|
||||||
|
if (in_array($oo->key,$images) && is_null($validated->get($oo->key)))
|
||||||
|
continue;
|
||||||
|
|
||||||
// Handle the images
|
$oo->value = $validated->get($oo->key) ?: '';
|
||||||
foreach(['site_logo','email_logo'] as $key)
|
$oo->save();
|
||||||
if (array_key_exists($key,$validated))
|
|
||||||
$validated[$key] = ($x=$validated[$key])->storeAs('site/'.$site->site_id,$x->getClientOriginalName(),'public');
|
|
||||||
|
|
||||||
foreach ($site->details as $oo)
|
$validated->forget($oo->key);
|
||||||
if (array_key_exists($oo->key,$validated)) {
|
|
||||||
$oo->value = Arr::get($validated,$oo->key);
|
|
||||||
$oo->save();
|
|
||||||
|
|
||||||
unset($validated[$oo->key]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Left over values to be created.
|
|
||||||
foreach ($validated as $k=>$v) {
|
|
||||||
$oo = new SiteDetail;
|
|
||||||
$oo->key = $k;
|
|
||||||
$oo->value = $v ?: '';
|
|
||||||
|
|
||||||
$site->details()->save($oo);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return redirect()
|
// Left over values to be created.
|
||||||
->back()
|
foreach ($validated as $k=>$v) {
|
||||||
->with('success','Settings saved');
|
$oo = new SiteDetail;
|
||||||
|
$oo->key = $k;
|
||||||
|
$oo->value = $v ?: '';
|
||||||
|
|
||||||
|
$site->details()->save($oo);
|
||||||
}
|
}
|
||||||
|
|
||||||
return view('theme.backend.adminlte.theme.backend.adminlte.a.setup');
|
return redirect()
|
||||||
|
->back()
|
||||||
|
->with('success','Settings saved');
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -7,16 +7,26 @@ use Illuminate\Foundation\Auth\SendsPasswordResetEmails;
|
|||||||
|
|
||||||
class ForgotPasswordController extends Controller
|
class ForgotPasswordController extends Controller
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
| Password Reset Controller
|
| Password Reset Controller
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
|
||||||
| This controller is responsible for handling password reset emails and
|
| This controller is responsible for handling password reset emails and
|
||||||
| includes a trait which assists in sending these notifications from
|
| includes a trait which assists in sending these notifications from
|
||||||
| your application to your users. Feel free to explore this trait.
|
| your application to your users. Feel free to explore this trait.
|
||||||
|
|
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use SendsPasswordResetEmails;
|
use SendsPasswordResetEmails;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display the form to request a password reset link.
|
||||||
|
*
|
||||||
|
* @return \Illuminate\View\View
|
||||||
|
*/
|
||||||
|
public function showLinkRequestForm()
|
||||||
|
{
|
||||||
|
return view('adminlte::auth.passwords.email');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,26 +4,46 @@ namespace App\Http\Controllers\Auth;
|
|||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use Illuminate\Foundation\Auth\ResetsPasswords;
|
use Illuminate\Foundation\Auth\ResetsPasswords;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
class ResetPasswordController extends Controller
|
class ResetPasswordController extends Controller
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
| Password Reset Controller
|
| Password Reset Controller
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
|
||||||
| This controller is responsible for handling password reset requests
|
| This controller is responsible for handling password reset requests
|
||||||
| and uses a simple trait to include this behavior. You're free to
|
| and uses a simple trait to include this behavior. You're free to
|
||||||
| explore this trait and override any methods you wish to tweak.
|
| explore this trait and override any methods you wish to tweak.
|
||||||
|
|
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use ResetsPasswords;
|
use ResetsPasswords;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Where to redirect users after resetting their password.
|
* Where to redirect users after resetting their password.
|
||||||
*
|
*
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
protected $redirectTo = '/home';
|
protected $redirectTo = '/home';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display the password reset view for the given token.
|
||||||
|
*
|
||||||
|
* If no token is present, display the link request form.
|
||||||
|
*
|
||||||
|
* @param \Illuminate\Http\Request $request
|
||||||
|
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
|
||||||
|
*/
|
||||||
|
public function showResetForm(Request $request)
|
||||||
|
{
|
||||||
|
$token = $request->route()->parameter('token');
|
||||||
|
|
||||||
|
return view('adminlte::auth.passwords.reset')
|
||||||
|
->with([
|
||||||
|
'token' => $token,
|
||||||
|
'email' => $request->email
|
||||||
|
]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,14 +3,38 @@
|
|||||||
namespace App\Http\Controllers;
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
use Carbon\Carbon;
|
use Carbon\Carbon;
|
||||||
|
use Illuminate\Http\RedirectResponse;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Arr;
|
||||||
|
use Illuminate\Support\Facades\Hash;
|
||||||
use Illuminate\Support\Facades\Session;
|
use Illuminate\Support\Facades\Session;
|
||||||
use Illuminate\Validation\Rule;
|
use Illuminate\Validation\Rule;
|
||||||
|
|
||||||
|
use App\Http\Requests\UserEdit;
|
||||||
use App\Models\{Supplier,User};
|
use App\Models\{Supplier,User};
|
||||||
|
|
||||||
class UserController extends Controller
|
class UserController extends Controller
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* Update user settings
|
||||||
|
*
|
||||||
|
* @param UserEdit $request
|
||||||
|
* @param User $o
|
||||||
|
* @return RedirectResponse
|
||||||
|
*/
|
||||||
|
public function edit(UserEdit $request,User $o): RedirectResponse
|
||||||
|
{
|
||||||
|
foreach (Arr::except($request->validated(),['password']) as $field => $value)
|
||||||
|
$o->{$field} = $value;
|
||||||
|
|
||||||
|
if ($x=$request->validated('password'))
|
||||||
|
$o->password = Hash::make($x);
|
||||||
|
|
||||||
|
return redirect()
|
||||||
|
->back()
|
||||||
|
->with('success',($o->isDirty() && $o->save()) ? 'User Updated' : 'No Changes');
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add a supplier to a user's profile
|
* Add a supplier to a user's profile
|
||||||
*
|
*
|
||||||
|
@ -6,7 +6,6 @@ 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;
|
||||||
|
|
||||||
@ -19,11 +18,13 @@ use App\Models\Site;
|
|||||||
class SetSite
|
class SetSite
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* @param Request $request
|
* Handle an incoming request.
|
||||||
* @param Closure $next
|
*
|
||||||
|
* @param Request $request
|
||||||
|
* @param \Closure $next
|
||||||
* @return mixed
|
* @return mixed
|
||||||
*/
|
*/
|
||||||
public function handle(Request $request,Closure $next)
|
public function handle(Request $request,\Closure $next): mixed
|
||||||
{
|
{
|
||||||
$so = new Site;
|
$so = new Site;
|
||||||
|
|
||||||
|
43
app/Http/Requests/SiteEdit.php
Normal file
43
app/Http/Requests/SiteEdit.php
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
<?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',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
39
app/Http/Requests/UserEdit.php
Normal file
39
app/Http/Requests/UserEdit.php
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Requests;
|
||||||
|
|
||||||
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
use Illuminate\Validation\Rules\Password;
|
||||||
|
|
||||||
|
class UserEdit extends FormRequest
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Determine if the user is authorized to make this request.
|
||||||
|
*/
|
||||||
|
public function authorize(): bool
|
||||||
|
{
|
||||||
|
return Auth::id() === $this->route('o')->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the validation rules that apply to the request.
|
||||||
|
*
|
||||||
|
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
|
||||||
|
*/
|
||||||
|
public function rules(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'email'=>'required|email|min:5',
|
||||||
|
'password'=>['nullable','confirmed',Password::min(8)],
|
||||||
|
'firstname'=>'required|min:2',
|
||||||
|
'lastname'=>'required|min:2',
|
||||||
|
'address1'=>'required|min:8',
|
||||||
|
'address2'=>'nullable|min:8',
|
||||||
|
'city'=>'required|min:4',
|
||||||
|
'state'=>'required|min:3|max:3',
|
||||||
|
'postcode'=>'required|min:4|max:4',
|
||||||
|
'country_id'=>'required|exists:countries,id'
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
@ -102,7 +102,7 @@ class Account extends Model implements IDs
|
|||||||
public function invoices()
|
public function invoices()
|
||||||
{
|
{
|
||||||
return $this->hasMany(Invoice::class)
|
return $this->hasMany(Invoice::class)
|
||||||
->with(['items.taxes','paymentitems.payment']);
|
->with(['items.taxes','payment_items.payment']);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -3,21 +3,16 @@
|
|||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Leenooks\Traits\ScopeActive;
|
||||||
|
|
||||||
class Country extends Model
|
class Country extends Model
|
||||||
{
|
{
|
||||||
|
use ScopeActive;
|
||||||
|
|
||||||
public $timestamps = FALSE;
|
public $timestamps = FALSE;
|
||||||
|
|
||||||
/* RELATIONS */
|
/* RELATIONS */
|
||||||
|
|
||||||
/**
|
|
||||||
* The currency this country belongs to
|
|
||||||
*/
|
|
||||||
public function currency()
|
|
||||||
{
|
|
||||||
return $this->belongsTo(Currency::class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function taxes()
|
public function taxes()
|
||||||
{
|
{
|
||||||
return $this->hasMany(Tax::class);
|
return $this->hasMany(Tax::class);
|
||||||
|
@ -1,29 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Models;
|
|
||||||
|
|
||||||
use Illuminate\Database\Eloquent\Model;
|
|
||||||
|
|
||||||
class Currency extends Model
|
|
||||||
{
|
|
||||||
public $timestamps = FALSE;
|
|
||||||
|
|
||||||
const ROUND_HALF_UP = 1;
|
|
||||||
const ROUND_HALF_DOWN = 2;
|
|
||||||
const ROUND_HALF_EVEN = 3;
|
|
||||||
const ROUND_HALF_ODD = 4;
|
|
||||||
|
|
||||||
/* RELATIONS */
|
|
||||||
|
|
||||||
public function country()
|
|
||||||
{
|
|
||||||
return $this->hasOne(Country::class);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* METHODS */
|
|
||||||
|
|
||||||
public function round($value,$mode=self::ROUND_HALF_UP)
|
|
||||||
{
|
|
||||||
return round($value,$this->rounding,$mode);
|
|
||||||
}
|
|
||||||
}
|
|
@ -7,6 +7,7 @@ use Clarkeash\Doorman\Facades\Doorman;
|
|||||||
use Clarkeash\Doorman\Models\Invite;
|
use Clarkeash\Doorman\Models\Invite;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Illuminate\Support\Arr;
|
use Illuminate\Support\Arr;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
use Leenooks\Casts\LeenooksCarbon;
|
use Leenooks\Casts\LeenooksCarbon;
|
||||||
use Leenooks\Traits\ScopeActive;
|
use Leenooks\Traits\ScopeActive;
|
||||||
|
|
||||||
@ -19,16 +20,16 @@ use App\Traits\PushNew;
|
|||||||
* Invoices that belong to an Account
|
* Invoices that belong to an Account
|
||||||
*
|
*
|
||||||
* Attributes for services:
|
* Attributes for services:
|
||||||
|
* + created_at : Date the invoice was created
|
||||||
* + due : Balance due on an invoice
|
* + due : Balance due on an invoice
|
||||||
* + due_date : Date the invoice is due
|
* + due_at : Date the invoice is due
|
||||||
* + invoice_date : Date the invoice was created
|
|
||||||
* + lid : Local ID for invoice
|
* + lid : Local ID for invoice
|
||||||
* + paid : Total of payments received (excluding pending)
|
* + paid : Total of payments received (excluding pending)
|
||||||
* + paid_date : Date the invoice was paid in full
|
* + paid_date : Date the invoice was paid in full
|
||||||
* + paid_pending : Total of pending payments received
|
* + paid_pending : Total of pending payments received
|
||||||
* + sid : System ID for invoice
|
* + sid : System ID for invoice
|
||||||
* + sub_total : Invoice sub-total before taxes
|
* + sub_total : Invoice sub-total before taxes
|
||||||
* + total_tax : Invoices total of taxes
|
* + tax_total : Invoices total of taxes
|
||||||
* + total : Invoice total
|
* + total : Invoice total
|
||||||
*
|
*
|
||||||
* @package App\Models
|
* @package App\Models
|
||||||
@ -97,13 +98,13 @@ class Invoice extends Model implements IDs
|
|||||||
// Array of items that can be updated with PushNew
|
// Array of items that can be updated with PushNew
|
||||||
protected $pushable = ['items'];
|
protected $pushable = ['items'];
|
||||||
|
|
||||||
/*
|
|
||||||
protected $with = [
|
protected $with = [
|
||||||
'account.country.currency',
|
'items_active:id,start_at,stop_at,quantity,price_base,discount_amt,item_type,product_id,service_id,invoice_id',
|
||||||
'items.taxes',
|
'items_active.taxes:id,invoice_item_id,amount,tax_id',
|
||||||
'paymentitems'
|
'items_active.product:id',
|
||||||
|
'items_active.product.translate:id,product_id,name_short,name_detail',
|
||||||
|
'payment_items_active:id,amount,payment_id,invoice_id',
|
||||||
];
|
];
|
||||||
*/
|
|
||||||
|
|
||||||
/* STATIC METHODS */
|
/* STATIC METHODS */
|
||||||
|
|
||||||
@ -181,29 +182,58 @@ class Invoice extends Model implements IDs
|
|||||||
|
|
||||||
/* RELATIONS */
|
/* RELATIONS */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Account this invoice belongs to
|
||||||
|
*/
|
||||||
public function account()
|
public function account()
|
||||||
{
|
{
|
||||||
return $this->belongsTo(Account::class);
|
return $this->belongsTo(Account::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Items on this invoice belongs to
|
||||||
|
*/
|
||||||
public function items()
|
public function items()
|
||||||
{
|
{
|
||||||
return $this->hasMany(InvoiceItem::class)
|
return $this->hasMany(InvoiceItem::class)
|
||||||
->where('active',TRUE)
|
|
||||||
->with(['taxes','product']);
|
->with(['taxes','product']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Active items on this invoice belongs to
|
||||||
|
*/
|
||||||
|
public function items_active()
|
||||||
|
{
|
||||||
|
return $this->items()
|
||||||
|
->where('active',TRUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Payments applied to this invoice
|
||||||
|
*/
|
||||||
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')
|
||||||
->active();
|
->where('active',TRUE);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function paymentitems()
|
/**
|
||||||
|
* Payment items attached to this invoice
|
||||||
|
*/
|
||||||
|
public function payment_items()
|
||||||
{
|
{
|
||||||
return $this->hasMany(PaymentItem::class);
|
return $this->hasMany(PaymentItem::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function payment_items_active()
|
||||||
|
{
|
||||||
|
return $this->payment_items()
|
||||||
|
->where('payment_items.active',TRUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 3rd party provider details to this invoice (eg: accounting providers)
|
||||||
|
*/
|
||||||
public function providers()
|
public function providers()
|
||||||
{
|
{
|
||||||
return $this->belongsToMany(ProviderOauth::class,'invoice__provider')
|
return $this->belongsToMany(ProviderOauth::class,'invoice__provider')
|
||||||
@ -236,31 +266,6 @@ class Invoice extends Model implements IDs
|
|||||||
return sprintf('%3.2f',$this->getTotalAttribute()-$this->getPaidAttribute());
|
return sprintf('%3.2f',$this->getTotalAttribute()-$this->getPaidAttribute());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @return mixed
|
|
||||||
* @todo Change references to due_at to use due_date
|
|
||||||
*/
|
|
||||||
public function getDueDateAttribute(): Carbon
|
|
||||||
{
|
|
||||||
return $this->due_at;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Date the invoices was created
|
|
||||||
*
|
|
||||||
* @return Carbon
|
|
||||||
*/
|
|
||||||
public function getInvoiceDateAttribute(): Carbon
|
|
||||||
{
|
|
||||||
return $this->created_at;
|
|
||||||
}
|
|
||||||
|
|
||||||
// @todo Move this to a site configuration
|
|
||||||
public function getInvoiceTextAttribute()
|
|
||||||
{
|
|
||||||
return sprintf('Thank you for using %s for your Internet Services.',config('site')->site_name);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Total of payments received for this invoice
|
* Total of payments received for this invoice
|
||||||
* excluding pending payments
|
* excluding pending payments
|
||||||
@ -269,9 +274,7 @@ class Invoice extends Model implements IDs
|
|||||||
*/
|
*/
|
||||||
public function getPaidAttribute(): float
|
public function getPaidAttribute(): float
|
||||||
{
|
{
|
||||||
return $this->paymentitems
|
return $this->payment_items_active->sum('amount');
|
||||||
->filter(function($item) { return ! $item->payment->pending_status && $item->payment->active; })
|
|
||||||
->sum('amount');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -282,11 +285,13 @@ class Invoice extends Model implements IDs
|
|||||||
*/
|
*/
|
||||||
public function getPaidDateAttribute(): ?Carbon
|
public function getPaidDateAttribute(): ?Carbon
|
||||||
{
|
{
|
||||||
|
// If the invoice still has a due balance, its not paid
|
||||||
if ($this->getDueAttribute())
|
if ($this->getDueAttribute())
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
$o = $this->payments
|
$o = $this
|
||||||
->filter(function($item) { return ! $item->pending_status; })
|
->payments
|
||||||
|
->filter(fn($item)=>(! $item->pending_status))
|
||||||
->last();
|
->last();
|
||||||
|
|
||||||
return $o?->paid_at;
|
return $o?->paid_at;
|
||||||
@ -299,8 +304,8 @@ class Invoice extends Model implements IDs
|
|||||||
*/
|
*/
|
||||||
public function getPaidPendingAttribute(): float
|
public function getPaidPendingAttribute(): float
|
||||||
{
|
{
|
||||||
return $this->paymentitems
|
return $this->payment_items
|
||||||
->filter(function($item) { return $item->payment->pending_status; })
|
->filter(fn($item)=>$item->payment->pending_status)
|
||||||
->sum('amount');
|
->sum('amount');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -311,28 +316,17 @@ class Invoice extends Model implements IDs
|
|||||||
*/
|
*/
|
||||||
public function getSubTotalAttribute(): float
|
public function getSubTotalAttribute(): float
|
||||||
{
|
{
|
||||||
return $this->items->where('active',TRUE)->sum('sub_total');
|
return $this->items_active->sum('sub_total');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the invoices taxes total
|
* Get the invoices taxes total
|
||||||
*
|
*
|
||||||
* @return float
|
* @return float
|
||||||
* @deprecated use getTotalTaxAttribute();
|
|
||||||
*/
|
*/
|
||||||
public function getTaxTotalAttribute(): float
|
public function getTaxTotalAttribute(): float
|
||||||
{
|
{
|
||||||
return $this->getTotalTaxAttribute();
|
return $this->items_active->sum('tax');
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the invoices taxes total
|
|
||||||
*
|
|
||||||
* @return float
|
|
||||||
*/
|
|
||||||
public function getTotalTaxAttribute(): float
|
|
||||||
{
|
|
||||||
return $this->items->where('active',TRUE)->sum('tax');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -342,17 +336,11 @@ class Invoice extends Model implements IDs
|
|||||||
*/
|
*/
|
||||||
public function getTotalAttribute(): float
|
public function getTotalAttribute(): float
|
||||||
{
|
{
|
||||||
return $this->getSubTotalAttribute()+$this->getTotalTaxAttribute();
|
return $this->getSubTotalAttribute()+$this->getTaxTotalAttribute();
|
||||||
}
|
}
|
||||||
|
|
||||||
/* METHODS */
|
/* METHODS */
|
||||||
|
|
||||||
// @todo This shouldnt be here - current should be handled at an account level.
|
|
||||||
public function currency()
|
|
||||||
{
|
|
||||||
return $this->account->country->currency;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return a download link for non-auth downloads
|
* Return a download link for non-auth downloads
|
||||||
*
|
*
|
||||||
@ -366,57 +354,37 @@ class Invoice extends Model implements IDs
|
|||||||
$tokendate = ($x=Carbon::now()->addDays(21)) > ($y=$this->due_at->addDays(21)) ? $x : $y;
|
$tokendate = ($x=Carbon::now()->addDays(21)) > ($y=$this->due_at->addDays(21)) ? $x : $y;
|
||||||
|
|
||||||
// Extend the expire date
|
// Extend the expire date
|
||||||
if ($io AND ($tokendate > $io->valid_until)) {
|
if ($io && ($tokendate > $io->valid_until)) {
|
||||||
$io->valid_until = $tokendate;
|
$io->valid_until = $tokendate;
|
||||||
$io->save();
|
$io->save();
|
||||||
}
|
}
|
||||||
|
|
||||||
$code = (! $io) ? Doorman::generate()->for($this->account->user->email)->uses(0)->expiresOn($tokendate)->make()->first()->code : $io->code;
|
$code = (! $io)
|
||||||
|
? Doorman::generate()
|
||||||
|
->for($this->account->user->email)
|
||||||
|
->uses(0)
|
||||||
|
->expiresOn($tokendate)
|
||||||
|
->make()
|
||||||
|
->first()
|
||||||
|
->code
|
||||||
|
: $io->code;
|
||||||
|
|
||||||
return url('u/invoice',[$this->id,'email',$code]);
|
return url('u/invoice',[$this->id,'email',$code]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// @todo document
|
/**
|
||||||
public function products()
|
* Return all the items on an invoice for a particular service and product
|
||||||
|
*
|
||||||
|
* @param Product $po
|
||||||
|
* @param Service $so
|
||||||
|
* @return Collection
|
||||||
|
*/
|
||||||
|
public function product_service_items(Product $po,Service $so): Collection
|
||||||
{
|
{
|
||||||
$return = collect();
|
return $this
|
||||||
|
->items_active
|
||||||
foreach ($this->items->groupBy('product_id') as $o) {
|
->filter(fn($item)=>($item->product_id === $po->id) && ($item->service_id === $so->id))
|
||||||
$po = $o->first()->product;
|
->sortBy('item_type');
|
||||||
$po->count = count($o->pluck('service_id')->unique());
|
|
||||||
|
|
||||||
$return->push($po);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $return->sortBy(function ($item) {
|
|
||||||
return $item->name;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// @todo document
|
|
||||||
public function product_services(Product $po)
|
|
||||||
{
|
|
||||||
$return = collect();
|
|
||||||
|
|
||||||
$this->items->load(['service']);
|
|
||||||
|
|
||||||
foreach ($this->items->filter(function ($item) use ($po) {
|
|
||||||
return $item->product_id == $po->id;
|
|
||||||
}) as $o)
|
|
||||||
{
|
|
||||||
$so = $o->service;
|
|
||||||
$return->push($so);
|
|
||||||
};
|
|
||||||
|
|
||||||
return $return->unique()->sortBy('name');
|
|
||||||
}
|
|
||||||
|
|
||||||
// @todo document
|
|
||||||
public function product_service_items(Product $po,Service $so)
|
|
||||||
{
|
|
||||||
return $this->items->filter(function ($item) use ($po,$so) {
|
|
||||||
return $item->product_id == $po->id AND $item->service_id == $so->id;
|
|
||||||
})->filter()->sortBy('item_type');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -439,6 +407,7 @@ class Invoice extends Model implements IDs
|
|||||||
*
|
*
|
||||||
* @param array $options
|
* @param array $options
|
||||||
* @return bool
|
* @return bool
|
||||||
|
* @todo Change this to a saving event
|
||||||
*/
|
*/
|
||||||
public function save(array $options = [])
|
public function save(array $options = [])
|
||||||
{
|
{
|
||||||
@ -453,4 +422,29 @@ class Invoice extends Model implements IDs
|
|||||||
|
|
||||||
return parent::save($options);
|
return parent::save($options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Group the invoice items by product ID, returning the number of products and total
|
||||||
|
*
|
||||||
|
* @return Collection
|
||||||
|
*/
|
||||||
|
public function summary_products(): Collection
|
||||||
|
{
|
||||||
|
$return = collect();
|
||||||
|
|
||||||
|
foreach ($this->items_active->groupBy('product_id') as $o) {
|
||||||
|
$po = $o->first()->product;
|
||||||
|
$po->count = count($o->pluck('service_id')->unique());
|
||||||
|
|
||||||
|
$return->push([
|
||||||
|
'product' => $o->first()->product,
|
||||||
|
'services' => $o->pluck('service_id')->unique(),
|
||||||
|
'sub_total' => $o->sum('sub_total'),
|
||||||
|
'tax_total' => $o->sum('tax'),
|
||||||
|
'total' => $o->sum('total'),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $return->sortBy('product.name');
|
||||||
|
}
|
||||||
}
|
}
|
@ -125,7 +125,7 @@ class InvoiceItem extends Model
|
|||||||
*/
|
*/
|
||||||
public function getSubTotalAttribute(): float
|
public function getSubTotalAttribute(): float
|
||||||
{
|
{
|
||||||
return sprintf('%3.2f',$this->quantity * $this->price_base - $this->discount_amt);
|
return sprintf('%3.2f',$this->quantity * ($this->price_base - $this->discount_amt));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -44,11 +44,6 @@ class Site extends Model
|
|||||||
return $this->belongsTo(Country::class);
|
return $this->belongsTo(Country::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function currency()
|
|
||||||
{
|
|
||||||
return $this->belongsTo(Currency::class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function details()
|
public function details()
|
||||||
{
|
{
|
||||||
return $this->hasMany(SiteDetail::class,NULL,'site_id');
|
return $this->hasMany(SiteDetail::class,NULL,'site_id');
|
||||||
|
@ -6,9 +6,11 @@ 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;
|
use CompositeKeys,SiteID;
|
||||||
|
|
||||||
protected $casts = [
|
protected $casts = [
|
||||||
'social' => 'array',
|
'social' => 'array',
|
||||||
@ -55,13 +57,6 @@ class SiteDetail extends Model
|
|||||||
|
|
||||||
public $timestamps = FALSE;
|
public $timestamps = FALSE;
|
||||||
|
|
||||||
/* RELATIONS */
|
|
||||||
|
|
||||||
public function site()
|
|
||||||
{
|
|
||||||
return $this->belongsTo(Site::class);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ATTRIBUTES */
|
/* ATTRIBUTES */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -82,12 +77,11 @@ 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 string
|
* @return void
|
||||||
* @throws \Exception
|
* @throws \Exception
|
||||||
*/
|
*/
|
||||||
public function setValueAttribute($value)
|
public function setValueAttribute($value): void
|
||||||
{
|
{
|
||||||
// Check that the value can be set
|
// Check that the value can be set
|
||||||
if (! $this->key)
|
if (! $this->key)
|
||||||
@ -111,7 +105,7 @@ class SiteDetail extends Model
|
|||||||
* @return mixed
|
* @return mixed
|
||||||
* @throws \Exception
|
* @throws \Exception
|
||||||
*/
|
*/
|
||||||
public static function sample($key)
|
public static function sample($key): mixed
|
||||||
{
|
{
|
||||||
return Arr::get(self::sampleData,$key);
|
return Arr::get(self::sampleData,$key);
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,7 @@ 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
|
||||||
@ -23,12 +24,13 @@ use App\Notifications\ResetPassword as ResetPasswordNotification;
|
|||||||
*/
|
*/
|
||||||
class User extends Authenticatable implements IDs
|
class User extends Authenticatable implements IDs
|
||||||
{
|
{
|
||||||
use HasFactory,HasApiTokens,Notifiable,UserSwitch,ScopeActive;
|
use HasFactory,HasApiTokens,Notifiable,UserSwitch,ScopeActive,SiteID;
|
||||||
|
|
||||||
private const CACHE_TIME = 3600;
|
private const CACHE_TIME = 3600;
|
||||||
|
|
||||||
protected $casts = [
|
protected $casts = [
|
||||||
'last_access' => 'datetime:Y-m-d H:i:s',
|
'last_access' => 'datetime:Y-m-d H:i:s',
|
||||||
|
'passkey' => 'json',
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Notifications;
|
namespace App\Notifications;
|
||||||
|
|
||||||
|
use App\Models\Site;
|
||||||
use Illuminate\Bus\Queueable;
|
use Illuminate\Bus\Queueable;
|
||||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
use Illuminate\Auth\Notifications\ResetPassword as ResetPasswordNotification;
|
use Illuminate\Auth\Notifications\ResetPassword as ResetPasswordNotification;
|
||||||
@ -9,25 +10,25 @@ use Illuminate\Notifications\Messages\MailMessage;
|
|||||||
|
|
||||||
class ResetPassword extends ResetPasswordNotification implements ShouldQueue
|
class ResetPassword extends ResetPasswordNotification implements ShouldQueue
|
||||||
{
|
{
|
||||||
use Queueable;
|
use Queueable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Build the mail representation of the notification.
|
* Build the mail representation of the notification.
|
||||||
*
|
*
|
||||||
* @param mixed $notifiable
|
* @param mixed $notifiable
|
||||||
* @return \Illuminate\Notifications\Messages\MailMessage
|
* @return MailMessage
|
||||||
*/
|
*/
|
||||||
public function toMail($notifiable)
|
public function toMail($notifiable): MailMessage
|
||||||
{
|
{
|
||||||
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'=>$notifiable->site,
|
||||||
'user'=>$notifiable,
|
'user'=>$notifiable,
|
||||||
'reset_link'=>route('password.reset',$this->token,true),
|
'reset_link'=>route('password.reset',$this->token,true),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -4,11 +4,13 @@ namespace App\Providers;
|
|||||||
|
|
||||||
use Illuminate\Support\Facades\Gate;
|
use Illuminate\Support\Facades\Gate;
|
||||||
use Illuminate\Support\ServiceProvider;
|
use Illuminate\Support\ServiceProvider;
|
||||||
|
use Intuit\Traits\IntuitSocialite;
|
||||||
use Leenooks\Traits\SingleOrFail;
|
use Leenooks\Traits\SingleOrFail;
|
||||||
|
|
||||||
class AppServiceProvider extends ServiceProvider
|
class AppServiceProvider extends ServiceProvider
|
||||||
{
|
{
|
||||||
use SingleOrFail;
|
use SingleOrFail;
|
||||||
|
use IntuitSocialite;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Register any application services.
|
* Register any application services.
|
||||||
@ -32,5 +34,7 @@ class AppServiceProvider extends ServiceProvider
|
|||||||
Gate::define('reseller', function ($user) {
|
Gate::define('reseller', function ($user) {
|
||||||
return $user->isReseller();
|
return $user->isReseller();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$this->bootIntuitSocialite();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,9 @@
|
|||||||
use Illuminate\Foundation\Application;
|
use Illuminate\Foundation\Application;
|
||||||
use Illuminate\Foundation\Configuration\Exceptions;
|
use Illuminate\Foundation\Configuration\Exceptions;
|
||||||
use Illuminate\Foundation\Configuration\Middleware;
|
use Illuminate\Foundation\Configuration\Middleware;
|
||||||
|
use Leenooks\Http\Middleware\ActiveUser;
|
||||||
|
|
||||||
|
use App\Http\Middleware\{Role,SetSite};
|
||||||
|
|
||||||
return Application::configure(basePath: dirname(__DIR__))
|
return Application::configure(basePath: dirname(__DIR__))
|
||||||
->withRouting(
|
->withRouting(
|
||||||
@ -12,11 +15,12 @@ return Application::configure(basePath: dirname(__DIR__))
|
|||||||
)
|
)
|
||||||
->withMiddleware(function (Middleware $middleware) {
|
->withMiddleware(function (Middleware $middleware) {
|
||||||
$middleware->append([
|
$middleware->append([
|
||||||
\App\Http\Middleware\SetSite::class,
|
SetSite::class,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$middleware->alias([
|
$middleware->alias([
|
||||||
'role' => \App\Http\Middleware\Role::class,
|
'activeuser' => ActiveUser::class,
|
||||||
|
'role' => Role::class,
|
||||||
]);
|
]);
|
||||||
})
|
})
|
||||||
->withExceptions(function (Exceptions $exceptions) {
|
->withExceptions(function (Exceptions $exceptions) {
|
||||||
|
@ -14,10 +14,11 @@
|
|||||||
"laravel/dreamscape": "^0.1.0",
|
"laravel/dreamscape": "^0.1.0",
|
||||||
"laravel/framework": "^11.0",
|
"laravel/framework": "^11.0",
|
||||||
"laravel/intuit": "^0.1.7",
|
"laravel/intuit": "^0.1.7",
|
||||||
"laravel/leenooks": "^11.0",
|
|
||||||
"laravel/passport": "^12.0",
|
"laravel/passport": "^12.0",
|
||||||
"laravel/socialite": "^5.15",
|
"laravel/socialite": "^5.15",
|
||||||
"laravel/ui": "^4.5",
|
"laravel/ui": "^4.5",
|
||||||
|
"leenooks/laravel": "^11.1",
|
||||||
|
"leenooks/passkey": "^0.2",
|
||||||
"paypal/paypal-checkout-sdk": "^1.0",
|
"paypal/paypal-checkout-sdk": "^1.0",
|
||||||
"repat/laravel-job-models": "^0.9",
|
"repat/laravel-job-models": "^0.9",
|
||||||
"web-auth/webauthn-lib": "^4.4"
|
"web-auth/webauthn-lib": "^4.4"
|
||||||
@ -61,6 +62,10 @@
|
|||||||
"laravel-console-summary": {
|
"laravel-console-summary": {
|
||||||
"type": "vcs",
|
"type": "vcs",
|
||||||
"url": "https://github.com/leenooks/laravel-console-summary"
|
"url": "https://github.com/leenooks/laravel-console-summary"
|
||||||
|
},
|
||||||
|
"passkey": {
|
||||||
|
"type": "vcs",
|
||||||
|
"url": "https://gitea.dege.au/laravel/passkey.git"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
591
composer.lock
generated
591
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@ -2,4 +2,6 @@
|
|||||||
|
|
||||||
return [
|
return [
|
||||||
'language_id' => 1,
|
'language_id' => 1,
|
||||||
|
'invoice_text' => 'Thank you for using our Internet Services.',
|
||||||
|
'admin' => env('APP_ADMIN'),
|
||||||
];
|
];
|
258
public/passkey/passkey.js
vendored
Normal file
258
public/passkey/passkey.js
vendored
Normal file
@ -0,0 +1,258 @@
|
|||||||
|
/*
|
||||||
|
* Passkey Implementation
|
||||||
|
*/
|
||||||
|
let passkey_debug = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a ArrayBuffer to Base64
|
||||||
|
* @param {ArrayBuffer} buffer
|
||||||
|
* @returns {String}
|
||||||
|
*/
|
||||||
|
function arrayBufferToBase64(buffer) {
|
||||||
|
let binary = '';
|
||||||
|
let bytes = new Uint8Array(buffer);
|
||||||
|
let len = bytes.byteLength;
|
||||||
|
for (let i = 0; i < len; i++) {
|
||||||
|
binary += String.fromCharCode( bytes[ i ] );
|
||||||
|
}
|
||||||
|
return window.btoa(binary);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* convert RFC 1342-like base64 strings to array buffer
|
||||||
|
* @param {mixed} obj
|
||||||
|
* @returns {undefined}
|
||||||
|
*/
|
||||||
|
function recursiveBase64StrToArrayBuffer(obj) {
|
||||||
|
let prefix = '=?BINARY?B?';
|
||||||
|
let suffix = '?=';
|
||||||
|
if (typeof obj === 'object') {
|
||||||
|
for (let key in obj) {
|
||||||
|
if (typeof obj[key] === 'string') {
|
||||||
|
let str = obj[key];
|
||||||
|
if (str.substring(0, prefix.length) === prefix && str.substring(str.length - suffix.length) === suffix) {
|
||||||
|
str = str.substring(prefix.length, str.length - suffix.length);
|
||||||
|
|
||||||
|
let binary_string = window.atob(str);
|
||||||
|
let len = binary_string.length;
|
||||||
|
let bytes = new Uint8Array(len);
|
||||||
|
for (let i = 0; i < len; i++) {
|
||||||
|
bytes[i] = binary_string.charCodeAt(i);
|
||||||
|
}
|
||||||
|
obj[key] = bytes.buffer;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
recursiveBase64StrToArrayBuffer(obj[key]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function passkey_check_browser()
|
||||||
|
{
|
||||||
|
// check browser support
|
||||||
|
if ((! window.fetch) || (! navigator.credentials) || (! navigator.credentials.create))
|
||||||
|
throw new Error('Browser not supported.');
|
||||||
|
|
||||||
|
/*
|
||||||
|
// Availability of `window.PublicKeyCredential` means WebAuthn is usable.
|
||||||
|
// `isUserVerifyingPlatformAuthenticatorAvailable` means the feature detection is usable.
|
||||||
|
// `isConditionalMediationAvailable` means the feature detection is usable.
|
||||||
|
if (window.PublicKeyCredential &&
|
||||||
|
PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable &&
|
||||||
|
PublicKeyCredential.isConditionalMediationAvailable) {
|
||||||
|
// Check if user verifying platform authenticator is available.
|
||||||
|
Promise.all([
|
||||||
|
PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable(),
|
||||||
|
PublicKeyCredential.isConditionalMediationAvailable(),
|
||||||
|
]).then(results => {
|
||||||
|
if (results.every(r => r === true)) {
|
||||||
|
// Display "Create a new passkey" button
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (passkey_debug)
|
||||||
|
console.log('Passkey: Browser OK');
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register/Create a passkey for a user
|
||||||
|
*/
|
||||||
|
async function passkey_register(csrf_token,icon_dom,icon,icon_shell_current,icon_shell_success,icon_shell_fail)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
if (! passkey_check_browser())
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Change our icon so that it is obvious we are doing something
|
||||||
|
icon_dom.find('i').removeClass(icon).addClass('spinner-grow spinner-grow-sm');
|
||||||
|
|
||||||
|
// Get our arguments
|
||||||
|
var createArgs;
|
||||||
|
$.ajax({
|
||||||
|
url: '/passkey/register',
|
||||||
|
type: 'GET',
|
||||||
|
dataType: 'json',
|
||||||
|
async: false,
|
||||||
|
cache: false,
|
||||||
|
success: function(data) {
|
||||||
|
if (passkey_debug)
|
||||||
|
console.log('Passkey: Get Register Success');
|
||||||
|
|
||||||
|
recursiveBase64StrToArrayBuffer(data);
|
||||||
|
createArgs = data;
|
||||||
|
},
|
||||||
|
error: function(e,status,error) {
|
||||||
|
throw new Error(status || 'Unknown error occurred');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create credentials
|
||||||
|
try {
|
||||||
|
const cred = await navigator.credentials.create(createArgs);
|
||||||
|
|
||||||
|
const authenticatorAttestationResponse = {
|
||||||
|
id: cred.id,
|
||||||
|
rawId: arrayBufferToBase64(cred.rawId),
|
||||||
|
transports: cred.response.getTransports ? cred.response.getTransports() : null,
|
||||||
|
clientDataJSON: cred.response.clientDataJSON ? arrayBufferToBase64(cred.response.clientDataJSON) : null,
|
||||||
|
attestationObject: cred.response.attestationObject ? arrayBufferToBase64(cred.response.attestationObject) : null,
|
||||||
|
authenticatorAttachment: cred.authenticatorAttachment,
|
||||||
|
_token: csrf_token,
|
||||||
|
};
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: '/passkey/check',
|
||||||
|
type: 'POST',
|
||||||
|
data: authenticatorAttestationResponse,
|
||||||
|
cache: false,
|
||||||
|
success: function(data) {
|
||||||
|
if (passkey_debug)
|
||||||
|
console.log('Passkey: Registration Success');
|
||||||
|
|
||||||
|
icon_dom.find('i').addClass(icon).removeClass('spinner-grow spinner-grow-sm');
|
||||||
|
icon_dom.removeClass(icon_shell_current).addClass(icon_shell_success);
|
||||||
|
},
|
||||||
|
error: function(e,status,error) {
|
||||||
|
throw new Error(status || 'Unknown error occurred');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (status) {
|
||||||
|
if (passkey_debug)
|
||||||
|
console.log(status || 'Passkey: User Aborted Register');
|
||||||
|
|
||||||
|
// Restore the icon
|
||||||
|
icon_dom.removeClass(icon_shell_current).addClass(icon_shell_fail).find('i').addClass(icon).removeClass('spinner-grow spinner-grow-sm');
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
window.alert(err || 'An UNKNOWN error occurred?');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check a passkey being presented
|
||||||
|
*/
|
||||||
|
async function passkey_check(csrf_token,redirect)
|
||||||
|
{
|
||||||
|
if (passkey_debug)
|
||||||
|
console.log('Passkey: Check User Passkey');
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (! passkey_check_browser())
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Get our arguments
|
||||||
|
var getArgs;
|
||||||
|
$.ajax({
|
||||||
|
url: '/passkey/get',
|
||||||
|
type: 'GET',
|
||||||
|
dataType: 'json',
|
||||||
|
async: false,
|
||||||
|
cache: false,
|
||||||
|
success: function(data) {
|
||||||
|
if (passkey_debug)
|
||||||
|
console.log('Passkey: Get Args Success');
|
||||||
|
|
||||||
|
recursiveBase64StrToArrayBuffer(data);
|
||||||
|
getArgs = data;
|
||||||
|
},
|
||||||
|
error: function(e,status,error) {
|
||||||
|
throw new Error(status || 'Unknown error occurred');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// check credentials with hardware
|
||||||
|
const cred = await navigator.credentials.get(getArgs);
|
||||||
|
|
||||||
|
// create object for transmission to server
|
||||||
|
const authenticatorAttestationResponse = {
|
||||||
|
id: cred.rawId ? arrayBufferToBase64(cred.rawId) : null,
|
||||||
|
clientDataJSON: cred.response.clientDataJSON ? arrayBufferToBase64(cred.response.clientDataJSON) : null,
|
||||||
|
authenticatorData: cred.response.authenticatorData ? arrayBufferToBase64(cred.response.authenticatorData) : null,
|
||||||
|
signature: cred.response.signature ? arrayBufferToBase64(cred.response.signature) : null,
|
||||||
|
userHandle: cred.response.userHandle ? arrayBufferToBase64(cred.response.userHandle) : null,
|
||||||
|
_token: csrf_token
|
||||||
|
};
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: '/passkey/process',
|
||||||
|
type: 'POST',
|
||||||
|
data: authenticatorAttestationResponse,
|
||||||
|
cache: false,
|
||||||
|
success: function(data) {
|
||||||
|
if (passkey_debug)
|
||||||
|
console.log('Passkey: Process Success');
|
||||||
|
|
||||||
|
// Direct to the home page
|
||||||
|
window.location.href = (redirect !== undefined) ? redirect : '/';
|
||||||
|
},
|
||||||
|
error: function(e,status,error) {
|
||||||
|
throw new Error(status || 'Unknown error occurred');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
window.alert(err || 'An UNKNOWN error occurred?');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function passkey_create(object,csrf,icon,icon_class_current,icon_class_success,icon_class_nop)
|
||||||
|
{
|
||||||
|
if (passkey_debug)
|
||||||
|
console.log('Passkey: Create Click');
|
||||||
|
|
||||||
|
// Availability of `window.PublicKeyCredential` means WebAuthn is usable.
|
||||||
|
// `isUserVerifyingPlatformAuthenticatorAvailable` means the feature detection is usable.
|
||||||
|
// `sConditionalMediationAvailable` means the feature detection is usable.
|
||||||
|
if (window.PublicKeyCredential &&
|
||||||
|
PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable &&
|
||||||
|
PublicKeyCredential.isConditionalMediationAvailable) {
|
||||||
|
// Check if user verifying platform authenticator is available.
|
||||||
|
Promise.all([
|
||||||
|
PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable(),
|
||||||
|
PublicKeyCredential.isConditionalMediationAvailable(),
|
||||||
|
]).then(results => {
|
||||||
|
if (passkey_debug)
|
||||||
|
console.log('Passkey: Browser Supported');
|
||||||
|
|
||||||
|
if (results.every(r => r === true)) {
|
||||||
|
passkey_register(csrf,object,icon,icon_class_current,icon_class_success,icon_class_nop);
|
||||||
|
} else {
|
||||||
|
alert('It seems that passkey is NOT supported by your browse (B)');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
} else {
|
||||||
|
alert('It seems that passkey is NOT supported by your browser (A)');
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
@ -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->products() as $po)
|
@foreach ($invoice->summary_products() as $item)
|
||||||
| {{ $po->count }} | {{ $po->product_id }} | {{ $po->name }} | ${{ number_format($invoice->items->filter(function($item) use ($po) {return $item->product_id == $po->id; })->sum('total'),$invoice->currency()->rounding) }} |
|
| {{ $item['services']->count() }} | {{ $item['product']->lid }} | {{ $item['product']->name }} | ${{ number_format($item['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) }} |
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
@if(($x=$o->invoices()
|
@if(($x=$o->invoices()
|
||||||
->where('active',TRUE)
|
->where('active',TRUE)
|
||||||
->orderBy('due_at')
|
->orderBy('due_at')
|
||||||
->with(['items.taxes','paymentitems.payment','account'])
|
->with(['items.taxes','payment_items.payment','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">
|
||||||
@ -22,12 +22,12 @@
|
|||||||
@foreach ($x as $io)
|
@foreach ($x as $io)
|
||||||
<tr>
|
<tr>
|
||||||
<td><a href="{{ url('u/invoice',[$io->id]) }}">{{ $io->sid }}</a></td>
|
<td><a href="{{ url('u/invoice',[$io->id]) }}">{{ $io->sid }}</a></td>
|
||||||
<td>{{ $io->invoice_date->format('Y-m-d') }}</td>
|
<td>{{ $io->created_at->format('Y-m-d') }}</td>
|
||||||
<td>{{ $io->due_at->format('Y-m-d') }}</td>
|
<td>{{ $io->due_at->format('Y-m-d') }}</td>
|
||||||
<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->paymentitems->filter(function($item) use ($pid) { return $item->payment_id == $pid; })) ? $x->sum('amount') : 0,2) }}">
|
<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) }}">
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@endforeach
|
@endforeach
|
||||||
|
@ -21,9 +21,7 @@
|
|||||||
<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>
|
||||||
@if(session()->has('success'))
|
<x-leenooks::button.success class="float-right"/>
|
||||||
<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">
|
||||||
@ -31,182 +29,69 @@
|
|||||||
@csrf
|
@csrf
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-4">
|
<div class="col-12 col-sm-6 col-lg-4">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-12">
|
<div class="col">
|
||||||
<div class="form-group has-validation">
|
<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/>
|
||||||
<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-10">
|
<div class="col">
|
||||||
<div class="form-group has-validation">
|
<x-leenooks::form.text name="site_address1" label="Address" placeholder="Address..." :value="$site->site_address1" required/>
|
||||||
<label for="site_logo">Site Logo</label>
|
<x-leenooks::form.text name="site_address2" helper="At Least 1 address line required" :value="$site->site_address2"/>
|
||||||
<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-10">
|
<div class="col-12 col-md-8">
|
||||||
<div class="form-group has-validation">
|
<x-leenooks::form.text name="site_city" label="City" feedback="City is required" :value="$site->site_city" required/>
|
||||||
<label for="email_logo">Email Logo</label>
|
</div>
|
||||||
<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) }}">
|
</div>
|
||||||
<span class="invalid-feedback" role="alert">
|
|
||||||
@error('email_logo')
|
<div class="row">
|
||||||
{{ $message }}
|
<div class="col-12 col-md-4">
|
||||||
@enderror
|
<x-leenooks::form.text name="site_state" label="State" feedback="State is required" :value="$site->site_state" required/>
|
||||||
</span>
|
</div>
|
||||||
</div>
|
<div class="col-12 col-md-4">
|
||||||
|
<x-leenooks::form.text name="site_postcode" label="Post Code" feedback="Postcode is required" :value="$site->site_postcode" required/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-4">
|
<div class="col-12 col-sm-6 col-lg-4">
|
||||||
<div class="row">
|
<x-leenooks::form.text name="site_phone" label="Phone" :value="$site->site_phone"/>
|
||||||
<div class="col-12">
|
<x-leenooks::form.text name="site_fax" label="Fax" :value="$site->site_fax"/>
|
||||||
<div class="form-group has-validation">
|
<x-leenooks::form.email name="site_email" label="Email" feedback="Email is required" :value="$site->site_email" required/>
|
||||||
<label for="site_address1">Address Lines</label>
|
<x-leenooks::form.text name="site_tax" label="Tax Number" prepend="<small>ABN</small>" feedback="Tax Number is required" :value="$site->site_tax" required/>
|
||||||
<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>
|
|
||||||
<input type="text" class="form-control form-control-border" id="site_address2" name="site_address2" placeholder="Address2" value="{{ old('site_address2',$site->site_address2) }}">
|
|
||||||
<span class="invalid-feedback" role="alert">
|
|
||||||
@error('site_address1')
|
|
||||||
{{ $message }}
|
|
||||||
@else
|
|
||||||
Atleast 1 address line required.
|
|
||||||
@enderror
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-12">
|
|
||||||
<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>
|
||||||
|
|
||||||
<div class="col-4">
|
<div class="col-12 col-sm-6 col-lg-4">
|
||||||
<div class="form-group has-validation">
|
<div class="row">
|
||||||
<label for="site_phone">Phone</label>
|
<div class="col">
|
||||||
<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) }}">
|
<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>
|
||||||
<span class="invalid-feedback" role="alert">
|
</div>
|
||||||
@error('site_phone')
|
|
||||||
{{ $message }}
|
|
||||||
@enderror
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group has-validation">
|
<div class="row">
|
||||||
<label for="site_fax">Fax</label>
|
<div class="col">
|
||||||
<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) }}">
|
<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>
|
||||||
<span class="invalid-feedback" role="alert">
|
</div>
|
||||||
@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>
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<div class="form-group has-validation">
|
<x-leenooks::form.textarea name="site_description" label="Organisation Description" placeholder="Site Description..." helper="Brief description of site." :value="$site->site_description"/>
|
||||||
<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>
|
</div>
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<a href="{{ url('/home') }}" class="btn btn-danger">Cancel</a>
|
<x-leenooks::button.cancel/>
|
||||||
@can('wholesaler')
|
@can('wholesaler')
|
||||||
<button type="submit" name="submit" class="btn btn-success mr-0 float-right">@if ($site->exists)Save @else Add @endif</button>
|
<x-leenooks::button.submit class="float-right">Save</x-leenooks::button.submit>
|
||||||
@endcan
|
@endcan
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
@if ($x=$user->accounts_all->count())
|
@if($x=$o->accounts_all->count())
|
||||||
<table class="table table-striped table-hover" id="accounts">
|
<table class="table table-striped table-hover" id="accounts">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
@ -14,7 +14,10 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@foreach ($user->accounts_all as $ao)
|
@foreach($o->accounts_all as $ao)
|
||||||
|
@php
|
||||||
|
$ao->load(['services:id,active,account_id']);
|
||||||
|
@endphp
|
||||||
<tr>
|
<tr>
|
||||||
<td><a href="{{ url('r/switch/start',$ao->user_id) }}"><i class="fas fa-external-link-alt"></i></a></td>
|
<td><a href="{{ url('r/switch/start',$ao->user_id) }}"><i class="fas fa-external-link-alt"></i></a></td>
|
||||||
<td>{{ $ao->name }}</td>
|
<td>{{ $ao->name }}</td>
|
||||||
|
@ -1,4 +1,8 @@
|
|||||||
<!-- $o = Account::class -->
|
<!-- $o = Account::class -->
|
||||||
|
@php
|
||||||
|
$o->load(['services_active.invoiced_service_items_active_recent']);
|
||||||
|
@endphp
|
||||||
|
|
||||||
<!-- Show active services -->
|
<!-- Show active services -->
|
||||||
<div class="card card-light">
|
<div class="card card-light">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
@ -6,7 +10,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
@if (($x=$o->services->where('active',TRUE))->count())
|
@if (($x=$o->services_active)->count())
|
||||||
<table class="table table-striped table-hover w-100" id="services_active_{{ $ao->id }}">
|
<table class="table table-striped table-hover w-100" id="services_active_{{ $ao->id }}">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
|
@ -1,3 +1,8 @@
|
|||||||
|
@php
|
||||||
|
use App\Models\{Account,Service};
|
||||||
|
$acts = $o->accounts_all->pluck('id');
|
||||||
|
@endphp
|
||||||
|
|
||||||
@if($user->isReseller() && ($o->accounts->count() <= 2) && ($x=$o->accounts->pluck('providers')->flatten())->count())
|
@if($user->isReseller() && ($o->accounts->count() <= 2) && ($x=$o->accounts->pluck('providers')->flatten())->count())
|
||||||
<div class="col-12 col-sm-4 col-md-2">
|
<div class="col-12 col-sm-4 col-md-2">
|
||||||
<div class="info-box">
|
<div class="info-box">
|
||||||
@ -31,7 +36,7 @@
|
|||||||
|
|
||||||
<div class="info-box-content">
|
<div class="info-box-content">
|
||||||
<span class="info-box-text">Active Services</span>
|
<span class="info-box-text">Active Services</span>
|
||||||
<span class="info-box-number">{{ $o->accounts_all->map(fn($item)=>$item->services->where('active',TRUE)->count())->sum() }} <small>/{{ $o->accounts_all->map(fn($item)=>$item->services->count())->sum() }}</small></span>
|
<span class="info-box-number">{{ Service::active()->whereIn('account_id',$acts)->count() }} <small>/{{ Service::whereIn('account_id',$acts)->count() }}</small></span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -42,7 +47,7 @@
|
|||||||
|
|
||||||
<div class="info-box-content">
|
<div class="info-box-content">
|
||||||
<span class="info-box-text">Account Balance</span>
|
<span class="info-box-text">Account Balance</span>
|
||||||
<span class="info-box-number"><small>$</small> {{ number_format(($x=$o->accounts_all->map(fn($item)=>$item->invoiceSummaryDue()->get()->pluck('_balance'))->flatten())->sum(),2) }}</span>
|
<span class="info-box-number"><small>$</small> {{ number_format(($x=Account::InvoicesDue()->filter(fn($item)=>$acts->contains($item->account_id)))->sum('_balance'),2) }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,3 +1,7 @@
|
|||||||
|
@php
|
||||||
|
use App\Models\{Checkout,Service};
|
||||||
|
@endphp
|
||||||
|
|
||||||
<!-- $o = Invoice::class -->
|
<!-- $o = Invoice::class -->
|
||||||
@extends('adminlte::layouts.app')
|
@extends('adminlte::layouts.app')
|
||||||
|
|
||||||
@ -49,7 +53,7 @@
|
|||||||
{!! $o->account->address->join('<br>') !!}
|
{!! $o->account->address->join('<br>') !!}
|
||||||
<br><br>
|
<br><br>
|
||||||
<strong>Email:</strong> {{ $o->account->user->email }}<br>
|
<strong>Email:</strong> {{ $o->account->user->email }}<br>
|
||||||
@if ($o->account->phone)
|
@if($o->account->phone)
|
||||||
<strong>Phone:</strong> {{ $o->account->phone }}<br>
|
<strong>Phone:</strong> {{ $o->account->phone }}<br>
|
||||||
@endif
|
@endif
|
||||||
</address>
|
</address>
|
||||||
@ -58,7 +62,7 @@
|
|||||||
<div class="ml-auto col-3">
|
<div class="ml-auto col-3">
|
||||||
<table class="table table-borderless text-right" style="font-size: 1.1rem;">
|
<table class="table table-borderless text-right" style="font-size: 1.1rem;">
|
||||||
<tr >
|
<tr >
|
||||||
<td class="p-0">Issue Date:</td><td class="p-0"><strong>{{ $o->invoice_date->format('Y-m-d') }}</strong></td>
|
<td class="p-0">Issue Date:</td><td class="p-0"><strong>{{ $o->created_at->format('Y-m-d') }}</strong></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr >
|
<tr >
|
||||||
<td class="p-0">Account:</td><td class="p-0"><strong>{{ $o->account->sid }}</strong></td>
|
<td class="p-0">Account:</td><td class="p-0"><strong>{{ $o->account->sid }}</strong></td>
|
||||||
@ -70,12 +74,12 @@
|
|||||||
<td class="p-0">Payment Due:</td><td class="p-0"><strong>{{ $o->due_at->format('Y-m-d') }}</strong></td>
|
<td class="p-0">Payment Due:</td><td class="p-0"><strong>{{ $o->due_at->format('Y-m-d') }}</strong></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td class="p-0">This Invoice Due:</td><td class="p-0"><strong>${{ number_format($o->total,$o->currency()->rounding) }}</strong></td>
|
<td class="p-0">This Invoice Due:</td><td class="p-0"><strong>${{ number_format($o->total,2) }}</strong></td>
|
||||||
</tr>
|
</tr>
|
||||||
{{--
|
{{--
|
||||||
<!-- @todo -->
|
<!-- @todo -->
|
||||||
<tr>
|
<tr>
|
||||||
<td class="p-0">Total Account Due:</td><td class="p-0"><strong>${{ number_format($o->account->due,$o->currency()->rounding) }}</strong></td>
|
<td class="p-0">Total Account Due:</td><td class="p-0"><strong>${{ number_format($o->account->due,2) }}</strong></td>
|
||||||
</tr>
|
</tr>
|
||||||
--}}
|
--}}
|
||||||
</table>
|
</table>
|
||||||
@ -97,29 +101,29 @@
|
|||||||
</thead>
|
</thead>
|
||||||
|
|
||||||
<tbody>
|
<tbody>
|
||||||
@foreach ($o->products() as $po)
|
@foreach($o->summary_products() as $item)
|
||||||
<tr id="invoice-services">
|
<tr id="invoice-services">
|
||||||
<td>{{ $po->count }}</td>
|
<td>{{ $item['services']->count() }}</td>
|
||||||
<td>#{{ $po->lid }}</td>
|
<td>#{{ $item['product']->lid }}</td>
|
||||||
<td colspan="2">{{ $po->name }}</td>
|
<td colspan="2">{{ $item['product']->name }}</td>
|
||||||
<td colspan="3" class="text-right">${{ number_format($o->items->filter(function($item) use ($po) {return $item->product_id == $po->id; })->sum('total'),$o->currency()->rounding) }}</td>
|
<td colspan="3" class="text-right">${{ number_format($item['total'],2) }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
@foreach ($o->product_services($po) as $so)
|
@foreach(Service::whereIn('id',$item['services'])->get() as $so)
|
||||||
<tr id="invoice-service-items" class="invoice-services @if($o->products()->count() > 1) d-print-table-row @endif">
|
<tr id="invoice-service-items" class="invoice-services @if($item['services']->count() > 1) d-print-table-row @endif">
|
||||||
<td colspan="2"> </td>
|
<td colspan="2"> </td>
|
||||||
<td colspan="2">Service: <strong>{{ $so->sid }}: [{{ $so->name }}]</strong></td>
|
<td colspan="2">Service: <strong>{{ $so->sid }}: [{{ $so->name }}]</strong></td>
|
||||||
<td> </td>
|
<td> </td>
|
||||||
<td class="text-right">${{ number_format($o->product_service_items($po,$so)->sum('total'),$o->currency()->rounding) }}</td>
|
<td class="text-right">${{ number_format($o->product_service_items($item['product'],$so)->sum('total'),2) }}</td>
|
||||||
<td> </td>
|
<td> </td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
@foreach ($o->product_service_items($po,$so) as $io)
|
@foreach($o->product_service_items($item['product'],$so) as $io)
|
||||||
<tr class="invoice-service-items d-print-table-row">
|
<tr class="invoice-service-items d-print-table-row">
|
||||||
<td colspan="2"> </td>
|
<td colspan="2"> </td>
|
||||||
<td width="5%"> </td>
|
<td width="5%"> </td>
|
||||||
<td>{{ $io->item_type_name }}</td>
|
<td>{{ $io->item_type_name }}</td>
|
||||||
<td class="text-right">${{ number_format($io->total,$o->currency()->rounding) }}</td>
|
<td class="text-right">${{ number_format($io->total,2) }}</td>
|
||||||
<td colspan="2"> </td>
|
<td colspan="2"> </td>
|
||||||
</tr>
|
</tr>
|
||||||
@endforeach
|
@endforeach
|
||||||
@ -141,18 +145,18 @@
|
|||||||
<p class="lead">Payment Methods:</p>
|
<p class="lead">Payment Methods:</p>
|
||||||
|
|
||||||
<table class="table table-borderless">
|
<table class="table table-borderless">
|
||||||
@foreach (\App\Models\Checkout::available() as $cho)
|
@foreach(Checkout::available() as $cho)
|
||||||
<tr>
|
<tr>
|
||||||
<td style="width: 50px;"><i class="fa-2x fa-fw {{ $cho->icon }}"></i></td>
|
<td style="width: 50px;"><i class="fa-2x fa-fw {{ $cho->icon }}"></i></td>
|
||||||
<td>{{ $cho->name }}</td>
|
<td>{{ $cho->name }}</td>
|
||||||
<td>{{ $cho->description }}</td>
|
<td>{{ $cho->description }}</td>
|
||||||
<td class="w-25">@includeIf('theme.backend.adminlte.payment.widget.plugin.'.strtolower($cho->plugin),['o'=>$cho])</td>
|
<td class="w-25">@includeIf('theme.backend.adminlte.payment.widget.plugin.'.strtolower($cho->plugin),['o'=>$cho])</td>
|
||||||
</tr>
|
</tr>
|
||||||
@endforeach
|
@endforeach
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
<p class="text-muted well well-sm no-shadow" style="position: absolute;bottom: 0;left: 0;">
|
<p class="text-muted well well-sm no-shadow" style="position: absolute;bottom: 0;left: 0;">
|
||||||
{!! $o->invoice_text !!}
|
{{ config('osb.invoice_text') }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -161,12 +165,12 @@
|
|||||||
<table class="table">
|
<table class="table">
|
||||||
<tr>
|
<tr>
|
||||||
<th colspan="3" style="width:50%">Subtotal:</th>
|
<th colspan="3" style="width:50%">Subtotal:</th>
|
||||||
<td colspan="2" class="text-right">${{ number_format($o->sub_total,$o->currency()->rounding) }}</td>
|
<td colspan="2" class="text-right">${{ number_format($o->sub_total,2) }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th> </th>
|
<th> </th>
|
||||||
<th>Tax (GST 10%)</th>
|
<th>Tax (GST 10%)</th>
|
||||||
<td colspan="2" class="text-right">${{ number_format($o->total_tax,$o->currency()->rounding) }}</td>
|
<td colspan="2" class="text-right">${{ number_format($o->tax_total,2) }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th> </th>
|
<th> </th>
|
||||||
@ -176,23 +180,23 @@
|
|||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th colspan="2">Total:</th>
|
<th colspan="2">Total:</th>
|
||||||
<td colspan="2" class="text-right">${{ number_format($o->total,$o->currency()->rounding) }}</td>
|
<td colspan="2" class="text-right">${{ number_format($o->total,2) }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
@if($o->id)
|
@if($o->id)
|
||||||
<tr>
|
<tr>
|
||||||
<th> </th>
|
<th> </th>
|
||||||
<th>Payments To Clear:</th>
|
<th>Payments To Clear:</th>
|
||||||
<td colspan="2" class="text-right">${{ number_format($o->paid_pending,$o->currency()->rounding) }}</td>
|
<td colspan="2" class="text-right">${{ number_format($o->paid_pending,2) }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th> </th>
|
<th> </th>
|
||||||
<th>Payments:</th>
|
<th>Payments:</th>
|
||||||
<td>#{{ $o->payments->pluck('id')->join(', #') }}</td>
|
<td>#{{ $o->payment_items_active->pluck('payment_id')->join(', #') }}</td>
|
||||||
<td class="text-right">${{ number_format($o->paid,$o->currency()->rounding) }}</td>
|
<td class="text-right">${{ number_format($o->paid,2) }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr style="font-size: 145%">
|
<tr style="font-size: 145%">
|
||||||
<th colspan="2">Invoice Due:</th>
|
<th colspan="2">Invoice Due:</th>
|
||||||
<td colspan="2" class="text-right">${{ number_format($o->due,$o->currency()->rounding) }}</td>
|
<td colspan="2" class="text-right">${{ number_format($o->due,2) }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
@endif
|
@endif
|
||||||
</table>
|
</table>
|
||||||
|
126
resources/views/theme/backend/adminlte/user/settings.blade.php
Normal file
126
resources/views/theme/backend/adminlte/user/settings.blade.php
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
@extends('adminlte::layouts.app')
|
||||||
|
|
||||||
|
@section('htmlheader_title')
|
||||||
|
{{ ($o??$user)->role }} Settings
|
||||||
|
@endsection
|
||||||
|
@section('page_title')
|
||||||
|
{{ ($o??$user)->name_full }}
|
||||||
|
@endsection
|
||||||
|
|
||||||
|
@section('contentheader_title')
|
||||||
|
{{ ($o??$user)->name_full }}
|
||||||
|
@endsection
|
||||||
|
@section('contentheader_description')
|
||||||
|
{{ ($o??$user)->role }}
|
||||||
|
@endsection
|
||||||
|
|
||||||
|
@use(App\Models\Country)
|
||||||
|
<!-- ($o??$user)=User::class -->
|
||||||
|
@section('main-content')
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<div class="card-title">Update Settings</div>
|
||||||
|
<x-leenooks::button.success class="float-right"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form method="POST" action="{{ url(request()->path(),[($o??$user)->id]) }}">
|
||||||
|
@csrf
|
||||||
|
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12 col-sm-6">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
<x-leenooks::form.email id="email" name="email" icon="fa-at" label="Email" :value="($o??$user)->email ?? ''"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
<x-leenooks::form.password id="password" name="password" icon="fa-lock" label="Password"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
<x-leenooks::form.password id="password_confirm" name="password_confirmation" icon="fa-lock" label="Password Confirm" helper="Verify Password"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col pb-3">
|
||||||
|
<button type="reset" id="passkey" name="passkey" @class(['btn','btn-success'=>$x=($o??$user)->passkey,'btn-outline-secondary'=>! $x])><i class="fas fa-fw fa-key"></i> Passkey</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-12 col-sm-6">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12 col-md-6">
|
||||||
|
<x-leenooks::form.text name="firstname" icon="fa-headset" label="First Name" :value="($o??$user)->firstname ?? ''"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-12 col-md-6">
|
||||||
|
<x-leenooks::form.text name="lastname" icon="fa-signature" label="Last Name" :value="($o??$user)->lastname ?? ''"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
<x-leenooks::form.text name="address1" icon="fa-map" label="Address" :value="($o??$user)->address1 ?? ''"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
<x-leenooks::form.text name="address2" :value="($o??$user)->address2 ?? ''"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12 col-md-4">
|
||||||
|
<x-leenooks::form.text name="city" label="City" :value="($o??$user)->city ?? ''"/>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-md-4">
|
||||||
|
<x-leenooks::form.text name="state" label="State" :value="($o??$user)->state ?? ''"/>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-md-4">
|
||||||
|
<x-leenooks::form.text name="postcode" label="Post Code" :value="($o??$user)->postcode ?? ''"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12 col-md-6">
|
||||||
|
<x-leenooks::form.select id="country" name="country_id" icon="fa-globe" label="Country" :value="($o??$user)->country_id ?? ''" :options="Country::select(['id','name'])->active()->get()->map(function($item) { $item->value = $item->name; return $item; })->toArray()"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row pt-3">
|
||||||
|
<div class="col">
|
||||||
|
<x-leenooks::button.cancel/>
|
||||||
|
<x-leenooks::button.submit class="float-right">Save</x-leenooks::button.submit>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endsection
|
||||||
|
|
||||||
|
@section('page-scripts')
|
||||||
|
<!-- Passkeys -->
|
||||||
|
<script type='text/javascript' src='{{ asset('/passkey/passkey.js') }}'></script>
|
||||||
|
|
||||||
|
<script type="text/javascript">
|
||||||
|
$(document).ready(function() {
|
||||||
|
$('#passkey').on('click',function(item) {
|
||||||
|
return passkey_create($(this),'{{ csrf_token() }}','fa-key','{{ ($o??$user)->passkey ? 'btn-success' : 'btn-outline-secondary' }}','btn-success','{{ ($o??$user)->passkey ? 'btn-secondary' : 'btn-outline-secondary' }}');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
@append
|
161
resources/views/vendor/adminlte/layouts/partials/mainheader.blade.php
vendored
Normal file
161
resources/views/vendor/adminlte/layouts/partials/mainheader.blade.php
vendored
Normal file
@ -0,0 +1,161 @@
|
|||||||
|
<!-- Main Header -->
|
||||||
|
<!-- Navbar -->
|
||||||
|
<nav class="main-header navbar navbar-expand bg-white navbar-light border-bottom">
|
||||||
|
<!-- Left navbar links -->
|
||||||
|
<ul class="navbar-nav">
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" data-widget="pushmenu" href="#"><i class="fas fa-bars"></i></a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<!-- SEARCH FORM -->
|
||||||
|
<form class="form-inline ml-3">
|
||||||
|
<div class="input-group input-group-sm">
|
||||||
|
<input class="form-control form-control-navbar" name="q" type="search" placeholder="Search" aria-label="Search" autocomplete="off">
|
||||||
|
<div class="input-group-append">
|
||||||
|
<span class="input-group-text"><i class="fas fa-search"></i></span>
|
||||||
|
<span class="p-1 d-none" name="searching"><i class="fas fa-spinner fa-spin" style="margin-top: .33em; width: 1em; height: 1em;"></i></span>
|
||||||
|
</div>
|
||||||
|
<div id="search_results"></div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<!-- Right navbar links -->
|
||||||
|
<ul class="navbar-nav ml-auto">
|
||||||
|
@include('adminlte::layouts.partials.topmenu')
|
||||||
|
|
||||||
|
@if(Auth::check())
|
||||||
|
<!-- Profile Settings -->
|
||||||
|
<li class="nav-item dropdown">
|
||||||
|
<a class="nav-link" data-toggle="dropdown" href="#">
|
||||||
|
<i class="fas fa-cog"></i>
|
||||||
|
</a>
|
||||||
|
<div class="dropdown-menu dropdown-menu-lg dropdown-menu-right">
|
||||||
|
<span class="dropdown-item dropdown-header @if($user->switched) bg-danger @endif">
|
||||||
|
@if($user->switched)
|
||||||
|
SUDO (<small>{{ $user->name ?: 'User Name' }}</small>)
|
||||||
|
@else
|
||||||
|
{{ $user->name ?: 'User Name' }}
|
||||||
|
@endif
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<div class="dropdown-divider"></div>
|
||||||
|
<a href="{{ url('u/settings') }}" class="dropdown-item">
|
||||||
|
<i class="fas fa-user mr-2"></i> Settings
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<div class="dropdown-divider"></div>
|
||||||
|
@if ($user->switched)
|
||||||
|
<a href="{{ url('/admin/switch/stop') }}" class="dropdown-item">
|
||||||
|
<i class="fas fa-sign-out-alt mr-2"></i> {{ trans('adminlte_lang::message.switchoff') }}
|
||||||
|
</a>
|
||||||
|
@else
|
||||||
|
<a href="{{ url('logout') }}" class="dropdown-item">
|
||||||
|
<i class="fas fa-sign-out-alt mr-2"></i> Log Out
|
||||||
|
</a>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
@else
|
||||||
|
<a href="{{ url()->current().'?login=1' }}" class="text-muted pr-2"><i class="fas fa-lock"></i></a>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
<!-- Control Side Bar -->
|
||||||
|
@isset($controlsidebar)
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" data-widget="control-sidebar" data-slide="true" href="#">
|
||||||
|
<i class="fas fa-th"></i>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
@endisset
|
||||||
|
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
@section('page-scripts')
|
||||||
|
<style>
|
||||||
|
/* Solid border */
|
||||||
|
div.typeahead.dropdown-menu > .dropdown-header {
|
||||||
|
color: #000000;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script type="text/javascript">
|
||||||
|
$(document).ready(function() {
|
||||||
|
$('input[name=q]').typeahead({
|
||||||
|
autoSelect: false,
|
||||||
|
scrollHeight: 10,
|
||||||
|
theme: 'bootstrap4',
|
||||||
|
delay: 500,
|
||||||
|
minLength: 2,
|
||||||
|
items: {{ isset($search_limit) ? $search_limit : 100 }},
|
||||||
|
fitToElement: false,
|
||||||
|
selectOnBlur: false,
|
||||||
|
appendTo: "#search_results",
|
||||||
|
source: function (query,process) {
|
||||||
|
search('{{ url("search",['date'=>isset($ido) ? $ido->id : NULL]) }}',query,process);
|
||||||
|
},
|
||||||
|
|
||||||
|
matcher: function () { return true; },
|
||||||
|
|
||||||
|
// Disable sorting and just return the items (items should by the ajax method)
|
||||||
|
sorter: function(items) {
|
||||||
|
return items;
|
||||||
|
},
|
||||||
|
|
||||||
|
updater: function (item) {
|
||||||
|
window.parent.location.href = item.value;
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.on('keyup keypress', function(event) {
|
||||||
|
var key = event.keyCode || event.which;
|
||||||
|
if (key === 13) {
|
||||||
|
event.preventDefault();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
var c=0;
|
||||||
|
var search = _.debounce(function(url,query,process,icon){
|
||||||
|
$.ajax({
|
||||||
|
url : url,
|
||||||
|
type : 'GET',
|
||||||
|
data : 'term=' + query,
|
||||||
|
dataType : 'JSON',
|
||||||
|
async : true,
|
||||||
|
cache : false,
|
||||||
|
beforeSend : function() {
|
||||||
|
if (c++ == 0) {
|
||||||
|
if (icon)
|
||||||
|
$('i[name='+icon+']').addClass("fa-spin");
|
||||||
|
else {
|
||||||
|
$('span[name=searching]').removeClass("d-none");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
success : function(data) {
|
||||||
|
// if json is null, means no match, won't do again.
|
||||||
|
if(data==null || (data.length===0)) return;
|
||||||
|
|
||||||
|
process(data);
|
||||||
|
},
|
||||||
|
complete : function() {
|
||||||
|
if (--c == 0) {
|
||||||
|
if (icon)
|
||||||
|
$('i[name='+icon+']').removeClass("fa-spin");
|
||||||
|
else {
|
||||||
|
$('span[name=searching]').addClass("d-none");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
statusCode: {
|
||||||
|
401: function() {
|
||||||
|
window.parent.location.href = '{{ route('login') }}';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}, 500);
|
||||||
|
</script>
|
||||||
|
@append
|
@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
use Illuminate\Support\Facades\Route;
|
||||||
use Leenooks\Controllers\SwitchUserController;
|
use Leenooks\Controllers\SwitchUserController;
|
||||||
|
|
||||||
use App\Http\Controllers\{AdminController,
|
use App\Http\Controllers\{AdminController,
|
||||||
@ -8,7 +10,6 @@ use App\Http\Controllers\{AdminController,
|
|||||||
CheckoutController,
|
CheckoutController,
|
||||||
HomeController,
|
HomeController,
|
||||||
InvoiceController,
|
InvoiceController,
|
||||||
MediaController,
|
|
||||||
OrderController,
|
OrderController,
|
||||||
PaypalController,
|
PaypalController,
|
||||||
ProductController,
|
ProductController,
|
||||||
@ -16,7 +17,6 @@ use App\Http\Controllers\{AdminController,
|
|||||||
ServiceController,
|
ServiceController,
|
||||||
SupplierController,
|
SupplierController,
|
||||||
UserController,
|
UserController,
|
||||||
WelcomeController,
|
|
||||||
Wholesale\ReportController};
|
Wholesale\ReportController};
|
||||||
use App\Models\Supplier;
|
use App\Models\Supplier;
|
||||||
|
|
||||||
@ -44,6 +44,8 @@ Auth::routes([
|
|||||||
Route::get('logout',[LoginController::class,'logout'])
|
Route::get('logout',[LoginController::class,'logout'])
|
||||||
->name('logout-get');
|
->name('logout-get');
|
||||||
|
|
||||||
|
Route::redirect('passkey/loggedin','/home');
|
||||||
|
|
||||||
// Account linking to OPENID host
|
// Account linking to OPENID host
|
||||||
Route::group([],function() {
|
Route::group([],function() {
|
||||||
Route::get('auth/{socialProvider}',[SocialLoginController::class,'redirectToProvider']);
|
Route::get('auth/{socialProvider}',[SocialLoginController::class,'redirectToProvider']);
|
||||||
@ -58,14 +60,11 @@ Route::get('admin/switch/stop',[SwitchUserController::class,'switch_stop'])
|
|||||||
->middleware('auth')
|
->middleware('auth')
|
||||||
->name('switch.stop');
|
->name('switch.stop');
|
||||||
|
|
||||||
// Generic Image Renderer - Render images that we dont have with a generic image
|
|
||||||
Route::get('image/generic/{width}/{height}/{color}/{name?}',[MediaController::class,'image'])
|
|
||||||
->name('image');
|
|
||||||
|
|
||||||
// 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::match(['get','post'],'setup',[AdminController::class,'setup']);
|
Route::view('setup','theme.backend.adminlte.a.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']);
|
||||||
@ -193,6 +192,9 @@ Route::group(['middleware'=>['auth'],'prefix'=>'u'],function() {
|
|||||||
Route::get('service/{o}/change/{status}',[ServiceController::class,'change'])
|
Route::get('service/{o}/change/{status}',[ServiceController::class,'change'])
|
||||||
->where('o','[0-9]+')
|
->where('o','[0-9]+')
|
||||||
->middleware('can:progress,o,status');
|
->middleware('can:progress,o,status');
|
||||||
|
|
||||||
|
Route::view('settings','theme.backend.adminlte.user.settings');
|
||||||
|
Route::post('settings/{o}',[UserController::class,'edit']);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Doorman Code Routes
|
// Doorman Code Routes
|
||||||
|
Loading…
Reference in New Issue
Block a user