Compare commits

..

2 Commits
master ... test

Author SHA1 Message Date
Deon George
6707790943
Create current schema file 2021-07-13 13:27:26 +10:00
Deon George
6e4c332dea
Try new schema dump to trim migrations 2021-07-13 13:19:14 +10:00
528 changed files with 15696 additions and 25578 deletions

View File

@ -1,37 +1,32 @@
APP_ADMIN=
APP_DEBUG=false
APP_NAME=OSB
APP_NAME_HTML_LONG="<b>Graytech</b>Hosting"
APP_NAME_HTML_SHORT="<b>G</b>H"
APP_ENV=production
APP_KEY=
APP_TIMEZONE=Australia/Melbourne
APP_DEBUG=false
APP_URL=https://www.graytech.net.au
AUTH_PASSWORD_RESET_TOKEN_TABLE=password_resets
LOG_CHANNEL=stack
LOG_CHANNEL=daily
DB_CONNECTION=pgsql
DB_HOST=postgres
DB_PORT=5432
DB_DATABASE=graytech
DB_USERNAME=graytech
DB_PASSWORD=
DB_SCHEMA=billing
DB_CONNECTION=mysql
DB_HOST=database
DB_PORT=3306
DB_DATABASE=database
DB_USERNAME=homestead
DB_PASSWORD=secret
BROADCAST_DRIVER=log
CACHE_STORE=file
CACHE_DRIVER=file
SESSION_DRIVER=file
SESSION_LIFETIME=120
QUEUE_CONNECTION=database
QUEUE_DRIVER=database
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379
MAIL_MAILER=smtp
MAIL_HOST=smtp
MAIL_DRIVER=smtp
MAIL_HOST=MAIL
MAIL_PORT=25
MAIL_USERNAME=null
MAIL_PASSWORD=null
@ -48,13 +43,13 @@ MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"
EZYPAY_TOKEN=
EZYPAY_GUID=
QUICKBOOKS_CLIENT_ID=
QUICKBOOKS_CLIENT_SECRET=
QUICKBOOKS_API_URL=Production
AUTH_GOOGLE_CLIENT_ID=
AUTH_GOOGLE_SECRET=
AUTH_INTUIT_CLIENT_ID=
AUTH_INTUIT_SECRET_KEY=
INTUIT_VERIFYTOKEN=
PAYPAL_MODE=sandbox
PAYPAL_SANDBOX_CLIENT_ID=
PAYPAL_SANDBOX_SECRET=

View File

@ -1,147 +0,0 @@
name: Create Docker Image
run-name: ${{ gitea.actor }} Building Docker Image 🐳
on: [push]
env:
VERSION: latest
DOCKER_HOST: tcp://127.0.0.1:2375
jobs:
# test:
# strategy:
# matrix:
# arch:
# - x86_64
# # arm64
#
# name: Test Application
# runs-on: docker-${{ matrix.arch }}
# container:
# image: gitea.dege.au/docker/php:8.3-fpm-pgsql-server-test
#
# steps:
# - name: Environment Setup
# run: |
# # If we have a proxy use it
# if [ -n "${HTTP_PROXY}" ]; then echo "HTTP PROXY [${HTTP_PROXY}]"; sed -i -e s'/https/http/' /etc/apk/repositories; fi
# # Some pre-reqs
# apk add git nodejs
# ## Some debugging info
# # env|sort
#
# - name: Code Checkout
# uses: actions/checkout@v4
#
# - name: Run Tests
# run: |
# mv .env.testing .env
# # Install Composer and project dependencies.
# mkdir -p ${COMPOSER_HOME}
# if [ -n "${{ secrets.COMPOSER_GITHUB_TOKEN }}" ]; then echo ${{ secrets.COMPOSER_GITHUB_TOKEN }} > ${COMPOSER_HOME}/auth.json; fi
# composer install
# # Generate an application key. Re-cache.
# php artisan key:generate
# php artisan migrate
# php artisan db:seed
# # run laravel tests
# touch storage/app/test/*ZIP storage/app/test/file/*
# XDEBUG_MODE=coverage php vendor/bin/phpunit --coverage-text --colors=never
build:
strategy:
matrix:
arch:
- x86_64
# - arm64
# needs: [test]
name: Build Docker Image
runs-on: docker-${{ matrix.arch }}
container:
image: docker:dind
privileged: true
env:
ARCH: ${{ matrix.arch }}
VERSIONARCH: ${{ env.VERSION }}-${{ env.ARCH }}
steps:
- name: Environment Setup
run: |
# If we have a proxy use it
if [ -n "${HTTP_PROXY}" ]; then echo "HTTP PROXY [${HTTP_PROXY}]"; sed -i -e s'/https/http/' /etc/apk/repositories; fi
# Some pre-reqs
apk add git curl nodejs
# Start docker
( dockerd --host=tcp://0.0.0.0:2375 --tls=false & ) && sleep 3
## Some debugging info
# docker info && docker version
env|sort
echo "PRT: ${{ secrets.PKG_WRITE_TOKEN }}"
- name: Registry FQDN Setup
id: registry
run: |
registry=${{ github.server_url }}
echo "registry=${registry##http*://}" >> "$GITHUB_OUTPUT"
- name: Container Registry Login
uses: docker/login-action@v2
with:
registry: ${{ steps.registry.outputs.registry }}
username: ${{ gitea.actor }}
password: ${{ secrets.PKG_WRITE_TOKEN }}
- name: Code Checkout
uses: actions/checkout@v4
- name: Record version
run: |
pwd
ls -al
echo ${GITHUB_SHA::8} > VERSION
cat VERSION
- name: Build and Push Docker Image
uses: docker/build-push-action@v5
with:
context: .
file: docker/Dockerfile
push: true
tags: "${{ steps.registry.outputs.registry }}/${{ env.GITHUB_REPOSITORY }}:${{ env.VERSIONARCH }}"
manifest:
name: Final Docker Image Manifest
runs-on: docker-x86_64
container:
image: docker:dind
privileged: true
needs: [build]
steps:
- name: Environment Setup
run: |
# If we have a proxy use it
if [ -n "${HTTP_PROXY}" ]; then echo "HTTP PROXY [${HTTP_PROXY}]"; sed -i -e s'/https/http/' /etc/apk/repositories; fi
# Some pre-reqs
apk add git curl nodejs
# Start docker
( dockerd --host=tcp://0.0.0.0:2375 --tls=false & ) && sleep 3
- name: Registry FQDN Setup
id: registry
run: |
registry=${{ github.server_url }}
echo "registry=${registry##http*://}" >> "$GITHUB_OUTPUT"
- name: Container Registry Login
uses: docker/login-action@v2
with:
registry: ${{ steps.registry.outputs.registry }}
username: ${{ gitea.actor }}
password: ${{ secrets.PKG_WRITE_TOKEN }}
- name: Build Docker Manifest
run: |
docker manifest create ${{ steps.registry.outputs.registry }}/${{ env.GITHUB_REPOSITORY }}:${{ env.VERSION }} \
${{ steps.registry.outputs.registry }}/${{ env.GITHUB_REPOSITORY }}:${{ env.VERSION }}-x86_64
# ${{ steps.registry.outputs.registry }}/${{ env.GITHUB_REPOSITORY }}:${{ env.VERSION }}-arm64
docker manifest push --purge ${{ steps.registry.outputs.registry }}/${{ env.GITHUB_REPOSITORY }}:${{ env.VERSION }}

14
.gitlab-ci.yml Normal file
View File

@ -0,0 +1,14 @@
stages:
- test
- build
# This folder is cached between builds
# http://docs.gitlab.com/ce/ci/yaml/README.html#cache
cache:
key: ${CI_COMMIT_REF_SLUG}
paths:
- vendor/
include:
- .gitlab-test.yml
- .gitlab-docker-x86_64.yml

33
.gitlab-docker-x86_64.yml Normal file
View File

@ -0,0 +1,33 @@
docker:
image: docker:latest
stage: build
services:
- docker:dind
variables:
VERSION: latest
CACHETAG: build-${VERSION}
DOCKER_HOST: tcp://docker:2375
tags:
- docker
- x86_64
only:
- master
before_script:
- docker info
- docker version
- echo "$CI_JOB_TOKEN" | docker login -u "$CI_REGISTRY_USER" "$CI_REGISTRY" --password-stdin
- if [ -n "$GITHUB_TOKEN" ]; then cat $GITHUB_TOKEN |base64 -d > auth.json; fi
script:
- if [ -f init ]; then chmod 500 init; fi
- ([ -z "$REFRESH" ] && docker pull ${CI_REGISTRY_IMAGE}:${CACHETAG}) || echo "true"
- echo -n ${CI_COMMIT_SHORT_SHA} > VERSION
- rm -rf vendor/
- docker build --cache-from ${CI_REGISTRY_IMAGE}:${CACHETAG} -t ${CI_REGISTRY_IMAGE}:${VERSION} -t ${CI_REGISTRY_IMAGE}:${CACHETAG} .
- docker push ${CI_REGISTRY_IMAGE}:${VERSION}
- docker push ${CI_REGISTRY_IMAGE}:${CACHETAG}

48
.gitlab-test.yml Normal file
View File

@ -0,0 +1,48 @@
test:
image: registry.leenooks.net/leenooks/php:8.0-fpm-ext-test
stage: test
# NOTE: This service is dependant on project file configuration, which is not there if the cache was deleted
# resulting in the testing to fail on the first run.
services:
- mariadb:10.5
variables:
MYSQL_DATABASE: testing
MYSQL_ROOT_PASSWORD: test
MYSQL_USER: test
MYSQL_PASSWORD: test
tags:
- php
only:
- master
- test
before_script:
- mv .env.testing .env
# Install Composer and project dependencies.
- mkdir -p /root/.config/composer
- if [ -n "$GITHUB_TOKEN" ]; then cat $GITHUB_TOKEN |base64 -d > /root/.config/composer/auth.json ; fi
- composer install
# Add mysql client for schema pre-load
- apt update && apt install -f mariadb-client
# Generate an application key. Re-cache.
- php artisan key:generate --env=testing
- php artisan config:cache --env=testing
- php artisan migrate
- php artisan db:seed
script:
# run laravel tests
- XDEBUG_MODE=coverage php vendor/bin/phpunit --coverage-text --colors=never
# run frontend tests
# if you have any task for testing frontend
# set it in your package.json script
# comment this out if you don't have a frontend test
# npm test

13
Dockerfile Normal file
View File

@ -0,0 +1,13 @@
FROM registry.leenooks.net/leenooks/php:8.0-fpm-image
COPY . /var/www/html/
RUN export COMPOSER_HOME=/var/www/.composer \
&& mkdir -p /var/www/.composer \
&& ([ -r auth.json ] && mv auth.json /var/www/.composer/) || true \
&& touch .composer.refresh \
&& mv .env.example .env \
&& FORCE_PERMS=1 NGINX_START=FALSE /sbin/init \
&& chmod +x /var/www/html/artisan \
&& /var/www/html/artisan storage:link \
&& rm -rf /var/www/.composer

View File

@ -1,29 +0,0 @@
<?php
namespace App\Casts;
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
use Illuminate\Database\Eloquent\Model;
class CollectionOrNull implements CastsAttributes
{
/**
* Cast the given value.
*
* @param array<string, mixed> $attributes
*/
public function get(Model $model, string $key, mixed $value, array $attributes): mixed
{
return collect(json_decode($value, true));
}
/**
* Prepare the given value for storage.
*
* @param array<string, mixed> $attributes
*/
public function set(Model $model, string $key, mixed $value, array $attributes): mixed
{
return count($value) ? json_encode($value) : NULL;
}
}

7
app/Classes/External/Accounting.php vendored Normal file
View File

@ -0,0 +1,7 @@
<?php
namespace App\Classes\External;
abstract class Accounting
{
}

View File

@ -0,0 +1,68 @@
<?php
namespace App\Classes\External\Accounting;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Cache;
use QuickBooksOnline\API\Data\IPPCustomer;
use App\Classes\External\Accounting as Base;
use App\Models\User;
class Quickbooks extends Base
{
private $api = NULL;
public function __construct(User $uo)
{
if (Auth::user())
throw new \Exception('User logged in - *TODO* handle this');
Auth::loginUsingId($uo->id);
$this->api = app('Spinen\QuickBooks\Client');
}
public function getCustomers($refresh=FALSE): Collection
{
if ($refresh)
Cache::forget(__METHOD__);
return Cache::remember(__METHOD__,86400,function() {
return collect($this->api->getDataService()->Query('SELECT * FROM Customer'));
});
}
public function getInvoice(int $id,$refresh=FALSE)
{
if ($refresh)
Cache::forget(__METHOD__.$id);
return Cache::remember(__METHOD__.$id,86400,function() use ($id) {
return $this->api->getDataService()->Query(sprintf("SELECT * FROM Invoice where id = '%s'",$id));
});
}
public function getInvoices($refresh=FALSE): Collection
{
if ($refresh)
Cache::forget(__METHOD__);
return Cache::remember(__METHOD__,86400,function() {
return collect($this->api->getDataService()->Query('SELECT * FROM Invoice'));
});
}
public function updateCustomer(IPPCustomer $r,array $args)
{
$r->sparse = TRUE;
foreach ($args as $k=>$v)
{
$r->{$k} = $v;
}
return $this->api->getDataService()->Update($r);
}
}

View File

@ -30,8 +30,8 @@ class Ezypay extends Payments
$api_remain = Arr::get($result->getHeader('X-RateLimit-Remaining'),0);
$api_reset = Arr::get($result->getHeader('X-RateLimit-Reset'),0);
if ($api_remain === 0) {
Log::error('API Throttle.',['m'=>__METHOD__,'api_reset'=>$api_reset]);
if ($api_remain == 0) {
Log::error('API Throttle.',['m'=>__METHOD__]);
Cache::put('api_throttle',$api_reset,now()->addSeconds($api_reset));
}
@ -45,56 +45,6 @@ class Ezypay extends Payments
});
}
/**
* Get a list of configured customers
*
* @return Collection
* @todo Hard coded at 100 clients - need to make this more dynamic
*
* {#1079
* +"TermsAndConditions": true
* +"Account": {#1082
* +"PaymentMethodId": 0
* }
* +"Address1": "1 Road Street "
* +"BillingStatus": "Active"
* +"BusinessAccountReference": "12345"
* +"CountryCode": "AU"
* +"Email": "user@example.com"
* +"EzypayReferenceNumber": 12345678
* +"Firstname": "Bart"
* +"Id": "6219c42b-8e56-4e4e-af3a-76ddf4b0e4e1"
* +"MobilePhone": ""
* +"Postcode": "1234"
* +"ReferenceId": "01nnnn"
* +"State": "VIC"
* +"Suburb": "TOWN"
* +"Towncity": ""
* +"Surname": "Simpson "
* +"PaymentPlanId": "00000000-0000-0000-0000-000000000000"
* +"DateOfBirth": "1970-01-01T00:00:00.000"
* +"Gender": "M"
* +"DebitType": 0
* +"RecurringAmount": 0.0
* +"Frequency": 0
* +"FrequencyType": 0
* +"StartDate": "0001-01-01T00:00:00.000"
* +"RecurringDebitEndType": 0
* +"TotalAmountCollected": 0.0
* +"MinimumNumberOfPayment": 0
* +"RecurringWithDifferentFirstDebitAmount": 0.0
* +"RecurringDebitFirstDebitDate": "0001-01-01T00:00:00.000"
* +"RecurringDebitAmount": 0.0
* +"RecurringDebitFrequency": 0
* +"RecurringDebitFrequencyType": 0
* +"RecurringDebitStartDate": "0001-01-01T00:00:00.000"
* +"RecurringDebitDifferentFirstAmountEndType": 0
* +"RecurringDebitTotalAmountCollected": 0.0
* +"RecurringDebitMinimumNumberOfPayment": 0
* +"OnceOffAmount": 0.0
* +"OnceOffStartDate": "0001-01-01T00:00:00.000"
* }
*/
public function getCustomers(): Collection
{
return Cache::remember(__METHOD__,86400,function() {
@ -102,29 +52,6 @@ class Ezypay extends Payments
});
}
/**
* Get Specific debits for a client.
*
* @param array $opt
* @return Collection
*
* Illuminate\Support\Collection^ {#1077
* #items: array:4 [
* 0 => {#1826
* +"Amount": 99.99
* +"Id": "76666ef1-106c-458c-9162-e77fe746517c"
* +"CustomerId": "6219c42b-8e56-4e4e-af3a-76ddf4b0e4e1"
* +"Date": "2021-10-01T00:00:00.000"
* +"Status": "Pending"
* }
* 1 => {#2075
* +"Amount": 99.99
* +"Id": "80ba201d-fb6f-4700-b1b7-2b13fbfa91d5"
* +"CustomerId": "6219c42b-8e56-4e4e-af3a-76ddf4b0e4e1"
* +"Date": "2021-09-01T00:00:00.000"
* +"Status": "Pending"
* }
*/
public function getDebits($opt=[]): Collection
{
return Cache::remember(__METHOD__.http_build_query($opt),86400,function() use ($opt) {
@ -138,4 +65,4 @@ class Ezypay extends Payments
return Collect($this->connect('settlements/'.config('services.ezypay.guid').($opt ? '?'.http_build_query($opt) : '')));
});
}
}
}

View File

@ -17,8 +17,6 @@ abstract class Supplier
protected $o = NULL;
protected $_columns = [];
public const traffic_connection_keys = ['user','pass','url'];
public function __construct(Model $o)
{
$this->o = $o;
@ -28,29 +26,24 @@ abstract class Supplier
/**
* Connect and pull down traffic data
*
* @param array $connection
* @param string $type
* @return Collection
* @throws \Exception
*/
public function fetch(array $connection,string $type): Collection
public function fetch(): Collection
{
if (count(array_intersect(array_keys($connection),self::traffic_connection_keys)) !== 3)
throw new \Exception('No or missing connection details for:'.$type);
if ($x=$this->mustPause()) {
Log::notice(sprintf('%s:API Throttle, waiting [%s]...',self::LOGKEY,$x),['m'=>__METHOD__]);
sleep($x);
}
Log::debug(sprintf('%s:Supplier [%d], fetch data for [%s]...',self::LOGKEY,$this->o->id,Arr::get($connection,'last')),['m'=>__METHOD__]);
$key = 'Supplier:'.$this->o->id.Arr::get($connection,'last');
Log::debug(sprintf('%s:Supplier [%d], fetch data for [%s]...',self::LOGKEY,$this->o->id,$this->o->stats_lastupdate),['m'=>__METHOD__]);
$key = 'Supplier:'.$this->o->id.$this->o->stats_lastupdate;
$result = Cache::remember($key,86400,function() {
$client = $this->getClient();
$result = Cache::remember($key,86400,function() use ($connection) {
$response = Http::get(Arr::get($connection,'url'),[
$this->login_user_field => Arr::get($connection,'user'),
$this->login_pass_field => Arr::get($connection,'pass'),
$this->date_field => Arr::get($connection,'last'),
$response = Http::get($this->o->stats_url,[
$this->login_user_field => $this->o->stats_username,
$this->login_pass_field => $this->o->stats_password,
$this->date_field => $this->o->stats_lastupdate->format('Y-m-d'),
]);
// @todo These API rate limiting is untested.
@ -62,6 +55,7 @@ abstract class Supplier
Cache::put('api_throttle',$api_reset,now()->addSeconds($api_reset));
}
//dd($response->header('Content-Type'),$response->headers());
// Assume the supplier provides an ASCII output for text/html
if (preg_match('#^text/html;#',$x=$response->header('Content-Type'))) {
return collect(explode("\n",$response->body()))->filter();

View File

@ -3,10 +3,9 @@
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use App\Jobs\BroadbandTraffic as Job;
use App\Models\Supplier;
use App\Models\AdslSupplier;
class BroadbandTraffic extends Command
{
@ -15,8 +14,7 @@ class BroadbandTraffic extends Command
*
* @var string
*/
protected $signature = 'broadband:traffic:import'.
' {supplier? : Supplier Name}';
protected $signature = 'broadband:traffic:import';
/**
* The console command description.
@ -32,26 +30,7 @@ class BroadbandTraffic extends Command
*/
public function handle()
{
if ($this->argument('supplier')) {
try {
$o = Supplier::active()
->where('name','ilike',$this->argument('supplier'))
->sole();
} catch (ModelNotFoundException $e) {
$this->error(sprintf('Supplier [%s] not found',$this->argument('supplier')));
return self::FAILURE;
}
Job::dispatchSync($o->name);
return self::SUCCESS;
}
foreach (Supplier::active()->get() as $o)
Job::dispatchSync($o->name);
return self::SUCCESS;
foreach (AdslSupplier::active()->get() as $o)
Job::dispatch($o);
}
}

View File

@ -1,54 +0,0 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\Mail;
use App\Mail\Test;
use App\Models\{Site,User};
class EmailTest extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'test-email'
.' {--s|site : Site ID}'
.' {id : User ID}'
.' {email? : Alternative Email}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Send a test email';
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
Config::set(
'site',
$this->option('site')
? Site::findOrFail($this->option('site'))
: Site::where('url',config('app.url'))->sole()
);
$uo = User::find($this->argument('id'));
$result = Mail::to($this->argument('email') ?? $uo->email)
->send(new Test($uo));
$this->info($result->getMessageId());
return self::SUCCESS;
}
}

View File

@ -1,95 +0,0 @@
<?php
namespace App\Console\Commands\Ezypay;
use Carbon\Carbon;
use Illuminate\Console\Command;
use App\Classes\External\Payments\Ezypay;
use App\Models\Account;
class PaymentNext extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'ezypay:payment:next';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Load next payments, and ensure they cover the next invoice';
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
$poo = new Ezypay;
foreach ($poo->getCustomers() as $c) {
if ($c->BillingStatus == 'Inactive') {
$this->comment(sprintf('Ignoring INACTIVE: [%s] %s %s',$c->EzypayReferenceNumber,$c->Firstname,$c->Surname));
continue;
}
// Load Account Details from ReferenceId
$ao = Account::find((int)substr($c->ReferenceId,2,4));
if (! $ao) {
$this->warn(sprintf('Missing: [%s] %s %s (%s)',$c->EzypayReferenceNumber,$c->Firstname,$c->Surname,$c->ReferenceId));
continue;
}
// Get Due Invoices
$invoice_due = $ao->invoiceSummaryDue()->get();
$this->info(sprintf('Account [%s] (%s) has [%d] invoices due, totalling [%3.2f]',
$ao->lid,
$ao->name,
$invoice_due->count(),
($due=$invoice_due->sum('_balance')),
));
$next_pay = $poo->getDebits([
'customerId'=>$c->Id,
'dateFrom'=>now()->format('Y-m-d'),
'dateTo'=>now()->addQuarter()->format('Y-m-d'),
])->reverse()->first();
if ($next_pay->Status !== 'Pending') {
$this->warn(sprintf('- Next payment is not pending for (%s)',$ao->name));
continue;
}
$next_paydate = Carbon::createFromTimeString($next_pay->Date);
if ($next_pay->Amount < $due)
$this->error(sprintf('- Next payment on [%s] for (%s) [%s] not sufficient for outstanding balance [%s]',
$next_paydate->format('Y-m-d'),
$ao->name,
number_format($next_pay->Amount,2),
number_format($due,2)));
elseif ($next_pay->Amount > $due)
$this->warn(sprintf('- Next payment on [%s] for (%s) [%s] is too much for outstanding balance [%s]',
$next_paydate->format('Y-m-d'),
$ao->name,
number_format($next_pay->Amount,2),
number_format($due,2)));
else
$this->info(sprintf('- Next payment on [%s] for (%s) [%s] will cover outstanding balance [%s]',
$next_paydate->format('Y-m-d'),
$ao->name,
number_format($next_pay->Amount,2),
number_format($due,2)));
}
}
}

View File

@ -1,35 +0,0 @@
<?php
namespace App\Console\Commands\Ezypay;
use Illuminate\Console\Command;
use App\Classes\External\Payments\Ezypay;
use App\Jobs\PaymentsImport as Job;
class PaymentsImport extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'ezypay:payment:import';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Retrieve payments from Ezypay';
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
Job::dispatchSync(new Ezypay);
}
}

View File

@ -1,44 +0,0 @@
<?php
namespace App\Console\Commands;
use Carbon\Carbon;
use Illuminate\Console\Command;
use App\Jobs\ImportCosts as Job;
use App\Models\{Site,Supplier};
class ImportCosts extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'costs:import {siteid : Site ID} {supplier : Supplier Name} {file : Filename} {date : Date}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Import Costs from file';
/**
* Execute the console command.
*
* @return void
*/
public function handle()
{
if (! str_starts_with($this->argument('file'),'files/'))
throw new \Exception('Filename must start with files/');
Job::dispatchSync(
Site::findOrFail($this->argument('siteid')),
Supplier::where('name',$this->argument('supplier'))->singleOrFail(),
Carbon::create($this->argument('date')),
$this->argument('file'),
);
}
}

View File

@ -1,58 +0,0 @@
<?php
namespace App\Console\Commands\Intuit;
use Illuminate\Console\Command;
use Intuit\Exceptions\NotTokenException;
use Intuit\Jobs\AccountingCustomerUpdate;
use Intuit\Models\Customer as AccAccount;
use Intuit\Traits\ProviderTokenTrait;
use App\Models\Account;
class AccountAdd extends Command
{
use ProviderTokenTrait;
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'intuit:account:add'
.' {id : Account ID}'
.' {user? : User Email}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Add an account to quickbooks';
/**
* Execute the console command.
*
* @return int
* @throws NotTokenException
*/
public function handle()
{
$o = Account::findOrFail($this->argument('id'));
$acc = new AccAccount;
$acc->PrimaryEmailAddr = (object)['Address'=>$o->user->email];
$acc->ResaleNum = $o->sid;
$acc->GivenName = $o->user->firstname;
$acc->FamilyName = $o->user->lastname;
$acc->CompanyName = $o->name;
$acc->DisplayName = $o->name;
$acc->FullyQualifiedName = $o->name;
$acc->Active = (bool)$o->active;
return AccountingCustomerUpdate::dispatchSync(
$this->providerToken($this->argument('user')),
$acc
);
}
}

View File

@ -1,45 +0,0 @@
<?php
namespace App\Console\Commands\Intuit;
use Illuminate\Console\Command;
use Intuit\Exceptions\NotTokenException;
use Intuit\Traits\ProviderTokenTrait;
use App\Jobs\AccountingAccountSync;
/**
* Synchronise Customers with Accounts
*/
class AccountSync extends Command
{
use ProviderTokenTrait;
/**
* 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
* @throws NotTokenException
*/
public function handle()
{
AccountingAccountSync::dispatchSync($this->providerToken($this->argument('user')));
return self::SUCCESS;
}
}

View File

@ -1,132 +0,0 @@
<?php
namespace App\Console\Commands\Intuit;
use Illuminate\Console\Command;
use Intuit\Jobs\AccountingInvoiceUpdate;
use Intuit\Models\Invoice as AccInvoice;
use Intuit\Traits\ProviderTokenTrait;
use App\Models\Invoice;
class InvoiceAdd extends Command
{
use ProviderTokenTrait;
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'intuit:invoice:add'
.' {id : Invoice ID}'
.' {user? : User Email}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Add an invoice to the accounting provider';
/**
* Execute the console command.
*
* @return int
* @throws \Exception
*/
public function handle()
{
$to = $this->providerToken($this->argument('user'));
$io = Invoice::findOrFail($this->argument('id'));
// Check the customer exists
if ($io->account->providers->where('pivot.provider_oauth_id',$to->provider->id)->count() !== 1)
throw new \Exception(sprintf('Account [%d] for Invoice [%d] not defined',$io->account_id,$io->id));
$ao = $io->account->providers->where('pivot.provider_oauth_id',$to->provider->id)->pop();
// Some validation
if (! $ao->pivot->ref) {
$this->error(sprintf('Accounting not defined for account [%d]',$io->account_id));
return self::FAILURE;
}
$acc = new AccInvoice;
$acc->CustomerRef = (object)['value'=>$ao->pivot->ref];
$acc->DocNumber = $io->lid;
$acc->TxnDate = $io->created_at->format('Y-m-d');
$acc->DueDate = $io->due_at->format('Y-m-d');
$lines = collect();
$c = 0;
$subtotal = 0;
// @todo Group these by ItemRef and the Same Unit Price and Description, so that we can then use quantity to represent the number of them.
foreach ($io->items->groupBy(
fn($item)=>
sprintf('%s.%s.%s.%s',
$item->item_type_name,
$item->price_base,
$item->product?->provider_ref($to->provider),
$item->taxes->pluck('description')->join('|'))) as $os)
{
$key = $os->first();
// Some validation
if (! ($ref=$key->product?->provider_ref($to->provider))) {
$this->error(sprintf('Accounting not defined in product [%d]',$key->product_id));
return self::FAILURE;
}
if ($key->taxes->count() !== 1) {
$this->error(sprintf('Cannot handle when there is not just 1 tax line [%d]',$key->id));
return self::FAILURE;
}
$c++;
$line = new \stdClass;
$line->Id = $c;
$line->DetailType = 'SalesItemLineDetail';
$line->Description = $key->item_type_name;
$line->SalesItemLineDetail = (object)[
'Qty' => $os->sum('quantity'),
'UnitPrice' => $key->price_base,
'ItemRef' => ['value'=>$ref],
// @todo It is assumed there is only 1 tax category
'TaxCodeRef' => ['value'=>$tcf=$key->taxes->first()->tax->provider_ref($to->provider)],
];
$line->Amount = round($os->sum('quantity')*$key->price_base,2);
$subtotal += $line->Amount;
$lines->push($line);
}
$acc->Line = $lines;
// If our subtotal doesnt match, we need to add a tax line
if ($io->subtotal !== $subtotal) {
$acc->TxnTaxDetail = (object)[
'TotalTax' => $x=$io->total-$subtotal,
'TaxLine' => [
(object) [
'Amount' => $x,
'DetailType' => 'TaxLineDetail',
'TaxLineDetail' => (object)[
// @todo It is assumed there is only 1 tax category
'TaxRateRef' => (object)['value'=>$to->API()->getTaxCodeQuery($tcf)->getTaxRateRef()->first()],
'NetAmountTaxable' => $io->subtotal,
]
]
]
];
}
return AccountingInvoiceUpdate::dispatchSync($to,$acc);
}
}

View File

@ -1,41 +0,0 @@
<?php
namespace App\Console\Commands\Intuit;
use Illuminate\Console\Command;
use Intuit\Traits\ProviderTokenTrait;
use App\Jobs\AccountingInvoiceSync as Job;
class InvoiceSync extends Command
{
use ProviderTokenTrait;
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'intuit:invoice:sync'
.' {user? : User Email}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Synchronise invoices with accounting system';
/**
* Execute the console command.
*
* @return int
* @throws \Intuit\Exceptions\NotTokenException
*/
public function handle()
{
Job::dispatchSync($this->providerToken($this->argument('user')));
return self::SUCCESS;
}
}

View File

@ -1,60 +0,0 @@
<?php
namespace App\Console\Commands\Intuit;
use Illuminate\Console\Command;
use Intuit\Exceptions\NotTokenException;
use Intuit\Traits\ProviderTokenTrait;
use App\Models\Product;
/**
* Return a list of products and their accounting id
*/
class ItemList extends Command
{
use ProviderTokenTrait;
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'accounting:item:list'
.' {user? : User Email}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Synchronise items with accounting system';
/**
* Execute the console command.
*
* @return int
* @throws NotTokenException
*/
public function handle()
{
$to = $this->providerToken($this->argument('user'));
// Current Products used by services
$products = Product::select(['products.*'])
->distinct('products.id')
->join('services',['services.product_id'=>'products.id'])
->where('services.active',TRUE)
->get();
foreach ($products as $po) {
if (! $x=$po->provider_ref($to->provider))
$this->error(sprintf('Product [%03d](%s) doesnt have accounting set',$po->id,$po->name));
else
$this->info(sprintf('Product [%03d](%s) set to accounting [%s]',$po->id,$po->name,$x));
}
return self::SUCCESS;
}
}

View File

@ -1,44 +0,0 @@
<?php
namespace App\Console\Commands\Intuit;
use Illuminate\Console\Command;
use Intuit\Traits\ProviderTokenTrait;
use App\Jobs\AccountingPaymentSync as Job;
class PaymentSync extends Command
{
use ProviderTokenTrait;
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'intuit:payment:sync'
.' {user? : User Email}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Synchronise payments with accounting system';
/**
* Execute the console command.
*
* @return int
* @throws \Intuit\Exceptions\NotTokenException
*/
public function handle()
{
$to = $this->providerToken($this->argument('user'));
foreach ($to->API()->getPayments() as $acc)
Job::dispatchSync($to,$acc);
return self::SUCCESS;
}
}

View File

@ -1,44 +0,0 @@
<?php
namespace App\Console\Commands\Intuit;
use Illuminate\Console\Command;
use Intuit\Traits\ProviderTokenTrait;
use App\Jobs\AccountingTaxSync as Job;
/**
* Synchronise TAX ids with our taxes.
*/
class TaxSync extends Command
{
use ProviderTokenTrait;
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'intuit:tax:sync'
.' {user? : User Email}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Synchronise taxes with accounting system';
/**
* Execute the console command.
*
* @return int
* @throws \Intuit\Exceptions\NotTokenException
*/
public function handle()
{
Job::dispatchSync($this->providerToken($this->argument('user')));
return self::SUCCESS;
}
}

View File

@ -2,10 +2,10 @@
namespace App\Console\Commands;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\Mail;
use Illuminate\Console\Command;
use App\Models\{Invoice,Site};
use App\Models\Invoice;
class InvoiceEmail extends Command
{
@ -14,9 +14,7 @@ class InvoiceEmail extends Command
*
* @var string
*/
protected $signature = 'invoice:email'
.' {--s|site : Site ID}'
.' {id?}';
protected $signature = 'invoice:email {id}';
/**
* The console command description.
@ -25,6 +23,16 @@ class InvoiceEmail extends Command
*/
protected $description = 'Email Invoices to be client';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
@ -32,23 +40,19 @@ class InvoiceEmail extends Command
*/
public function handle()
{
Config::set(
'site',
$this->option('site')
? Site::findOrFail($this->option('site'))
: Site::where('url',config('app.url'))->sole()
);
$o = Invoice::findOrFail($this->argument('id'));
try {
$o->send();
Mail::to($o->account->user->email)->send(new \App\Mail\InvoiceEmail($o));
if (Mail::failures()) {
dump('Failure?');
dump(Mail::failures());
} else {
$o->print_status = TRUE;
$o->reminders = $o->reminders('send');
$o->save();
} catch (\Exception $e) {
dd($e);
}
return self::SUCCESS;
}
}

View File

@ -2,11 +2,10 @@
namespace App\Console\Commands;
use App\Models\Account;
use App\Models\Invoice;
use Carbon\Carbon;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Config;
use App\Models\{Account,Invoice,Site};
class InvoiceGenerate extends Command
{
@ -15,11 +14,7 @@ class InvoiceGenerate extends Command
*
* @var string
*/
protected $signature = 'invoice:generate'
.' {--l|list : List Items}'
.' {--p|preview : Preview}'
.' {--s|site : Site ID}'
.' {id?}';
protected $signature = 'invoice:generate {account?} {--p|preview : Preview} {--l|list : List Items}';
/**
* The console command description.
@ -28,6 +23,16 @@ class InvoiceGenerate extends Command
*/
protected $description = 'Generate Invoices to be Sent';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
@ -35,50 +40,35 @@ class InvoiceGenerate extends Command
*/
public function handle()
{
Config::set(
'site',
$this->option('site')
? Site::findOrFail($this->option('site'))
: Site::where('url',config('app.url'))->sole()
);
if ($this->argument('id'))
$accounts = collect()->push(Account::find($this->argument('id')));
if ($this->argument('account'))
$accounts = collect()->push(Account::find($this->argument('account')));
else
$accounts = Account::active()->get();
foreach ($accounts as $o) {
$items = $o->invoice_next(Carbon::now());
if (! $items->count()) {
$this->warn(sprintf('No items for account (%s) [%d]',$o->name,$o->id));
continue;
}
$this->info(sprintf('Account: %s [%d]',$o->name,$o->lid));
$io = new Invoice;
$io->account_id = $o->id;
foreach ($items as $oo)
$io->items_active->push($oo);
foreach ($o->services(TRUE)->get() as $so) {
foreach ($so->next_invoice_items(FALSE) as $ooo)
$io->items->push($ooo);
}
// If there are no items, no reason to do anything
if ($io->total < 0) {
$this->warn(sprintf(' - Invoice totals [%3.2f] - skipping',$io->total));
if (! $io->items->count() OR $io->total < 0)
continue;
}
$io->account_id = $o->id;
if ($this->option('list')) {
$this->line(sprintf('|%4s|%4s|%-50s|%8s|',
$this->warn(sprintf('|%4s|%4s|%-50s|%8s|',
'SID',
'PID',
'Name',
'Amount',
));
foreach ($io->items_active as $oo) {
foreach ($io->items as $oo) {
$this->info(sprintf('|%4s|%4s|%-50s|%8.2f|',
$oo->service_id,
$oo->product_id,
@ -88,9 +78,8 @@ class InvoiceGenerate extends Command
}
}
//dump($io);
if ($this->option('preview')) {
$this->info(sprintf('=> Invoice for Account [%d] - [%d] items totalling [%3.2f]',$o->id,$io->items_active->count(),$io->total));
$this->info(sprintf('Invoice for Account [%d] - [%d] items totalling [%3.2f]',$o->id,$io->items->count(),$io->total));
continue;
}
@ -100,7 +89,5 @@ class InvoiceGenerate extends Command
$io->pushNew();
}
return self::SUCCESS;
}
}

View File

@ -43,8 +43,8 @@ class OrderSend extends Command
{
$so = Service::findOrFail($this->argument('service'));
Mail::to(config('osb.ticket_admin'))
->sendNow(new OrderRequestApprove($so));
// @todo TO get from DB
Mail::to('help@graytech.net.au')->sendNow(new OrderRequestApprove($so));
if (Mail::failures()) {
dump('Failure?');

View File

@ -0,0 +1,107 @@
<?php
namespace App\Console\Commands;
use Carbon\Carbon;
use Illuminate\Console\Command;
use App\Classes\External\Payments\Ezypay;
use App\Models\{Account,Checkout,Payment};
class PaymentsEzypayImport extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'payments:ezypay:import';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Retrieve payments from Ezypay';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
$poo = new Ezypay();
// Get our checkout IDs for this plugin
$cos = Checkout::where('plugin',config('services.ezypay.plugin'))->pluck('id');
foreach ($poo->getCustomers() as $c)
{
if ($c->BillingStatus == 'Inactive')
{
$this->info(sprintf('Ignoring INACTIVE: [%s] %s %s',$c->EzypayReferenceNumber,$c->Firstname,$c->Surname));
continue;
}
// Load Account Details from ReferenceId
$ao = Account::where('site_id',(int)substr($c->ReferenceId,0,2))
->where('id',(int)substr($c->ReferenceId,2,4))
->first();
if (! $ao)
{
$this->warn(sprintf('Missing: [%s] %s %s (%s)',$c->EzypayReferenceNumber,$c->Firstname,$c->Surname,$c->ReferenceId));
continue;
}
// Find the last payment logged
$last = Carbon::createFromTimestamp(Payment::whereIN('checkout_id',$cos)->where('account_id',$ao->id)->max('date_payment'));
$o = $poo->getDebits([
'customerId'=>$c->Id,
'dateFrom'=>$last->format('Y-m-d'),
'dateTo'=>$last->addQuarter()->format('Y-m-d'),
'pageSize'=>100,
]);
// Load the payments
if ($o->count())
{
foreach ($o->reverse() as $p)
{
// If not success, ignore it.
if ($p->Status != 'Success')
continue;
$pd = Carbon::createFromFormat('Y-m-d?H:i:s.u',$p->Date);
$lp = $ao->payments->last();
if ($lp AND (($pd == $lp->date_payment) OR ($p->Id == $lp->checkout_data)))
continue;
// New Payment
$po = new Payment;
$po->site_id = 1; // @todo
$po->date_payment = $pd;
$po->checkout_id = '999'; // @todo
$po->checkout_data = $p->Id;
$po->total_amt = $p->Amount;
$ao->payments()->save($po);
$this->info(sprintf('Recorded: Payment for [%s] %s %s (%s) on %s',$c->EzypayReferenceNumber,$c->Firstname,$c->Surname,$po->id,$pd));
}
}
}
}
}

View File

@ -0,0 +1,81 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use App\Classes\External\Payments\Ezypay;
use App\Models\Account;
class PaymentsEzypayNext extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'payments:ezypay:next';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Load next payments, and ensure they cover the next invoice';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
$poo = new Ezypay();
foreach ($poo->getCustomers() as $c)
{
if ($c->BillingStatus == 'Inactive')
{
$this->info(sprintf('Ignoring INACTIVE: [%s] %s %s',$c->EzypayReferenceNumber,$c->Firstname,$c->Surname));
continue;
}
// Load Account Details from ReferenceId
$ao = Account::where('site_id',(int)substr($c->ReferenceId,0,2))
->where('id',(int)substr($c->ReferenceId,2,4))
->first();
if (! $ao)
{
$this->warn(sprintf('Missing: [%s] %s %s (%s)',$c->EzypayReferenceNumber,$c->Firstname,$c->Surname,$c->ReferenceId));
continue;
}
// Get Due Invoices
$account_due = $ao->dueInvoices()->sum('due');
$next_pay = $poo->getDebits([
'customerId'=>$c->Id,
'dateFrom'=>now()->format('Y-m-d'),
'dateTo'=>now()->addQuarter()->format('Y-m-d'),
])->reverse()->first();
if ($next_pay->Amount < $account_due)
$this->warn(sprintf('Next payment for (%s) [%s] not sufficient for outstanding balance [%s]',$ao->name,number_format($next_pay->Amount,2),number_format($account_due,2)));
elseif ($next_pay->Amount > $account_due)
$this->warn(sprintf('Next payment for (%s) [%s] is too much for outstanding balance [%s]',$ao->name,number_format($next_pay->Amount,2),number_format($account_due,2)));
else
$this->info(sprintf('Next payment for (%s) [%s] will cover outstanding balance [%s]',$ao->name,number_format($next_pay->Amount,2),number_format($account_due,2)));
}
}
}

View File

@ -1,46 +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\ProviderTokenRefresh as Job;
class ProviderTokenRefresh extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'provider:token:refresh'
.' {provider : Supplier Name}'
.' {user? : User Email}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Refresh users access/refresh token';
/**
* Execute the console command.
*
* @return int
*/
public function handle()
{
$uo = User::where('email',$this->argument('user') ?: config('osb.admin'))->singleOrFail();
$so = ProviderOauth::where('name',$this->argument('provider'))->singleOrFail();
if (($x=$so->tokens->where('user_id',$uo->id))->count() !== 1)
abort(500,sprintf('Unknown Tokens for [%s]',$uo->email));
Job::dispatchSync($x->pop());
return self::SUCCESS;
}
}

View File

@ -0,0 +1,261 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use QuickBooksOnline\API\Data\IPPEmailAddress;
use QuickBooksOnline\API\Data\IPPPhysicalAddress;
use App\Classes\External\Accounting\Quickbooks;
use App\Models\{Account,External\Integrations,User};
class QuickAccounts extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'external:sync:accounts {--m|match : Match Display Name}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Sync Account numbers with External Sources';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return mixed
* @throws \Exception
*/
public function handle()
{
foreach (Integrations::active()->type('ACCOUNTING')->get() as $into)
{
switch ($into->name)
{
case 'quickbooks':
$api = new Quickbooks($into->user);
break;
default:
throw new \Exception('No handler for: ',$into-name);
}
foreach ($api->getCustomers(TRUE) as $r)
{
$this->info(sprintf('Checking [%s] (%s)',$r->Id,$r->DisplayName));
if ($r->Notes == 'Cash Only')
{
$this->warn(sprintf('Skipping [%s] (%s)',$r->Id,$r->DisplayName));
continue;
}
if (! $this->option('match') AND (! $r->CompanyName AND (! $r->FamilyName AND ! $r->GivenName)))
{
$this->error(sprintf('No COMPANY or PERSONAL details for [%s] (%s)',$r->Id,$r->DisplayName));
continue;
}
if ($this->option('match')) {
$ao = Account::where('company',$r->DisplayName);
if (! $ao->count()) {
$uo = User::where('lastname',$r->FamilyName);
if ($r->GivenName)
$uo->where('firstname',$r->GivenName);
if ($uo->count() > 1 OR (! $uo->count()))
{
$this->error(sprintf('No SINGLE Users matched for [%s] (%s)',$r->Id,$r->DisplayName));
continue;
}
$uo = $uo->first();
$ao = $uo->accounts->where('active',TRUE);
}
} else {
if ($r->CompanyName) {
$ao = Account::where('company',$this->option('match') ? $r->DisplayName : $r->CompanyName);
} else {
$uo = User::where('lastname',$r->FamilyName);
if ($r->GivenName)
$uo->where('firstname',$r->GivenName);
if ($uo->count() > 1 OR (! $uo->count()))
{
$this->error(sprintf('No SINGLE matched for [%s] (%s)',$r->Id,$r->DisplayName));
continue;
}
$uo = $uo->first();
$ao = $uo->accounts->where('active',TRUE);
}
}
if (! $ao->count())
{
$this->error(sprintf('No Accounts matched for [%s] (%s)',$r->Id,$r->DisplayName));
continue;
}
if ($ao->count() > 1)
{
$this->error(sprintf('Too Many Accounts (%s) matched for [%s] (%s)',$ao->count(),$r->Id,$r->DisplayName));
continue;
}
$ao = $ao->first();
// If we are matching on DisplayName, make sure the account is updated correct for Business or Personal Accounts
$oldr = clone $r;
// @NOTE: This overwrites the ABN if it exists.
if ($r->PrimaryTaxIdentifier)
{
$this->warn(sprintf('ABN Overwrite for (%s)',$r->DisplayName));
}
switch ($ao->type)
{
case 'Business':
$r->CompanyName = $ao->company;
$r->ResaleNum = $ao->AccountId;
$r->SalesTermRef = '7'; // @todo
if ($ao->first_name)
$r->GivenName = chop($ao->user->firstname); // @todo shouldnt be required
if ($ao->last_name)
$r->FamilyName = $ao->user->lastname;
if ($ao->address1)
{
if (! $r->BillAddr)
$r->BillAddr = new IPPPhysicalAddress;
$r->BillAddr->Line1 = $ao->user->address1;
$r->BillAddr->Line2 = $ao->user->address2;
$r->BillAddr->City = $ao->user->city;
$r->BillAddr->CountrySubDivisionCode = strtoupper($ao->user->state);
$r->BillAddr->PostalCode = $ao->user->postcode;
$r->BillAddr->Country = 'Australia'; // @todo
//$r->ShipAddr = $r->BillAddr;
}
if ($ao->email) {
if (! $r->PrimaryEmailAddr)
$r->PrimaryEmailAddr = new IPPEmailAddress;
$r->PrimaryEmailAddr->Address = $ao->user->email;
$r->PreferredDeliveryMethod = 'Email';
}
if (! $r->Balance)
$r->Active = $ao->active ? 'true' : 'false';
break;
case 'Private':
$r->CompanyName = NULL;
$r->DisplayName = sprintf('%s %s',$ao->user->lastname,$ao->user->firstname);
$r->ResaleNum = $ao->AccountId;
$r->SalesTermRef = '7'; // @todo
if ($ao->first_name)
$r->GivenName = chop($ao->user->firstname); // @todo shouldnt be required
if ($ao->last_name)
$r->FamilyName = $ao->user->lastname;
if ($ao->address1)
{
if (! $r->BillAddr)
$r->BillAddr = new IPPPhysicalAddress;
$r->BillAddr->Line1 = $ao->user->address1;
$r->BillAddr->Line2 = $ao->user->address2;
$r->BillAddr->City = $ao->user->city;
$r->BillAddr->CountrySubDivisionCode = strtoupper($ao->user->state);
$r->BillAddr->PostalCode = $ao->user->postcode;
$r->BillAddr->Country = 'Australia'; // @todo
//$r->ShipAddr = $r->BillAddr;
}
if ($ao->email) {
if (! $r->PrimaryEmailAddr)
$r->PrimaryEmailAddr = new IPPEmailAddress;
$r->PrimaryEmailAddr->Address = $ao->user->email;
$r->PreferredDeliveryMethod = 'Email';
}
if (! $r->Balance)
$r->Active = $ao->active ? 'true' : 'false';
break;
default:
throw new \Exception('Unhandled account type: '.$ao->type);
}
// If something changed, lets update it.
if (count(array_diff_assoc(object_to_array($r,FALSE),object_to_array($oldr,FALSE))))
{
$api->updateCustomer($r,[]);
}
// If external integration doesnt exist, lets create it.
if (! $ao->ExternalAccounting($into))
{
$ao->external()->attach([$into->id=>['site_id'=>1,'link'=>$r->Id]]); // @todo site_id
}
// If the integration ID doesnt exist in the integration source, add it.
if (! $r->ResaleNum)
{
$api->updateCustomer($r,['ResaleNum'=>$ao->AccountId]);
}
// If integration exist, double check the numbers match
if ($r->ResaleNum != $ao->AccountId) {
$this->warn(sprintf('Integration ID Mismatch AID [%s] ID [%s]',$ao->id,$r->Id));
continue;
}
}
}
}
}
function object_to_array($object,$encode=TRUE)
{
// For child arrays, we just encode
if ($encode)
return json_encode($object);
if (is_object($object)) {
return array_map(__FUNCTION__,get_object_vars($object));
} else if (is_array($object)) {
return array_map(__FUNCTION__,$object);
} else {
return $object;
}
}

View File

@ -3,6 +3,8 @@
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use App\Models\Service;
@ -13,10 +15,10 @@ class ServiceList extends Command
*
* @var string
*/
protected $signature = 'service:list'.
' {--i|inactive : Include Inactive}'.
' {--t|type= : Type}'.
' {--f|fix : Fix start_date}';
protected $signature = 'service:list '.
'{--a|active : Active Only}'.
'{--category= : Category}'.
'{--f|fix : Fix start_date}';
/**
* The console command description.
@ -25,6 +27,16 @@ class ServiceList extends Command
*/
protected $description = 'List all services';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
@ -32,51 +44,50 @@ class ServiceList extends Command
*/
public function handle()
{
$header = '|%5s|%-9s|%-30s|%-30s|%7s|%7s|%10s|%10s|%10s|%10s|%10s|';
DB::listen(function($query) {
Log::debug('- SQL',['sql'=>$query->sql,'binding'=>$query->bindings]);
});
$this->warn(sprintf($header,
$this->warn(sprintf('|%10s|%-6s|%-20s|%-50s|%8s|%14s|%10s|%10s|%10s|%10s|%10s|',
'ID',
'Type',
'CAT',
'Product',
'Name',
'Active',
'Status',
'Start',
'Stop',
'Connect',
'First',
'Next',
));
'active',
'status',
'invoice next',
'start date',
'stop date',
'connect date',
'first invoice'
));
foreach (Service::cursor() as $o) {
if ((! $this->option('inactive')) && (! $o->isActive()))
foreach (Service::all() as $o) {
if ($this->option('active') AND ! $o->isActive())
continue;
if ($this->option('type') && ($o->product->getCategoryAttribute() !== $this->option('type')))
if ($this->option('category') AND $o->product->category !== $this->option('category'))
continue;
$c = $o->invoiced_items
->filter(fn($item)=>$item->item_type === 0)
->sortby('start_at')
->first();
$c = $o->invoice_items->filter(function($item) {return $item->item_type === 0; })->sortby('date_start')->first();
if ($this->option('fix') && (! $o->start_at) && $c && $c->start_at && $o->type && $o->type->connect_at && $c->start_at->format('Y-m-d') == $o->type->connect_at->format('Y-m-d')) {
$o->start_at = $o->type->connect_at;
if ($this->option('fix') AND ! $o->date_start AND $c AND $c->date_start AND $o->type AND $o->type->service_connect_date AND $c->date_start->format('Y-m-d') == $o->type->service_connect_date->format('Y-m-d')) {
$o->date_start = $o->type->service_connect_date;
$o->save();
}
$this->info(sprintf($header,
$o->lid,
$o->product->getCategoryNameAttribute(),
substr($o->product->getNameAttribute(),0,35),
substr($o->name_short,0,40),
$this->info(sprintf('|%10s|%-6s|%-20s|%-50s|%8s|%14s|%10s|%10s|%10s|%10s|%10s|',
$o->sid,
$o->product->category,
$o->product_name,
$o->name_short,
$o->active ? 'active' : 'inactive',
$o->status,
$o->start_at?->format('Y-m-d'),
$o->stop_at?->format('Y-m-d'),
($o->type && $o->type->connect_at) ? $o->type->connect_at->format('Y-m-d') : NULL,
($c && $c->start_at) ? $c->start_at->format('Y-m-d') : NULL,
$o->invoice_next?->format('Y-m-d'),
$o->invoice_next ? $o->invoice_next->format('Y-m-d') : NULL,
$o->date_start ? $o->date_start->format('Y-m-d') : NULL,
$o->date_end ? $o->date_end->format('Y-m-d') : NULL,
($o->type AND $o->type->service_connect_date) ? $o->type->service_connect_date->format('Y-m-d') : NULL,
$c ? $c->date_start->format('Y-m-d') : NULL,
));
}
}

View File

@ -1,67 +0,0 @@
<?php
namespace App\Console\Commands;
use Carbon\Carbon;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Config;
use App\Models\{Site,Supplier,User};
class SupplierAccountSync extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'supplier:account:sync'
.' {siteid : Site ID}'
.' {supplier : Supplier Name}'
.' {--f|forceprod : Force Prod API on dev environment}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Sync accounts with a supplier';
/**
* Execute the console command.
*
* @return int
*/
public function handle()
{
Config::set('site',Site::findOrFail($this->argument('siteid')));
$so = Supplier::where('name',$this->argument('supplier'))->singleOrFail();
foreach ($so->API($this->option('forceprod'))->getCustomers(['fetchall'=>true]) as $customer) {
// Check if we have this customer already (by ID)
if ($so->users->where('pivot.id',$customer->id)->count()) {
$this->info(sprintf('User already linked (%s:%s)',$customer->id,$customer->email));
} elseif ($x=User::where('email',$customer->email)->single()) {
//dump($x->suppliers->first());
if ($x->suppliers->count()) {
$this->alert(sprintf('User [%d:%s] already linked to this supplier with ID (%s)',$customer->id,$customer->email,$x->suppliers->first()->pivot->id));
} else {
$this->warn(sprintf('User [%d:%s] has same email (%s:%s) - Linked',$x->id,$x->email,$customer->id,$customer->email));
$so->users()->syncWithoutDetaching([
$x->id => [
'id'=>$customer->id,
'site_id'=>$x->site_id, // @todo See if we can have this handled automatically
'created_at'=>Carbon::create($customer->date_added),
]
]);
}
} else {
$this->error(sprintf('User doesnt exist with email (%s:%s)',$customer->id,$customer->email));
}
}
}
}

View File

@ -1,40 +0,0 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use App\Models\{Site,Supplier};
use App\Jobs\SupplierDomainSync as Job;
class SupplierDomainSync extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'supplier:domain:sync'
.' {siteid : Site ID}'
.' {supplier : Supplier Name}'
.' {--f|forceprod : Force Prod API on dev environment}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Sync domains from a supplier';
/**
* Execute the console command.
*
* @return int
*/
public function handle()
{
$so = Supplier::where('name',$this->argument('supplier'))->singleOrFail();
Job::dispatchSync(Site::findOrFail($this->argument('siteid')),$so,$this->option('forceprod'));
}
}

View File

@ -0,0 +1,49 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Mail;
use App\Mail\TestEmail as MailTest;
use App\Models\User;
class TestEmail extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'test:email {id}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Send a test email';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
$uo = User::find($this->argument('id'));
Mail::to($uo->email)
->send(new MailTest($uo));
}
}

46
app/Console/Kernel.php Normal file
View File

@ -0,0 +1,46 @@
<?php
namespace App\Console;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
use App\Models\AdslSupplier;
use App\Jobs\BroadbandTraffic;
class Kernel extends ConsoleKernel
{
/**
* The Artisan commands provided by your application.
*
* @var array
*/
protected $commands = [
//
];
/**
* Define the application's command schedule.
*
* @param \Illuminate\Console\Scheduling\Schedule $schedule
* @return void
*/
protected function schedule(Schedule $schedule)
{
// @todo This needs to be more generic and dynamic
// Exetel Traffic
$schedule->job(new BroadbandTraffic(AdslSupplier::find(1)))->timezone('Australia/Melbourne')->dailyAt('10:00');
}
/**
* Register the commands for the application.
*
* @return void
*/
protected function commands()
{
$this->load(__DIR__.'/Commands');
require base_path('routes/console.php');
}
}

View File

@ -1,40 +0,0 @@
<?php
namespace App\Events;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class ProviderPaymentCreated
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public array $paymentData;
public string $provider;
/**
* Create a new event instance.
*
* @return void
*/
public function __construct(string $provider,array $paymentData)
{
$this->provider = $provider;
$this->paymentData = $paymentData;
}
/**
* Get the channels the event should broadcast on.
*
* @return \Illuminate\Broadcasting\Channel|array
*/
public function broadcastOn()
{
return new PrivateChannel('channel-name');
}
}

View File

@ -22,7 +22,6 @@ class Handler extends ExceptionHandler
* @var array
*/
protected $dontFlash = [
'current_password',
'password',
'password_confirmation',
];

View File

@ -2,59 +2,138 @@
namespace App\Http\Controllers;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Arr;
use App\Http\Requests\SiteEdit;
use App\Models\SiteDetail;
use App\Models\{Account,Payment,PaymentItem,Service,SiteDetail};
/**
* The AdminController governs all routes that are prefixed with 'a/'.
*
* This is everything about the configuration of the application as a whole, or administration of a site.
*/
class AdminController extends Controller
{
public function service(Service $o)
{
return View('a.service',['o'=>$o]);
}
/**
* Record payments on an account.
*
* @param Request $request
* @param Payment $o
* @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View|\Illuminate\Http\RedirectResponse
*/
public function pay_add(Request $request,Payment $o)
{
if ($request->post()) {
$validation = $request->validate([
'account_id' => 'required|exists:ab_account,id',
'date_payment' => 'required|date',
'checkout_id' => 'required|exists:ab_checkout,id',
'total_amt' => 'required|numeric|min:0.01',
'fees_amt' => 'nullable|numeric|lt:total_amt',
'source_id' => 'nullable|exists:ab_account,id',
'pending' => 'nullable|boolean',
'notes' => 'nullable|string',
'ip' => 'nullable|ip',
'invoices' => ['nullable','array',function ($attribute,$value,$fail) use ($request) {
if (collect($value)->sum() > $request->post('total_amt'))
$fail('Allocation is greater than payment total.');
}],
'invoices.*.id' => 'nullable|exists:ab_invoice,id',
]);
$oo = new Payment;
$oo->forceFill($request->only(['account_id','date_payment','checkout_id','checkout_id','total_amt','fees_amt','source_id','pending','notes','ip']));
$oo->site_id = config('SITE')->site_id;
$oo->save();
foreach ($validation['invoices'] as $id => $amount) {
$ooo = new PaymentItem;
$ooo->invoice_id = $id;
$ooo->alloc_amt = $amount;
$ooo->site_id = config('SITE')->site_id;
$oo->items()->save($ooo);
}
return redirect()->back()
->with('success','Payment recorded');
}
return view('a.payment.add')
->with('o',$o);
}
/**
* Show a list of invoices to apply payments to
*
* @param Account $o
* @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View
*/
public function pay_invoices(Account $o)
{
return view('a.payment.widgets.invoices')
->with('o',$o);
}
/**
* Site setup
*
* @note This method is protected by the routes
* @param SiteEdit $request
* @return RedirectResponse
* @param Request $request
* @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View|\Illuminate\Http\RedirectResponse
*/
public function setup(SiteEdit $request)
public function setup(Request $request)
{
$site = config('site');
$images = ['site_logo','email_logo'];
$validated = collect($request->validated());
if ($request->post()) {
$validated = $request->validate([
'site_name' => 'required|string|max:255',
'site_email' => 'required|string|email|max:255',
'site_address1' => 'required|string|max:255',
'site_address2' => 'nullable|string|max:255',
'site_city' => 'required|string|max:64',
'site_state' => 'required|string|max:32',
'site_postcode' => 'required|string|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',
]);
// Handle the images
foreach($images as $key)
if ($x=$request->validated($key))
$validated->put($key,$x->storeAs('site/'.$site->site_id,$x->getClientOriginalName(),'public'));
$site = config('SITE');
foreach ($site->details as $oo)
if ($validated->has($oo->key)) {
// Dont set the following keys to null if they are null
if (in_array($oo->key,$images) && is_null($validated->get($oo->key)))
continue;
// @todo - not currently rendered in the home page
$validated['social'] = [];
$validated['top_menu'] = [];
$oo->value = $validated->get($oo->key) ?: '';
$oo->save();
// Handle the images
foreach(['site_logo','email_logo'] as $key)
if (array_key_exists($key,$validated))
$validated[$key] = ($x=$validated[$key])->storeAs('site/'.$site->site_id,$x->getClientOriginalName(),'public');
$validated->forget($oo->key);
foreach ($site->details as $oo)
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);
}
// 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()->back()
->with('success','Settings saved');
}
return redirect()
->back()
->with('success','Settings saved');
return view('a.setup');
}
}

View File

@ -4,29 +4,46 @@ namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\SendsPasswordResetEmails;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Password;
class ForgotPasswordController extends Controller
{
/*
|--------------------------------------------------------------------------
| Password Reset Controller
|--------------------------------------------------------------------------
|
| This controller is responsible for handling password reset emails and
| includes a trait which assists in sending these notifications from
| your application to your users. Feel free to explore this trait.
|
*/
/*
|--------------------------------------------------------------------------
| Password Reset Controller
|--------------------------------------------------------------------------
|
| This controller is responsible for handling password reset emails and
| includes a trait which assists in sending these notifications from
| 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');
}
public function sendResetLinkEmail(Request $request)
{
$this->validateEmail($request);
// If the account is not active, or doesnt exist, we'll send a fake "sent" message.
if (! ($x=$this->broker()->getUser($this->credentials($request))) || (! $x->active))
return $this->sendResetLinkResponse($request, Password::RESET_LINK_SENT);
// We will send the password reset link to this user. Once we have attempted
// to send the link, we will examine the response then see the message we
// need to show to the user. Finally, we'll send out a proper response.
$response = $this->broker()->sendResetLink(
$this->credentials($request)
);
return $response == Password::RESET_LINK_SENT
? $this->sendResetLinkResponse($request, $response)
: $this->sendResetLinkFailedResponse($request, $response);
}
}

View File

@ -3,6 +3,8 @@
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use App\Providers\RouteServiceProvider;
use Carbon\Carbon;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
@ -10,36 +12,35 @@ use Illuminate\Support\Facades\Schema;
class LoginController extends Controller
{
/*
|--------------------------------------------------------------------------
| Login Controller
|--------------------------------------------------------------------------
|
| This controller handles authenticating users for the application and
| redirecting them to your home screen. The controller uses a trait
| to conveniently provide its functionality to your applications.
|
*/
/*
|--------------------------------------------------------------------------
| Login Controller
|--------------------------------------------------------------------------
|
| This controller handles authenticating users for the application and
| redirecting them to your home screen. The controller uses a trait
| to conveniently provide its functionality to your applications.
|
*/
use AuthenticatesUsers;
use AuthenticatesUsers;
/**
* Where to redirect users after login.
*
* @var string
*/
protected $redirectTo = '/home';
/**
* Where to redirect users after login.
*
* @var string
*/
protected $redirectTo = RouteServiceProvider::HOME;
/**
* Create a new controller instance.
*
* @return void
*/
public function __construct()
{
$this->middleware('auth')
->only('logout');
}
/**
* Create a new controller instance.
*
* @return void
*/
public function __construct()
{
$this->middleware('guest')->except('logout');
}
public function login(Request $request)
{
@ -72,7 +73,6 @@ class LoginController extends Controller
if (file_exists('login_note.txt'))
$login_note = file_get_contents('login_note.txt');
return view('adminlte::auth.login')
->with('login_note',$login_note);
return view('adminlte::auth.login')->with('login_note',$login_note);
}
}

View File

@ -0,0 +1,89 @@
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use App\Providers\RouteServiceProvider;
use App\Models\User;
use Illuminate\Foundation\Auth\RegistersUsers;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Validator;
class RegisterController extends Controller
{
/*
|--------------------------------------------------------------------------
| Register Controller
|--------------------------------------------------------------------------
|
| This controller handles the registration of new users as well as their
| validation and creation. By default this controller uses a trait to
| provide this functionality without requiring any additional code.
|
*/
use RegistersUsers;
/**
* Where to redirect users after registration.
*
* @var string
*/
protected $redirectTo = RouteServiceProvider::HOME;
/**
* Create a new controller instance.
*
* @return void
*/
public function __construct()
{
$this->middleware('guest');
}
/**
* Get a validator for an incoming registration request.
*
* @param array $data
* @return \Illuminate\Contracts\Validation\Validator
*/
protected function validator(array $data)
{
return Validator::make($data, [
'name' => ['required', 'string', 'min:3', 'max:255'],
'email' => ['required', 'string', 'email', 'max:255', 'unique:users'],
'password' => ['required', 'string', 'min:8', 'confirmed'],
'token' => 'required|doorman:email',
]);
}
/**
* Create a new user instance after a valid registration.
*
* @param array $data
* @return User
*/
protected function create(array $data): User
{
$fields = [
'name' => $data['name'],
'email' => $data['email'],
'password' => Hash::make($data['password']),
];
if (config('auth.providers.users.field','email') === 'username' && isset($data['username'])) {
$fields['username'] = $data['username'];
}
try {
Doorman::redeem($data['token'],$data['email']);
// @todo Want to direct or display an appropriate error message (although the form validation does it anyway).
} catch (DoormanException $e) {
redirect('/error');
abort(403);
}
return User::create($fields);
}
}

View File

@ -3,47 +3,46 @@
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use App\Providers\RouteServiceProvider;
use Illuminate\Foundation\Auth\ResetsPasswords;
use Illuminate\Http\Request;
class ResetPasswordController extends Controller
{
/*
|--------------------------------------------------------------------------
| Password Reset Controller
|--------------------------------------------------------------------------
|
| This controller is responsible for handling password reset requests
| and uses a simple trait to include this behavior. You're free to
| explore this trait and override any methods you wish to tweak.
|
*/
/*
|--------------------------------------------------------------------------
| Password Reset Controller
|--------------------------------------------------------------------------
|
| This controller is responsible for handling password reset requests
| and uses a simple trait to include this behavior. You're free to
| explore this trait and override any methods you wish to tweak.
|
*/
use ResetsPasswords;
use ResetsPasswords;
/**
* Where to redirect users after resetting their password.
*
* @var string
*/
protected $redirectTo = RouteServiceProvider::HOME;
/**
* Where to redirect users after resetting their password.
* Create a new controller instance.
*
* @var string
* @return void
*/
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)
public function __construct()
{
$token = $request->route()->parameter('token');
return view('adminlte::auth.passwords.reset')
->with([
'token' => $token,
'email' => $request->email
]);
$this->middleware('guest');
}
}
public function showResetForm(Request $request, $token = null)
{
return view('adminlte::auth.passwords.reset')->with(
['token' => $token, 'email' => $request->email]
);
}
}

View File

@ -2,8 +2,6 @@
namespace App\Http\Controllers\Auth;
use Carbon\Carbon;
use Illuminate\Contracts\View\View;
use Illuminate\Http\Request;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Auth;
@ -12,120 +10,86 @@ use Laravel\Socialite\Facades\Socialite;
use App\Http\Controllers\Controller;
use App\Mail\SocialLink;
use App\Models\{ProviderOauth,ProviderToken,User,UserOauth};
use App\Models\Oauth;
use App\Models\AccountOauth;
use App\Models\User;
use App\Providers\RouteServiceProvider;
class SocialLoginController extends Controller
{
public function redirectToProvider($provider)
{
return Socialite::with($provider)
->redirect();
return Socialite::with($provider)->redirect();
}
public function handleProviderCallback($provider)
{
$openiduser = Socialite::with($provider)->user();
if (! $openiduser)
return redirect('/home')
->with('error','No user details obtained.');
$oo = ProviderOauth::firstOrCreate(['name'=>$provider,'active'=>TRUE]);
$oo = Oauth::firstOrCreate(['name'=>$provider,'active'=>TRUE]);
// See if this user has connected and linked previously
$aoo = $oo->users->where('userid',$openiduser->id);
$aoo = $oo->accounts->where('userid',$openiduser->id);
if ($aoo->count() === 1) {
if ($aoo->count() == 1) {
$aoo = $aoo->first();
if ((is_null($user=$aoo->user) && (is_null($aoo->account) || is_null($user=$aoo->account->user))) || ! $user->active) {
if (! $user)
if ((is_null($user=$aoo->user) AND (is_null($aoo->account) OR is_null($user=$aoo->account->user))) OR ! $user->active) {
if (! $user) {
$user = User::where('email',$openiduser->email)->first();
}
if ((! $user) || (! $user->active))
return redirect('/login')
->with('error','Invalid account, or account inactive, please contact an admin.');
if (! $user OR ! $user->active) {
return redirect('/login')->with('error','Invalid account, or account inactive, please contact an admin.');
}
return $this->link($provider,$aoo,$user);
}
// All Set to login
Auth::login($user);
Auth::login($user,FALSE);
// If there are too many users, then we have a problem
} elseif ($aoo->count() > 1) {
return redirect('/login')
->with('error','Seems you have multiple oauth IDs, please contact an admin.');
return redirect('/login')->with('error','Seems you have multiple oauth IDs, please contact an admin.');
// User is using OAUTH for the first time.
} else {
$uo = User::active()->where('email',$openiduser->email);
// See if their is an account with this email address
if ($uo->count() === 1) {
$aoo = new UserOauth;
if ($uo->count() == 1) {
$aoo = new AccountOauth;
$aoo->userid = $openiduser->id;
$aoo->oauth_data = $openiduser->user;
$oo->users()->save($aoo);
$oo->accounts()->save($aoo);
return $this->link($provider,$aoo,$uo->first());
// If there are too many users, then we have a problem
} elseif ($uo->count() > 1) {
return redirect('/login')
->with('error','Seems you have multiple accounts, please contact an admin.');
return redirect('/login')->with('error','Seems you have multiple accounts, please contact an admin.');
} else {
return redirect('/login')
->with('error','Seems you dont have an account with that email, please contact an admin.');
return redirect('/login')->with('error','Seems you dont have an account with that email, please contact an admin.');
}
}
return redirect()
->intended('/home');
}
public function handleBearerTokenCallback($provider)
{
$openiduser = Socialite::with($provider)->user();
if (! $openiduser)
return redirect('/home')
->with('error','No user details obtained.');
$po = ProviderOauth::where('name',$provider)->singleOrFail();
$uoo = ProviderToken::where('user_id',Auth::id())->where('provider_oauth_id',$po->id)->firstOrNew();
$uoo->user_id = Auth::id();
$uoo->access_token = $openiduser->token;
$uoo->access_token_expires_at = Carbon::now()->addSeconds($openiduser->expiresIn);
$uoo->refresh_token = $openiduser->refreshToken;
$uoo->refresh_token_expires_at = Carbon::now()->addSeconds($openiduser->refresh_token_expires_in);
$uoo->realm_id = $openiduser->realmid;
$po->tokens()->save($uoo);
return redirect()
->intended('/home')
->with('success','Token refreshed.');
return redirect()->intended(RouteServiceProvider::HOME);
}
/**
* We have identified the user and oauth, just need them to confirm the link
*
* @param $provider
* @param UserOauth $ao
* @param $provider
* @param User $uo
* @return View
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
*/
public function link($provider,UserOauth $ao,User $uo): View
public function link($provider,AccountOauth $ao,User $uo)
{
// @note If this is sent now (send()), it results in the caller to be executed a second time (handleProviderCallback()).
Mail::to($uo->email)->queue(new SocialLink($ao));
Mail::to($uo->email)->send(new SocialLink($ao));
return view('theme.backend.adminlte.auth.social_link')
return view('auth.social_link')
->with('oauthid',$ao->id)
->with('provider',$provider);
}
@ -133,29 +97,23 @@ class SocialLoginController extends Controller
public function linkcomplete(Request $request,$provider)
{
// Load our oauth id
$aoo = UserOauth::findOrFail($request->post('oauthid'));
$aoo = AccountOauth::findOrFail($request->post('oauthid'));
// Check our email matches
if (Arr::get($aoo->oauth_data,'email','invalid') !== $request->post('email'))
return redirect('/login')
->with('error','Account details didnt match to make link.');
return redirect('/login')->with('error','Account details didnt match to make link.');
// Check our token matches
if ($aoo->link_token !== $request->post('token'))
return redirect('/login')
->with('error','Token details didnt match to make link.');
return redirect('/login')->with('error','Token details didnt match to make link.');
// Load our email.
$uo = User::where('email',$request->post('email'))->firstOrFail();
// Incase we have an existing record with a different oauthid
UserOauth::where('user_id',$uo->id)->delete();
$aoo->user_id = $uo->id;
$aoo->save();
Auth::login($uo);
Auth::login($uo,FALSE);
return redirect()
->intended('/home');
return redirect()->intended(RouteServiceProvider::HOME);
}
}

View File

@ -1,70 +0,0 @@
<?php
namespace App\Http\Controllers;
use App\Http\Requests\ChargeAdd;
use App\Models\Charge;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Gate;
use Illuminate\View\View;
class ChargeController extends Controller
{
/**
* Add a charge to a service/account
*
* @param ChargeAdd $request
* @return RedirectResponse
*/
public function addedit(ChargeAdd $request): RedirectResponse
{
$o = Charge::findOrNew(Arr::get($request->validated(),'id'));
// Dont update processed charges
if ($o->processed)
abort(403);
$o->forceFill(array_merge(Arr::except($request->validated(),['id']),['active'=>TRUE]));
$o->save();
return redirect()
->back()
->with('success',sprintf('Charge %s #%d',$o->wasRecentlyCreated ? 'Created' : 'Updated',$o->id));
}
public function delete(Charge $o): array
{
if (Gate::allows('delete',$o)) {
$o->active = FALSE;
$o->save();
return ['ok'];
} else {
abort(401,'Not Allowed');
}
}
/**
* Add a charge to a service/account
*
* @param Request $request
* @return View
*/
public function edit(Request $request): View
{
$o = Charge::where('processed',FALSE)
->where('id',$request->id)
->firstOrFail();
if (Gate::allows('update',$o)) {
return view('theme.backend.adminlte.charge.widget.addedit')
->with('o',$o)
->with('so',$o->service);
}
abort(403);
}
}

View File

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

View File

@ -2,10 +2,12 @@
namespace App\Http\Controllers;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Routing\Controller as BaseController;
use Illuminate\Foundation\Validation\ValidatesRequests;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
abstract class Controller extends \Illuminate\Routing\Controller
class Controller extends BaseController
{
use AuthorizesRequests, ValidatesRequests;
}
use AuthorizesRequests, DispatchesJobs, ValidatesRequests;
}

View File

@ -4,9 +4,11 @@ namespace App\Http\Controllers;
use Clarkeash\Doorman\Exceptions\{DoormanException,ExpiredInviteCode};
use Clarkeash\Doorman\Facades\Doorman;
use Illuminate\Contracts\View\Factory;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Log;
use Illuminate\View\View;
use Barryvdh\Snappy\Facades\SnappyPdf as PDF;
use App\Models\{Invoice,Service,User};
@ -20,19 +22,60 @@ use App\Models\{Invoice,Service,User};
*/
class HomeController extends Controller
{
public function __construct()
{
$this->middleware('auth');
}
/**
* Logged in users home page
*
* @param User $o
* @return View
* @return Factory|View
*/
public function home(User $o): View
{
if (! $o->exists)
$o = Auth::user();
// If we are passed a user to view, we'll open up their home page.
if ($o->exists) {
$o->load(['accounts','services']);
return View('u.home',['o'=>$o]);
}
return view('theme.backend.adminlte.home')
->with(['o'=>$o]);
// If User was null, then test and see what type of logged on user we have
$o = Auth::user();
switch (Auth::user()->role()) {
case 'customer':
return View('u.home',['o'=>$o]);
case 'reseller':
case 'wholesaler':
return View('r.home',['o'=>$o]);
default:
abort(404,'Unknown role: '.$o->role());
}
}
/**
* Render a specific invoice for the user
*
* @param Invoice $o
* @return View
*/
public function invoice(Invoice $o): View
{
return View('u.invoice.home',['o'=>$o]);
}
/**
* Return the invoice in PDF format, ready to download
*
* @param Invoice $o
* @return mixed
*/
public function invoice_pdf(Invoice $o)
{
return PDF::loadView('u.invoice.home',['o'=>$o])->stream(sprintf('%s.pdf',$o->invoice_account_id));
}
/**
@ -58,7 +101,18 @@ class HomeController extends Controller
abort(404);
}
return $this->invoice($o);
return $this->invoice_pdf($o);
}
/**
* Return details on the users service
*
* @param Service $o
* @return View
*/
public function service(Service $o): View
{
return View('u.service.home',['o'=>$o]);
}
/**
@ -68,7 +122,6 @@ class HomeController extends Controller
* @param Service $o
* @param string $status
* @return \Illuminate\Http\RedirectResponse
* @deprecated
*/
public function service_progress(Service $o,string $status)
{

View File

@ -1,71 +0,0 @@
<?php
namespace App\Http\Controllers;
use Clarkeash\Doorman\Exceptions\{ExpiredInviteCode,InvalidInviteCode,NotYourInviteCode};
use Clarkeash\Doorman\Facades\Doorman;
use Illuminate\Http\Request;
use Illuminate\View\View;
use Barryvdh\Snappy\Facades\SnappyPdf as PDF;
use App\Models\{Account,Invoice};
/**
* Class InvoiceController
* This controller manages invoices
*
* The methods to this controller should be projected by the route
*
* @package App\Http\Controllers
*/
class InvoiceController extends Controller
{
/**
* Show a list of invoices to apply payments to
*
* @param Request $request
* @return \Illuminate\Contracts\View\View
*/
public function api_account_invoices(Request $request): \Illuminate\Contracts\View\View
{
session()->flashInput($request->post('old',[]));
return view('theme.backend.adminlte.payment.widget.invoices')
->with('pid',$request->post('pid'))
->with('o',Account::findOrFail($request->post('aid')));
}
/**
* Return the invoice in PDF format, ready to download
*
* @param Invoice $o
* @return mixed
*/
public function pdf(Invoice $o)
{
return PDF::loadView('theme.backend.adminlte.u.invoice.home',['o'=>$o])
->stream(sprintf('%s.pdf',$o->sid));
}
/**
* Render a specific invoice for the user
*
* @param Invoice $o
* @param string|null $code
* @return View
*/
public function view(Invoice $o,string $code=NULL): View
{
if ($code) {
try {
Doorman::redeem($code,$o->account->user->email);
} catch (ExpiredInviteCode|InvalidInviteCode|NotYourInviteCode $e) {
abort(404);
}
}
return view('theme.backend.adminlte.invoice.view')
->with('o',$o);
}
}

View File

@ -0,0 +1,27 @@
<?php
namespace App\Http\Controllers;
use Image;
class MediaController extends Controller
{
/**
* Create a generic image
*
* @param $width
* @param $height
* @param string $color
* @return mixed
*/
public function image($width,$height,$color='#ccc') {
$io = Image::canvas($width,$height,$color);
$io->text(sprintf('IMAGE-%sx%s',$width,$height),$width/2,$height/2,function($font) {
$font->file(5);
$font->align('center');
$font->valign('middle');
});
return $io->response();
}
}

View File

@ -3,49 +3,63 @@
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Igaster\LaravelTheme\Facades\Theme;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Validator;
use Illuminate\Database\Eloquent\Model;
use App\Mail\OrderRequest;
use App\Models\{Account,Product,Rtm,Service,User};
use App\Models\{Account,Product,Service,User};
class OrderController extends Controller
{
// @todo To check
public function __construct()
{
$this->middleware('auth');
}
public function index()
{
return view('order');
}
public function product_order(Product $o)
{
Theme::set('metronic-fe');
return view('widgets.product_order',['o'=>$o]);
}
public function product_info(Product $o)
{
Theme::set('metronic-fe');
return view('widgets.product_description',['o'=>$o]);
}
public function submit(Request $request)
{
Validator::make($request->all(),
[
'product_id'=>'required|exists:products,id'
]
)
Validator::make($request->all(),['product_id'=>'required|exists:ab_product,id'])
// Reseller
->sometimes(
'account_id',
'required|email',
fn($input)=>is_null($input->account_id) && is_null($input->order_email_manual)
)
->sometimes('account_id','required|email',function($input) use ($request) {
return is_null($input->account_id) AND is_null($input->order_email_manual);
})
// Un-Authed User
->sometimes(
'order_email_manual',
'required|email|unique:users,email,NULL,id',
fn($input)=>(is_null($input->order_email_manual) && (! isset($input->account_id))) || $input->order_email_manual
)
->sometimes('order_email_manual','required|email|unique:users,email,NULL,id',function($input) use ($request) {
return (is_null($input->order_email_manual) AND ! isset($input->account_id)) OR $input->order_email_manual;
})
// Authed User
->sometimes(
'account_id',
'required|email',
fn($input)=>is_null($input->account_id) && (! isset($input->order_email_manual))
)
->sometimes('account_id','required|email',function($input) use ($request) {
return is_null($input->account_id) AND ! isset($input->order_email_manual);
})
->validate();
// Check the plugin details.
$po = Product::findOrFail($request->input('product_id'));
// Check we have the custom attributes for the product
$order = $po->orderValidation($request);
$options = $po->orderValidation($request);
if ($request->input('order_email_manual')) {
$uo = User::firstOrNew(['email'=>$request->input('order_email_manual')]);
@ -53,13 +67,12 @@ class OrderController extends Controller
// If this is a new client
if (! $uo->exists) {
// @todo Make this automatic
$uo->site_id = config('site')->site_id;
$uo->site_id = config('SITE')->site_id;
$uo->active = FALSE;
$uo->firstname = '';
$uo->lastname = '';
$uo->country_id = config('site')->country_id; // @todo This might be wrong
$uo->country_id = config('SITE')->country_id; // @todo This might be wrong
$uo->parent_id = Auth::id() ?: 1; // @todo This should be configured to a default user
$uo->language_id = config('site')->language_id; // @todo This might be wrong
$uo->active = 1;
$uo->save();
}
@ -68,9 +81,12 @@ class OrderController extends Controller
// If we have a new account.
if (is_null($request->input('account_id'))) {
$ao = new Account;
//$ao->id = Account::NextId();
// @todo Make this automatic
$ao->site_id = config('site')->site_id;
$ao->country_id = config('site')->country_id; // @todo This might be wrong
$ao->site_id = config('SITE')->site_id;
$ao->country_id = config('SITE')->country_id; // @todo This might be wrong
$ao->language_id = config('SITE')->language_id; // @todo This might be wrong
$ao->currency_id = config('SITE')->currency_id; // @todo This might be wrong
$ao->active = 1;
$uo->accounts()->save($ao);
@ -81,33 +97,28 @@ class OrderController extends Controller
$so = new Service;
// @todo Make this automatic
$so->site_id = config('site')->site_id;
$so->product_id = $po->id;
$so->site_id = config('SITE')->site_id;
$so->product_id = $request->input('product_id');
$so->order_status = 'ORDER-SUBMIT';
$so->ordered_by = Auth::id();
$so->active = FALSE;
$so->model = $order ? get_class($order) : NULL;
$so->recur_schedule = $po->billing_interval;
$so->orderby_id = Auth::id();
$so->model = get_class($options);
if ($order && $order->order_info) {
$so->order_info = $order->order_info;
if ($options->order_info) {
$so->order_info = $options->order_info;
unset($order->order_info);
unset($options->order_info);
}
$so = $ao->services()->save($so);
if ($order instanceOf Model) {
$order->service_id = $so->id;
$order->save();
if ($options instanceOf Model) {
$options->service_id = $so->id;
$options->save();
}
$ro = Rtm::where('parent_id',NULL)->sole();
Mail::to('help@graytech.net.au')
->queue((new OrderRequest($so,$request->input('options.notes')))->onQueue('email')); //@todo Get email from DB.
Mail::to(config('osb.ticket_admin'))
->queue((new OrderRequest($so,$request->input('options.notes') ?: ''))->onQueue('email')); //@todo Get email from DB.
return view('theme.frontend.metronic.order_received')
->with('o',$so);
return view('order_received',['o'=>$so]);
}
}
}

View File

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

View File

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

View File

@ -1,127 +0,0 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
use App\Http\Requests\ProductAddEdit;
use App\Models\{Product,ProductTranslate};
class ProductController extends Controller
{
/**
* Get a list of products that meet a type
*
* @param Request $request
* @return Collection
* @throws \Exception
*/
public function api_supplied_products(Request $request): Collection
{
switch ($request->type) {
case Product\Broadband::class:
return Product\Broadband::select(['id','supplier_item_id'])
->with(['supplied.supplier_detail.supplier'])
->get()
->map(fn($item)=>['id'=>$item->id,'name'=>sprintf('%s: %s',$item->supplied->supplier->name,$item->supplied->name)])
->sortBy('name')
->values();
case Product\Domain::class:
return Product\Domain::select(['id','supplier_item_id'])
->with(['supplied.supplier_detail.supplier'])
->get()
->map(fn($item)=>['id'=>$item->id,'name'=>sprintf('%s: %s',$item->supplied->supplier->name,$item->supplied->name)])
->sortBy('name')
->values();
case Product\Email::class:
return Product\Email::select(['id','supplier_item_id'])
->with(['supplied.supplier_detail.supplier'])
->get()
->map(fn($item)=>['id'=>$item->id,'name'=>sprintf('%s: %s',$item->supplied->supplier->name,$item->supplied->name)])
->sortBy('name')
->values();
case Product\Generic::class:
return Product\Generic::select(['id','supplier_item_id'])
->with(['supplied.supplier_detail.supplier'])
->get()
->map(fn($item)=>['id'=>$item->id,'name'=>sprintf('%s: %s',$item->supplied->supplier->name,$item->supplied->name)])
->sortBy('name')
->values();
case Product\Host::class:
return Product\Host::select(['id','supplier_item_id'])
->with(['supplied.supplier_detail.supplier'])
->get()
->map(fn($item)=>['id'=>$item->id,'name'=>sprintf('%s: %s',$item->supplied->supplier->name,$item->supplied->name)])
->sortBy('name')
->values();
case Product\Phone::class:
return Product\Phone::select(['id','supplier_item_id'])
->with(['supplied.supplier_detail.supplier'])
->get()
->map(fn($item)=>['id'=>$item->id,'name'=>sprintf('%s: %s',$item->supplied->supplier->name,$item->supplied->name)])
->sortBy('name')
->values();
default:
throw new \Exception('Unknown type: '.$request->type);
}
}
public function addedit(ProductAddEdit $request,Product $o)
{
foreach (Arr::except($request->validated(),['translate','accounting','pricing','active']) as $key => $item)
$o->{$key} = $item;
$o->active = (bool)$request->active;
// Trim down the pricing array, remove null values
$o->pricing = collect($request->validated('pricing'))
->map(function($item) {
foreach ($item as $k=>$v) {
if (is_array($v)) {
$v = array_filter($v);
$item[$k] = $v;
}
}
return $item;
});
try {
$o->save();
} catch (\Exception $e) {
return redirect()
->back()
->withErrors($e->getMessage())
->withInput();
}
$o->load(['translate']);
$oo = $o->translate ?: new ProductTranslate;
foreach ($request->validated('translate',[]) as $key => $item)
$oo->{$key} = $item;
$o->translate()->save($oo);
foreach ($request->validated('accounting',[]) as $k=>$v) {
$o->providers()->syncWithoutDetaching([
$k => [
'ref' => $v,
'site_id'=>$o->site_id,
],
]);
}
return redirect()
->back()
->with('success','Product saved');
}
}

View File

@ -2,18 +2,27 @@
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use App\Models\Account;
use Auth;
class ResellerServicesController extends Controller
{
public function services(Request $request,Account $o)
public function accounts()
{
return $o->services
->filter(function($item) use ($request) {
return $item->active || ($item->id == $request->include);
});
return ['data'=>Auth::user()->all_accounts()->values()];
}
public function agents()
{
return ['data'=>Auth::user()->all_agents()->values()];
}
public function clients()
{
return ['data'=>Auth::user()->all_clients()->values()];
}
public function service_inactive()
{
return ['data'=>Auth::user()->all_client_service_inactive()->values()];
}
}

View File

@ -3,11 +3,10 @@
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Collection;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Gate;
use App\Models\{Account,Invoice,Payment,Service,User};
use App\Models\{Account,Invoice,Service,Service\Adsl,User};
class SearchController extends Controller
{
@ -15,87 +14,73 @@ class SearchController extends Controller
* Search from the Application Dashboard.
*
* @param Request $request
* @return Collection
* @return Response
*/
public function search(Request $request): Collection
public function search(Request $request)
{
$result = collect();
// If the user isnt logged in
if (! Auth::user())
abort(401,'Need to login');
// If there isnt a term value, return null
if (! $request->input('term'))
return $result;
return [];
$account_ids = ($x=Auth::user()->accounts_all)->pluck('id');
$user_ids = $x->pluck('user_id')->unique();
$result = collect();
$accounts = ($x=Auth::user()->all_accounts())->pluck('id');
$users = $x->transform(function($item) { return $item->user;});
// Look for User
# Look for User
foreach (User::Search($request->input('term'))
->whereIN('id',$user_ids)
->whereIN('id',$users->pluck('id'))
->orderBy('lastname')
->orderBy('firstname')
->limit(10)->get() as $o)
{
$result->push(['name'=>sprintf('%s (%s) - %s',$o->name,$o->lid,$o->email),'value'=>'/u/home/'.$o->id,'category'=>'Users']);
$result->push(['name'=>sprintf('%s %s',$o->aid,$o->name),'value'=>'/u/home/'.$o->id,'category'=>'Users']);
}
// Look for User by their Supplier ID with some suppliers
if (is_numeric($request->input('term')))
foreach (Account::select(['user_id','suppliers.name AS supplier_name','account_supplier.supplier_ref AS pivot_id'])
->join('account_supplier',['account_supplier.account_id'=>'accounts.id'])
->join('suppliers',['suppliers.id'=>'account_supplier.supplier_id'])
->whereIN('accounts.id',$account_ids)
->where('account_supplier.supplier_ref','like','%'.$request->input('term').'%')
->orderBy('company')
->limit(10)->get() as $o)
{
$result->push(['name'=>sprintf('%s (%s:%s)',$o->company,$o->supplier_name,$o->supplier_ref),'value'=>'/u/home/'.$o->user_id,'category'=>'Suppliers']);
}
// Look for Account
# Look for Account
foreach (Account::Search($request->input('term'))
->whereIN('id',$account_ids)
->orderBy('company')
->limit(10)->get() as $o)
->whereIN('user_id',$users->pluck('id'))
->orderBy('company')
->limit(10)->get() as $o)
{
$result->push(['name'=>sprintf('%s (%s)',$o->name,$o->lid),'value'=>'/u/home/'.$o->user_id,'category'=>'Accounts']);
$result->push(['name'=>sprintf('%s %s',$o->aid,$o->company),'value'=>'/u/home/'.$o->user_id,'category'=>'Accounts']);
}
// Look for a Service
# Look for a Service
foreach (Service::Search($request->input('term'))
->whereIN('account_id',$account_ids)
->orderBy('id')
->limit(20)
->with(['product'])
->get() as $o)
->whereIN('account_id',$accounts)
->orderBy('id')
->limit(10)->get() as $o)
{
$result->push(['name'=>sprintf('%s (%s) %s',$o->name,$o->lid,$o->active ? '' : '<small>INACT</small>'),'value'=>'/u/service/'.$o->id,'category'=>$o->product->category_name]);
$result->push(['name'=>sprintf('%s (%s)',$o->name,$o->sid),'value'=>'/u/service/'.$o->id,'category'=>'Services']);
}
// Look for an Invoice
# Look for an Invoice
foreach (Invoice::Search($request->input('term'))
->whereIN('account_id',$account_ids)
->orderBy('id')
->limit(10)->get() as $o)
->whereIN('account_id',$accounts)
->orderBy('id')
->limit(10)->get() as $o)
{
$result->push(['name'=>sprintf('%s: %s',$o->lid,$o->account->name),'value'=>'/u/invoice/'.$o->id,'category'=>'Invoices']);
$result->push(['name'=>sprintf('%s: %s',$o->sid,$o->account->name),'value'=>'/u/invoice/'.$o->id,'category'=>'Invoices']);
}
if (Gate::any(['wholesaler'],new Payment)) {
// Look for Payments
foreach (Payment::Search($request->input('term'))
->whereIN('account_id',$account_ids)
->limit(10)->get() as $o)
{
$result->push(['name'=>sprintf('%s: %s $%s',$o->lid,$o->account->name,number_format($o->total,2)),'value'=>'/r/payment/addedit/'.$o->id,'category'=>'Payments']);
}
# Look for an ADSL/NBN Service
foreach (Adsl::Search($request->input('term'))
->whereIN('account_id',$accounts)
->orderBy('service_number')
->limit(10)->get() as $o)
{
$result->push(['name'=>sprintf('%s (%s)',$o->name,$o->service->sid),'value'=>'/u/service/'.$o->id,'category'=>'Broadband']);
}
return $result
->sortBy(fn($item)=>$item['category'].$item['name'])
->values();
# Look for Domain Name
foreach (Service\Domain::Search($request->input('term'))
->whereIN('account_id',$accounts)
->orderBy('domain_name')
->limit(10)->get() as $o)
{
$result->push(['name'=>sprintf('%s (%s)',$o->service_name,$o->service->sid),'value'=>'/u/service/'.$o->id,'category'=>'Domains']);
}
return $result;
}
}

View File

@ -2,479 +2,146 @@
namespace App\Http\Controllers;
use Carbon\Carbon;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Session;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;
use Illuminate\Validation\ValidationException;
use Illuminate\View\View;
use Symfony\Component\HttpKernel\Exception\HttpException;
use App\Http\Requests\{ServiceCancel,ServiceChange,ServiceChangeRequest};
use App\Mail\{CancelRequest,ChangeRequest};
use App\Models\{Charge,Invoice,Product,Service};
use App\Models\Service;
class ServiceController extends Controller
{
/* SERVICE WORKFLOW METHODS */
/**
* Cancel a request to cancel a service
* Edit a domain service details
*
* @param Request $request
* @param Service $o
* @return bool
* @return \Illuminate\Http\RedirectResponse
*/
private function action_cancel_cancel(Service $o): bool
public function domain_edit(Request $request,Service $o)
{
if (! $o->order_info)
$o->order_info = collect();
session()->flash('service_update',TRUE);
$o->order_info->put('cancel_cancel',Carbon::now()->format('Y-m-d H:i:s'));
$o->order_status = 'ACTIVE';
$o->stop_at = NULL;
$validation = $request->validate([
'service.domain_name' => sprintf('required|unique:%s,domain_name,%d',$o->type->getTable(),$o->type->id),
'service.domain_expire' => 'required|date',
'service.domain_tld_id' => 'required|exists:ab_domain_tld,id',
'service.domain_registrar_id' => 'required|exists:ab_domain_registrar,id',
'service.registrar_account' => 'required',
'service.registrar_username' => 'required|string|min:5',
'service.registrar_ns' => 'required|string|min:5',
]);
return $o->save();
}
$o->type->forceFill($validation['service'])->save();
private function action_cancel_pending_enter(Service $o): bool
{
$o->order_status = 'CANCEL-PENDING';
return $o->save();
}
private function action_cancelled(Service $o): bool
{
$o->order_status = 'CANCELLED';
$o->active = FALSE;
return $o->save();
}
/**
* Cancel a request to change a service
*
* @param Service $o
* @return bool
*/
private function action_change_cancel(Service $o): bool
{
if (! $o->order_info)
$o->order_info = collect();
// @todo add some validation if this doesnt return a result
$np = $o->changes()->where('service__change.active',TRUE)->where('complete',FALSE)->get()->pop();
$np->pivot->active = FALSE;
$np->pivot->save();
$o->order_status = 'ACTIVE';
return $o->save();
}
/**
* Action to change a service order_status to another stage
* This is a generic function that can redirect the user to a page that is required to completed to enter
* the new stage
*
* @param Service $o
* @param string $stage
* @return \Illuminate\Contracts\Foundation\Application|RedirectResponse|\Illuminate\Routing\Redirector
*/
private function action_request_enter_redirect(Service $o,string $stage)
{
return redirect(sprintf('u/service/%d/%s',$o->id,strtolower($stage)));
}
/* OTHER METHODS */
public function change_pending(ServiceChangeRequest $request,Service $o)
{
// @todo add some validation if this doesnt return a result
$np = $o->changes()->where('service__change.active',TRUE)->where('complete',FALSE)->get()->pop();
if ($request->post()) {
foreach ($this->service_change_charges($request,$o) as $co)
$co->save();
$o->product_id = Arr::get($request->broadband,'product_id');
$o->price = Arr::get($request->broadband,'price');
$o->order_status = 'ACTIVE';
$o->save();
$np->pivot->complete = TRUE;
$np->pivot->effective_at = Carbon::now();
$np->pivot->save();
return redirect()->to(url('u/service',[$o->id]));
}
return view('theme.backend.adminlte.service.change_pending')
->with('breadcrumb',collect()->merge($o->account->breadcrumb))
->with('o',$o)
->with('np',$np);
}
/**
* Process a request to cancel a service
*
* @param ServiceCancel $request
* @param Service $o
* @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View|RedirectResponse|\Illuminate\Routing\Redirector
*/
public function cancel_request(ServiceCancel $request,Service $o)
{
if (! $o->order_info)
$o->order_info = collect();
$o->stop_at = $request->stop_at;
$o->order_info->put('cancel_note',$request->validated('notes'));
if ($request->validated('extra_charges'))
$o->order_info->put('cancel_extra_charges_accepted',$request->extra_charges_amount);
$o->order_status = 'CANCEL-REQUEST';
$o->save();
Mail::to(config('osb.ticket_admin'))
->queue((new CancelRequest($o,$request->notes))->onQueue('email'));
return redirect('u/service/'.$o->id)
->with('success','Cancellation lodged');
}
/**
* Change the status of a service
* @todo This needs to be optimized
*
* @note This route is protected by middleware @see ServicePolicy::progress()
* It is assumed that the next stage is valid for the services current stage - validated in ServicePolicy::progress()
* @param Service $o
* @param string $stage
* @return RedirectResponse
*/
public function change(Service $o,string $stage): RedirectResponse
{
// While stage has a string value, that indicates the next stage we want to go to
// If stage is NULL, the current stage hasnt been completed
// If stage is FALSE, then the current stage failed, and may optionally be directed to another stage.
while ($stage) {
// Check that stage is a valid next action for the user currently performing it
//$current = $this->getStageParameters($this->order_status);
$next = $o->getStageParameters($stage);
// If valid, call the method to confirm that the current stage is complete
if ($x=$next->get('enter_method')) {
if (! method_exists($this,$x))
abort(500,sprintf('ENTER_METHOD [%s]defined doesnt exist',$x));
Log::debug(sprintf('Running ENTER_METHOD [%s] on Service [%d] to go to stage [%s]',$x,$o->id,$stage));
// @todo Should call exit_method of the current stage first, to be sure we can change
try {
$result = $this->{$x}($o,$stage);
// If we have a form to complete, we need to return with a URL, so we can catch that with an Exception
} catch (HttpException $e) {
if ($e->getStatusCode() == 301)
return ($e->getMessage());
}
// An Error Condition
if (is_null($result))
return redirect()->to('u/service/'.$o->id);
elseif ($result instanceof RedirectResponse)
return $result;
// The service cannot enter the next stage
elseif (! $result)
abort(500,'Current Method FAILED: '.$result);
} else {
$o->order_status = $stage;
if ($stage == 'ACTIVE')
$o->active = TRUE;
$o->save();
}
// If valid, call the method to start the next stage
$stage = ''; // @todo this is temporary, we havent written the code to automatically jump to the next stage if wecan
}
return redirect()->to('u/service/'.$o->id);
}
/**
* Process a request to cancel a service
*
* @param ServiceChange $request
* @param Service $o
* @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View|RedirectResponse|\Illuminate\Routing\Redirector
*/
public function change_request(ServiceChange $request,Service $o)
{
$o->changes()->attach([$o->id=>[
'site_id'=> $o->site_id,
'ordered_by' => Auth::id(),
'ordered_at' => Carbon::now(),
'effective_at' => $request->validated('change_date'),
'product_id' => $request->validated('product_id'),
'notes' => $request->validated('notes'),
'active' => TRUE,
'complete' => FALSE,
]]);
$o->order_status = 'CHANGE-REQUEST';
$o->save();
Mail::to(config('osb.ticket_admin'))
->queue((new ChangeRequest($o,$request->validated('notes')))->onQueue('email'));
return redirect('u/service/'.$o->id)
->with('success','Upgrade requested');
return redirect()->back()->with('success','Record updated.');
}
/**
* List all the domains managed by the user
*
* @return View
* @todo revalidate
*/
public function domain_list(): View
{
$o = Service\Domain::ServiceActive()
->AccountUserAuthorised('services')
->select('service_domain.*')
->join('services',['services.id'=>'service_domain.service_id'])
$o = Service\Domain::serviceActive()
->serviceUserAuthorised(Auth::user())
->select('service_domains.*')
->join('ab_service',['ab_service.id'=>'service_domains.service_id'])
->with(['service.account','registrar'])
->get();
return view('theme.backend.adminlte.service.domain.list')
->with('o',$o);
}
public function email_list(): View
{
$o = Service\Email::ServiceActive()
->AccountUserAuthorised('services')
->select('service_email.*')
->join('services',['services.id'=>'service_email.service_id'])
->with(['service.account','service.product.type.supplied.supplier_detail.supplier','tld'])
->get();
return view('theme.backend.adminlte.service.email.list')
return view('r.service.domain.list')
->with('o',$o);
}
/**
* Return details on the users service
*
* @param Service $o
* @return View
*/
public function home(Service $o): View
{
return view('theme.backend.adminlte.service.home')
->with('breadcrumb',collect()->merge($o->account->breadcrumb))
->with('o',$o);
}
public function hosting_list(): View
{
$o = Service\Host::ServiceActive()
->AccountUserAuthorised('services')
->select('service_host.*')
->join('services',['services.id'=>'service_host.service_id'])
->with(['service.account','service.product.type.supplied.supplier_detail.supplier','tld'])
->get();
return view('theme.backend.adminlte.service.hosting.list')
->with('o',$o);
}
private function service_change_charges(Request $request,Service $o): Collection
{
$charges = collect();
$po = Product::findOrFail(Arr::get($request->broadband,'product_id'));
$start_at = Carbon::create(Arr::get($request->broadband,'start_at'));
// Get the invoiced items covering the start_at date
foreach ($o->invoiced_items
->filter(fn($item)=>($item->start_at < $start_at) && ($item->stop_at > $start_at) && ($item->item_type === 0)) as $iio)
{
// Reverse the original charge
$co = new Charge;
$co->active = TRUE;
$co->service_id = $o->id;
$co->account_id = $o->account_id;
$co->sweep_type = 6;
$co->product_id = $iio->product_id;
$co->description = 'Plan Upgrade Adjustment';
$co->user_id = Auth::id();
$co->type = $iio->item_type;
$co->start_at = $start_at;
$co->stop_at = $iio->stop_at;
$co->amount = $iio->price_base;
$co->taxable = TRUE; // @todo this should be determined
$co->quantity = -1*$start_at->diffInDays($iio->stop_at)/$iio->start_at->diffInDays($iio->stop_at);
$charges->push($co);
// Add the new charge
$co = new Charge;
$co->active = TRUE;
$co->service_id = $o->id;
$co->account_id = $o->account_id;
$co->sweep_type = 6;
$co->product_id = $po->id;
$co->description = 'Plan Upgrade Adjustment';
$co->user_id = Auth::id();
$co->type = $iio->item_type;
$co->start_at = $start_at;
$co->stop_at = $iio->stop_at;
$co->amount = Arr::get($request->broadband,'price') ?: $po->base_charge;
$co->taxable = TRUE; // @todo this should be determined
$co->quantity = $start_at->diffInDays($iio->stop_at)/$iio->start_at->diffInDays($iio->stop_at);
$charges->push($co);
}
// Add any fee
if (Arr::get($request->broadband,'change_fee')) {
$co = new Charge;
$co->active = TRUE;
$co->service_id = $o->id;
$co->account_id = $o->account_id;
$co->sweep_type = 6;
$co->product_id = $po->id;
$co->description = 'Plan Upgrade Fee';
$co->user_id = Auth::id();
$co->type = 3;
$co->start_at = $start_at;
$co->stop_at = $start_at;
$co->amount = Arr::get($request->broadband,'change_fee');
$co->taxable = TRUE; // @todo this should be determined
$co->quantity = 1;
$charges->push($co);
}
return $charges;
}
/**
* This is an API method, that works with service change - to return the new charges as a result of changing a service
* Update a service
*
* @note: Route Middleware protects this path
* @param Request $request
* @param Service $o
* @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View
*/
public function service_change_charges_display(Request $request,Service $o)
{
return view('theme.backend.adminlte.service.change_charge')
->with('charges',$this->service_change_charges($request,$o));
}
/**
* Update details about a service
*
* @param Request $request
* @param Service $o
* @return RedirectResponse
* @throws ValidationException
* @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View|\Illuminate\Http\RedirectResponse
*/
public function update(Request $request,Service $o)
{
Session::put('service_update',true);
switch ($o->order_status) {
case 'CANCEL-REQUEST':
if ($request->post()) {
if (! $request->post('date_end'))
return redirect()->back()->withErrors('Cancellation Date not provided');
// We dynamically create our validation
$validator = Validator::make(
$request->post(),
collect($o->type->validation())
->keys()
->map(fn($item)=>sprintf('%s.%s',$o->product->category,$item))
->combine(array_values($o->type->validation()))
->map(fn($item)=>is_string($item)
? preg_replace('/^exclude_without:/',sprintf('exclude_without:%s.',$o->product->category),$item)
: $item)
->merge(
[
'external_billing' => 'nullable|in:on',
'suspend_billing' => 'nullable|in:on',
'recur_schedule' => ['required',Rule::in(collect(Invoice::billing_periods)->keys())],
'invoice_next_at' => 'nullable|date',
'price' => 'nullable|numeric',
$o->product->category => 'array|min:1',
]
)
->toArray()
);
$o->date_end = $request->post('date_end');
if ($validator->fails()) {
return redirect()
->back()
->withErrors($validator)
->withInput();
}
foreach (['cancel_notes'] as $key) {
if ($request->post($key))
$o->setOrderInfo($key,$request->post($key));
}
$validated = collect($validator->validated());
$o->order_status='CANCEL-PENDING';
$o->save();
// Store our service type values
$o->type->forceFill($validated->get($o->product->category));
// Some special handling
switch ($o->product->category) {
case 'broadband':
// If pppoe is not set, then we dont need username/password
$o->type->pppoe = ($x=data_get($validated,$o->product->category.'.pppoe',FALSE));
if (! $x) {
$o->type->service_username = NULL;
$o->type->service_password = NULL;
return redirect()->to(url('u/service',$o->id))->with('updated','Service cancellation submitted.');
}
break;
return $this->update_request_cancel($o);
case 'ORDER-SENT':
if ($request->post()) {
foreach (['reference','notes'] as $key) {
$o->setOrderInfo($key,$request->post($key));
}
$o->save();
foreach ($request->post($o->stype) as $k=>$v) {
$o->type->{$k} = $v;
}
$o->type->save();
return redirect()->to(url('u/service',$o->id))->with('updated','Order sent notes updated.');
}
return $this->update_order_status($o);
case 'PROVISION-PLANNED':
if ($request->post()) {
foreach (['provision_notes'] as $key) {
$o->setOrderInfo($key,$request->post($key));
}
$o->date_start = $request->post('date_start');
$o->save();
foreach ($request->post($o->stype) as $k=>$v) {
$o->type->{$k} = $v;
}
$o->type->save();
return redirect()->to(url('u/service',$o->id))->with('updated','Order sent notes updated.');
}
return $this->update_provision_planned($o);
default:
abort(499,'Not yet implemented');
}
}
$o->type->save();
private function update_order_status(Service $o)
{
return View('r.service.order.sent',['o'=>$o]);
}
if ($validated->has('invoice_next_at'))
$o->invoice_next_at = $validated?->get('invoice_next_at');
private function update_request_cancel(Service $o)
{
return View('u.service.order.cancel',['o'=>$o]);
}
if ($validated->has('recur_schedule'))
$o->recur_schedule = $validated->get('recur_schedule');
$o->suspend_billing = ($validated->get('suspend_billing') == 'on');
$o->external_billing = ($validated->get('external_billing') == 'on');
$o->price = $validated->get('price');
// Also update our service start_at date.
// @todo We may want to make start_at/stop_at dynamic values calculated by the type records
if ($validated->has('start_at'))
$o->start_at = $validated->get('start_at');
else {
// For broadband, start_at is connect_at in the type record
switch ($o->product->category) {
case 'broadband':
$o->start_at = $o->type->connect_at;
break;
}
}
$o->save();
return redirect()
->back()
->with('success','Record Updated');
private function update_provision_planned(Service $o)
{
return View('r.service.order.provision_plan',['o'=>$o]);
}
}

View File

@ -1,149 +0,0 @@
<?php
namespace App\Http\Controllers;
use Carbon\Carbon;
use Illuminate\Http\Request;
use Illuminate\Support\Arr;
use App\Http\Requests\{SupplierAddEdit,SupplierProductAddEdit};
use App\Jobs\ImportCosts;
use App\Models\{Supplier,SupplierDetail};
class SupplierController extends Controller
{
/**
* Update a suppliers details
*
* @param SupplierAddEdit $request
* @param Supplier $o
* @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View|\Illuminate\Http\RedirectResponse
*/
public function addedit(SupplierAddEdit $request,Supplier $o)
{
foreach (Arr::except($request->validated(),['supplier_details','api_key','api_secret','submit']) as $key => $item)
$o->{$key} = $item;
$o->active = (bool)$request->validated('active');
try {
$o->save();
} catch (\Exception $e) {
return redirect()->back()->withErrors($e->getMessage())->withInput();
}
$o->load(['detail']);
$oo = $o->detail ?: new SupplierDetail;
foreach ($request->get('supplier_details',[]) as $key => $item)
$oo->{$key} = $item;
$oo->connections = $oo->connections->merge([
'api_key'=>$request->get('api_key'),
'api_secret'=>$request->get('api_secret'),
])->filter();
$o->detail()->save($oo);
return redirect()
->back()
->with('success','Supplier Saved');
}
public function cost_submit(Request $request,Supplier $o)
{
$request->validate([
'file' => 'required|filled',
'billed_at' => 'required|date',
]);
$filename = $request->file('file')->store('cost_import');
ImportCosts::dispatch(
config('site'),
$o,
Carbon::create($request->billed_at),
$filename,
)->onQueue('low');
return redirect()
->back()
->with('success','File uploaded');
}
public function product_addedit(SupplierProductAddEdit $request,Supplier $o,int $id,string $type)
{
// Quick validation
if ($type !== $request->offering_type)
abort(500,'Type and offering type do not match');
if ($o->exists && ($o->detail->id !== (int)$request->supplier_detail_id))
abort(500,sprintf('Supplier [%d] and supplier_detail_id [%d] do not match',$o->detail->id,$request->supplier_detail_id));
switch ($request->offering_type) {
case 'broadband':
$oo = Supplier\Broadband::findOrNew($id);
// @todo these are broadband requirements - get them from the broadband class.
foreach (Arr::only($request->validated(),[
'supplier_detail_id',
'product_id'.
'product_desc',
'base_cost',
'setup_cost',
'contract_term',
'metric',
'speed',
'technology',
'offpeak_start',
'offpeak_end',
'base_down_peak',
'base_up_peak',
'base_down_offpeak',
'base_up_offpeak',
'extra_down_peak',
'extra_up_peak',
'extra_down_offpeak',
'extra_up_offpeak',
]) as $key => $value)
$oo->$key = $value;
// Our boolean values
foreach (Arr::only($request->validated(),['active','extra_shaped','extra_charged']) as $key => $value)
$oo->{$key} = ($value == 'on' ? 1 : 0);
break;
default:
throw new \Exception('Unknown offering type:'.$request->offering_type);
}
$oo->save();
return redirect()
->back()
->with('success','Saved');
}
/**
* Return the form for a specific product type
*
* @param Request $request
* @param string $type
* @param int|null $id
* @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View
*/
public function product_view_type(Request $request,string $type,int $id=NULL)
{
$o = $id ? Supplier::offeringTypeClass($type)->findOrFail($id) : NULL;
if ($request->old)
$request->session()->flashInput($request->old);
if ($o)
$o->load(['products.products.services']);
return view('theme.backend.adminlte.supplier.product.widget.'.$type)
->with('o',$id ? $o : NULL)
->withErrors($request->errors);
}
}

View File

@ -0,0 +1,102 @@
<?php
namespace App\Http\Controllers;
use App\Models\Supplier;
use Illuminate\Http\Request;
class SuppliersController extends Controller
{
public function __construct()
{
$this->middleware('auth ');
}
/**
* Display a listing of the resource.
*
* @return \Illuminate\Http\Response
*/
public function index()
{
return view('r/supplier/index');
}
/**
* Show the form for creating a new resource.
*
* @return \Illuminate\Http\Response
*/
public function create()
{
return view('r/supplier/create');
}
/**
* Store a newly created resource in storage.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function store(Request $request)
{
// @todo Insure site index is included.
$o = new Supplier;
$o->name = $request->input('name');
$o->active = 1;
$o->account_mgr = '';
$o->account_email = '';
$o->email_provision = '';
$o->email_support = '';
$o->phone_provision = '';
$o->phone_support = '';
$o->save();
echo 'REDIRECT TO <a href="'.url('r/supplier/index').'">here</a>';
}
/**
* Display the specified resource.
*
* @param \App\suppliers $suppliers
* @return \Illuminate\Http\Response
*/
public function show(suppliers $suppliers)
{
//
}
/**
* Show the form for editing the specified resource.
*
* @param \App\suppliers $suppliers
* @return \Illuminate\Http\Response
*/
public function edit(suppliers $suppliers)
{
//
}
/**
* Update the specified resource in storage.
*
* @param \Illuminate\Http\Request $request
* @param \App\suppliers $suppliers
* @return \Illuminate\Http\Response
*/
public function update(Request $request, suppliers $suppliers)
{
//
}
/**
* Remove the specified resource from storage.
*
* @param \App\suppliers $suppliers
* @return \Illuminate\Http\Response
*/
public function destroy(suppliers $suppliers)
{
//
}
}

View File

@ -0,0 +1,43 @@
<?php
namespace App\Http\Controllers\User;
use App\Http\Controllers\Controller;
use App\Models\{Account,Invoice};
class AccountController extends Controller
{
/**
* Show the users next invoice
*/
public function view_invoice_next(Account $o)
{
$io = new Invoice;
$io->account = $o;
// Get the account services
$s = $o->services(TRUE)
->with(['invoice_items','charges'])
->get()
->filter(function($item) {
return ! $item->suspend_billing AND ! $item->external_billing;
});
// Get our invoice due date for this invoice
$io->due_date = $s->min(function($item) { return $item->invoice_next; });
// @todo The days in advance is an application parameter
$io->date_orig = $io->due_date->subDays(30);
// Work out items to add to this invoice, plus any in the next additional days
$days = now()->diffInDays($io->due_date)+1+7;
foreach ($s as $so)
{
if ($so->isInvoiceDueSoon($days))
foreach ($so->next_invoice_items() as $o)
$io->items->push($o);
}
return View('u.invoice.home',['o'=>$io]);
}
}

View File

@ -1,74 +0,0 @@
<?php
namespace App\Http\Controllers;
use Carbon\Carbon;
use Illuminate\Http\RedirectResponse;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Session;
use App\Http\Requests\{AccountSupplierAdd,UserEdit};
use App\Models\{Account,Supplier,User};
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
*
* @param AccountSupplierAdd $request
* @param Account $o
* @return RedirectResponse
*/
public function supplier_addedit(AccountSupplierAdd $request,Account $o): RedirectResponse
{
$o->suppliers()->attach([
$request->validated('supplier_id') => [
'supplier_ref'=>$request->validated('supplier_ref'),
'created_at'=>Carbon::now(),
]
]);
return redirect()
->back()
->with('success','Supplier Added');
}
/**
* Remove a supplier from a user's profile
*
* @param Account $o
* @param Supplier $so
* @return RedirectResponse
*/
public function supplier_delete(Account $o,Supplier $so): RedirectResponse
{
Session::put('supplier_update',TRUE);
$o->suppliers()->detach([$so->id]);
return redirect()
->back()
->with('success','Supplier Deleted');
}
}

View File

@ -0,0 +1,19 @@
<?php
namespace App\Http\Controllers;
class WelcomeController extends Controller
{
public function __construct()
{
$this->middleware('demoMode');
}
public function home() {
return view('welcome.home');
}
public function under_construction() {
abort(499,'Under Construction');
}
}

View File

@ -0,0 +1,13 @@
<?php
namespace App\Http\Controllers\Wholesale;
use App\Http\Controllers\Controller;
class ReportController extends Controller
{
public function products()
{
return view('a/product/report');
}
}

86
app/Http/Kernel.php Normal file
View File

@ -0,0 +1,86 @@
<?php
namespace App\Http;
use Illuminate\Foundation\Http\Kernel as HttpKernel;
class Kernel extends HttpKernel
{
/**
* The application's global HTTP middleware stack.
*
* These middleware are run during every request to your application.
*
* @var array
*/
protected $middleware = [
\App\Http\Middleware\CheckForMaintenanceMode::class,
\Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
\App\Http\Middleware\TrimStrings::class,
\Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
\App\Http\Middleware\TrustProxies::class,
\App\Http\Middleware\SetSite::class,
];
/**
* The application's route middleware groups.
*
* @var array
*/
protected $middlewareGroups = [
'web' => [
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
// \Illuminate\Session\Middleware\AuthenticateSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\VerifyCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
// \App\Http\Middleware\SetSite::class,
\Laravel\Passport\Http\Middleware\CreateFreshApiToken::class,
],
'api' => [
'throttle:60,1',
'bindings',
],
];
/**
* The application's route middleware.
*
* These middleware may be assigned to groups or used individually.
*
* @var array
*/
protected $routeMiddleware = [
'auth' => \App\Http\Middleware\Authenticate::class,
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,
'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
'can' => \Illuminate\Auth\Middleware\Authorize::class,
'demoMode' => \Spatie\DemoMode\DemoMode::class,
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
'role' => \App\Http\Middleware\Role::class,
'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class,
'theme' => \Igaster\LaravelTheme\Middleware\setTheme::class,
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
];
/**
* The priority-sorted list of middleware.
*
* This forces non-global middleware to always be in the given order.
*
* @var array
*/
protected $middlewarePriority = [
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\Authenticate::class,
\Illuminate\Session\Middleware\AuthenticateSession::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
\Illuminate\Auth\Middleware\Authorize::class,
];
}

View File

@ -0,0 +1,21 @@
<?php
namespace App\Http\Middleware;
use Illuminate\Auth\Middleware\Authenticate as Middleware;
class Authenticate extends Middleware
{
/**
* Get the path the user should be redirected to when they are not authenticated.
*
* @param \Illuminate\Http\Request $request
* @return string
*/
protected function redirectTo($request)
{
if (! $request->expectsJson()) {
return route('login');
}
}
}

View File

@ -0,0 +1,17 @@
<?php
namespace App\Http\Middleware;
use Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode as Middleware;
class CheckForMaintenanceMode extends Middleware
{
/**
* The URIs that should be reachable while maintenance mode is enabled.
*
* @var array
*/
protected $except = [
//
];
}

View File

@ -0,0 +1,17 @@
<?php
namespace App\Http\Middleware;
use Illuminate\Cookie\Middleware\EncryptCookies as Middleware;
class EncryptCookies extends Middleware
{
/**
* The names of the cookies that should not be encrypted.
*
* @var array
*/
protected $except = [
'toggleState',
];
}

View File

@ -0,0 +1,26 @@
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Support\Facades\Auth;
class RedirectIfAuthenticated
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @param string|null $guard
* @return mixed
*/
public function handle($request, Closure $next, $guard = null)
{
if (Auth::guard($guard)->check()) {
return redirect('/home');
}
return $next($request);
}
}

View File

@ -2,19 +2,15 @@
namespace App\Http\Middleware;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Closure;
/**
* Enable us to use the role during middleware authorisation
*/
class Role
{
public function handle(Request $request, Closure $next, string $role)
public function handle($request, Closure $next, $role)
{
if ($role AND ! Auth::user())
abort(403,'Not Authenticated');
return abort(303,'Not Authenticated');
switch ($role) {
case 'wholesaler':

View File

@ -2,10 +2,10 @@
namespace App\Http\Middleware;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\View;
use Closure;
use App\Models\Site;
@ -20,11 +20,11 @@ class SetSite
/**
* Handle an incoming request.
*
* @param Request $request
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle(Request $request,\Closure $next): mixed
public function handle($request, Closure $next)
{
$so = new Site;
@ -43,7 +43,7 @@ class SetSite
}
// Set who we are in SETUP.
Config::set('site',$so);
Config::set('SITE',$so);
if (! $request->ajax())
View::share('site',$so);

View File

@ -0,0 +1,18 @@
<?php
namespace App\Http\Middleware;
use Illuminate\Foundation\Http\Middleware\TrimStrings as Middleware;
class TrimStrings extends Middleware
{
/**
* The names of the attributes that should not be trimmed.
*
* @var array
*/
protected $except = [
'password',
'password_confirmation',
];
}

View File

@ -0,0 +1,23 @@
<?php
namespace App\Http\Middleware;
use Illuminate\Http\Request;
use Fideloper\Proxy\TrustProxies as Middleware;
class TrustProxies extends Middleware
{
/**
* The trusted proxies for this application.
*
* @var array
*/
protected $proxies;
/**
* The headers that should be used to detect proxies.
*
* @var int
*/
protected $headers = Request::HEADER_X_FORWARDED_ALL;
}

View File

@ -0,0 +1,24 @@
<?php
namespace App\Http\Middleware;
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as Middleware;
class VerifyCsrfToken extends Middleware
{
/**
* Indicates whether the XSRF-TOKEN cookie should be set on the response.
*
* @var bool
*/
protected $addHttpCookie = true;
/**
* The URIs that should be excluded from CSRF verification.
*
* @var array
*/
protected $except = [
//
];
}

View File

@ -1,42 +0,0 @@
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\Facades\Session;
use Illuminate\Validation\Rule;
class AccountSupplierAdd 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
{
Session::put('supplier_update',true);
return [
'supplier_ref'=> [
'required',
'string',
'min:2',
Rule::unique('account_supplier')
->where(fn($query)=>$query
->where('account_id',request()->get('account_id')))
->where('supplier_id',request()->get('supplier_id')),
],
'supplier_id'=>'required|exists:suppliers,id',
];
}
}

View File

@ -1,46 +0,0 @@
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Session;
use App\Models\{Charge,InvoiceItem};
class ChargeAdd extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return Auth::user()
->accounts_all
->contains(request()->post('account_id'));
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
Session::put('charge_add',true);
return [
'id' => 'sometimes|exists:charges,id',
'account_id' => 'required|exists:accounts,id',
'charge_at' => 'required|date',
'service_id' => 'required|exists:services,id',
'site_id' => 'required|exists:sites,id',
'quantity' => 'required|numeric|not_in:0',
'amount' => 'required|numeric|min:0.01',
'sweep_type' => 'required|numeric|in:'.implode(',',array_keys(Charge::sweep)),
'type' => 'required|numeric|in:'.implode(',',array_keys(InvoiceItem::type)),
'taxable' => 'nullable|boolean',
'description' => 'nullable|string|min:5|max:128',
];
}
}

View File

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

View File

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

View File

@ -1,57 +0,0 @@
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Gate;
use App\Models\ProviderOauth;
/**
* Editing Suppliers
*/
class ProductAddEdit extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return Gate::allows('wholesaler');
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, mixed>
*/
public function rules()
{
return [
'translate.name_short' => 'required|string|min:2|max:100',
'translate.name_detail' => 'required|string|min:2|max:100',
'translate.description' => 'required|string|min:2|max:65535',
'active' => 'sometimes|accepted',
'model' => 'sometimes|string', // @todo Check that it is a valid model type
'model_id' => 'sometimes|int', // @todo Check that it is a valid model type
'accounting' => [
'nullable',
'array',
function (string $attribute,mixed $value,\Closure $fail) {
if (! is_array($value))
$fail("Invalid format for {$attribute}");
foreach ($value as $k=>$v) {
if (! ProviderOauth::where('id',$k)->exists())
$fail("Provider doesnt exist [$k]");
// @todo Validate that the value is in the accounting system
}
},
],
'pricing' => 'required|array', // @todo Validate the elements in the pricing
];
}
}

View File

@ -1,54 +0,0 @@
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Gate;
/**
* Editing Suppliers
*/
class ServiceCancel extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return Gate::allows('view',$this->route('o'));
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, mixed>
*/
public function rules()
{
//dd(request()->post());
return [
'stop_at'=> [
'required',
'date',
'after:today',
'exclude_unless:extra_charges,null',
function($attribute,$value,$fail) {
if ($this->route('o')->cancel_date->greaterThan($value))
$fail(sprintf('Service cannot be cancelled before: %s',$this->route('o')->cancel_date->format('Y-m-d')));
}
],
'extra_charges_amount' => [
'nullable',
'exclude_unless:extra_charges,null',
function($attribute,$value,$fail) {
if ($this->route('o')->cancel_date->greaterThan(request('stop_at')) && (request('extra_charges') !== 1))
$fail('Extra charges must be accepted if cancelling before contract end');
},
],
'extra_charges' => 'sometimes|required|accepted',
'notes' => 'nullable|min:5',
];
}
}

View File

@ -1,45 +0,0 @@
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Gate;
use Illuminate\Validation\Rule;
/**
* Editing Suppliers
*/
class ServiceChange extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return Gate::allows('view',$this->route('o'));
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, mixed>
*/
public function rules()
{
return [
'change_date'=> [
'required',
'date',
'after:today',
],
'product_id' => [
'required',
'exists:products,id',
Rule::notIn([request()->route('o')->product_id]),
],
'notes' => 'nullable|min:5',
];
}
}

View File

@ -1,41 +0,0 @@
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
class ServiceChangeRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return $this
->route('o')
->AccountUserAuthorised();
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, mixed>
* @todo This is specific to broadband - this needs to be more generic.
*/
public function rules(Request $request)
{
if (! $request->isMethod('post'))
return [];
return [
'broadband.product_id' => 'required|exists:products,id',
'broadband.change_fee' => 'nullable|numeric',
'broadband.price' => 'nullable|numeric',
'broadband.start_at' => 'required|date', // @todo Check that it is not more than 1 billing cycle ago, and not future.
];
}
}

View File

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

View File

@ -1,46 +0,0 @@
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Auth;
/**
* Editing Suppliers
*/
class SupplierAddEdit extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return Auth::user()->isWholesaler();
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, mixed>
*/
public function rules()
{
return [
'name' => 'required|string|min:2|max:255',
'active' => 'sometimes|accepted',
'address1' => 'nullable|string|min:2|max:255',
'address2' => 'nullable|string|min:2|max:255',
'city' => 'nullable|string|min:2|max:64',
'state' => 'nullable|string|min:2|max:32',
'postcode' => 'nullable|string|min:2|max:8',
'supplier_details.notes' => 'nullable|string|min:3',
'supplier_details.accounts' => 'nullable|email',
'supplier_details.support' => 'nullable|email',
'supplier_details.payments' => 'nullable|string|min:3',
'api_key' => 'nullable|string|min:3',
'api_secret' => 'nullable|string|min:3',
];
}
}

View File

@ -1,60 +0,0 @@
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Validation\Rule;
use App\Models\Supplier;
class SupplierProductAddEdit extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return Auth::user()->isWholesaler();
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, mixed>
*/
public function rules(Request $request)
{
// @todo these are broadband requirements - perhaps move them to the broadband class.
// @todo Enhance the validation so that extra_* values are not accepted if base_* values are not included.
return [
'id' => 'required|nullable',
'offering_type' => ['required',Rule::in(Supplier::offeringTypeKeys()->toArray())],
'supplier_detail_id' => 'required|exists:supplier_details,id',
'active' => 'sometimes|accepted',
'extra_shaped' => 'sometimes|accepted',
'extra_charged' => 'sometimes|accepted',
'product_id' => 'required|string|min:2',
'product_desc' => 'required|string|min:2',
'base_cost' => 'required|numeric|min:.01',
'setup_cost' => 'nullable|numeric',
'contract_term' => 'nullable|numeric|min:1',
'metric' => 'nullable|numeric|min:1',
'speed' => 'nullable|string|max:64',
'technology' => 'nullable|string|max:255',
'offpeak_start' => 'nullable|date_format:H:i',
'offpeak_end' => 'nullable|date_format:H:i',
'base_down_peak' => 'nullable|numeric',
'base_up_peak' => 'nullable|numeric',
'base_down_offpeak' => 'nullable|numeric',
'base_up_offpeak' => 'nullable|numeric',
'extra_down_peak' => 'nullable|numeric',
'extra_up_peak' => 'nullable|numeric',
'extra_down_offpeak' => 'nullable|numeric',
'extra_up_offpeak' => 'nullable|numeric',
];
}
}

View File

@ -1,39 +0,0 @@
<?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'
];
}
}

View File

@ -9,12 +9,12 @@ interface IDs
*
* @return mixed
*/
public function getLIDAttribute(): string;
public function getLIDattribute(): string;
/**
* Return the system ID of the item
*
* @return mixed
*/
public function getSIDAttribute(): string;
public function getSIDattribute(): string;
}

View File

@ -1,29 +0,0 @@
<?php
namespace App\Interfaces;
use Illuminate\Support\Collection;
interface ProductItem
{
/**
* Return the traffic inclusion with the service
*
* @return mixed
*/
public function allowance(): Collection;
/**
* Render the traffic inclusion as a string
*
* @return mixed
*/
public function allowance_string(): string;
/**
* Does this offering capture usage information
*
* @return bool
*/
public function hasUsage(): bool;
}

View File

@ -0,0 +1,39 @@
<?php
namespace App\Interfaces;
use Illuminate\Support\Collection;
interface ProductSupplier
{
/**
* Return the traffic inclusion with the service
*
* @return mixed
*/
public function allowance(): Collection;
/**
* Render the traffic inclusion as a string
*
* @return mixed
*/
public function allowance_string(): string;
/**
* Return the product cost
*
* @return float
*/
public function getCostAttribute(): float;
/**
* Return the supplier class
* If there is a model relationship return:
* return $this->getRelationValue('supplier');
* otherwise return a stdClass with name
*
* @return mixed
*/
public function getSupplierAttribute();
}

View File

@ -6,13 +6,6 @@ use Carbon\Carbon;
interface ServiceItem
{
/**
* Months the service is contracted for.
*
* @return int
*/
public function getContractTermAttribute(): int;
/**
* Return the Service Description.
*
@ -23,7 +16,7 @@ interface ServiceItem
/**
* Date the service expires
*/
public function getServiceExpireAttribute(): ?Carbon;
public function getServiceExpireAttribute(): Carbon;
/**
* Return the Service Name.
@ -32,13 +25,6 @@ interface ServiceItem
*/
public function getServiceNameAttribute(): string;
/**
* Has this service expired
*
* @return bool
*/
public function hasExpired(): bool;
/**
* Is this service in a contract
*

View File

@ -6,13 +6,6 @@ use Illuminate\Support\Collection;
interface ServiceUsage
{
/**
* Our model that holds traffic information
*
* @return mixed
*/
public function traffic();
/**
* This service provides usage information
*

View File

@ -1,107 +0,0 @@
<?php
namespace App\Jobs;
use Carbon\Carbon;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Log;
use Intuit\Jobs\AccountingCustomerUpdate;
use App\Models\{Account,ProviderToken};
/**
* Synchronise customers with our accounts.
*
* This will:
* + Create the account in the accounting system
* + Update the account in the accounting system with our data (we are master)
*/
class AccountingAccountSync implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
private const LOGKEY = 'JAS';
private ProviderToken $to;
/**
* Create a new job instance.
*
* @param ProviderToken $to
*/
public function __construct(ProviderToken $to)
{
$this->to = $to;
}
/**
* Execute the job.
*
* @return void
* @throws \Exception
*/
public function handle()
{
$api = $this->to->API();
$ref = Account::select('id','site_id','company','user_id')->with(['user'])->get();
foreach ($api->getCustomers() as $acc) {
$o = NULL;
// See if we are already linked
if (($x=$this->to->provider->accounts->where('pivot.ref',$acc->id))->count() === 1) {
$o = $x->pop();
// If not, see if our reference matches
} elseif (($x=$ref->filter(function($item) use ($acc) { return $item->sid == $acc->ref; }))->count() === 1) {
$o = $x->pop();
// Look based on Name
} elseif (($x=$ref->filter(function($item) use ($acc) { return $item->company == $acc->companyname || $item->name == $acc->fullname || $item->user->email == $acc->email; }))->count() === 1) {
$o = $x->pop();
} else {
// Log not found
Log::alert(sprintf('%s:Customer not found [%s:%s]',self::LOGKEY,$acc->id,$acc->DisplayName));
continue;
}
$o->providers()->syncWithoutDetaching([
$this->to->provider->id => [
'ref' => $acc->id,
'synctoken' => $acc->synctoken,
'created_at'=>Carbon::create($acc->created_at),
'updated_at'=>Carbon::create($acc->updated_at),
'site_id'=>$this->to->site_id,
],
]);
Log::alert(sprintf('%s:Customer updated [%s:%s]',self::LOGKEY,$o->id,$acc->id));
// Check if QB is out of Sync and update it.
$acc->syncOriginal();
$acc->PrimaryEmailAddr = (object)['Address'=>$o->user->email];
$acc->ResaleNum = $o->sid;
$acc->GivenName = $o->user->firstname;
$acc->FamilyName = $o->user->lastname;
$acc->CompanyName = $o->name;
$acc->DisplayName = $o->name;
$acc->FullyQualifiedName = $o->name;
//$acc->Active = (bool)$o->active; // @todo implement in-activity, but only if all invoices are paid and services cancelled
if ($acc->getDirty()) {
Log::info(sprintf('%s:Customer [%s] (%s:%s) has changed',self::LOGKEY,$o->sid,$acc->id,$acc->DisplayName),['dirty'=>$acc->getDirty()]);
$acc->sparse = 'true';
AccountingCustomerUpdate::dispatch($this->to,$acc);
}
// @todo Identify accounts in our DB that are not in accounting
}
}
}

View File

@ -1,79 +0,0 @@
<?php
namespace App\Jobs;
use Carbon\Carbon;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Log;
use App\Models\{Invoice,ProviderToken};
/**
* Synchronise invoices ref numbers with our database
*/
class AccountingInvoiceSync implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
private const LOGKEY = 'JIS';
private ProviderToken $to;
/**
* Create a new job instance.
*
* @param ProviderToken $to
*/
public function __construct(ProviderToken $to)
{
$this->to = $to;
}
/**
* Execute the job.
*
* @return void
* @throws \Exception
*/
public function handle()
{
$api = $this->to->API();
$ref = Invoice::select('id')->get();
foreach ($api->getInvoices() as $acc) {
$o = NULL;
// See if we are already linked
if (($x=$this->to->provider->accounts->where('pivot.ref',$acc->id))->count() === 1) {
$o = $x->pop();
// If not, see if our reference matches
} elseif (($x=$ref->filter(fn($item)=>$item->lid == $acc->DocNumber))->count() === 1) {
$o = $x->pop();
} else {
// Log not found
Log::alert(sprintf('%s:Invoice not found [%s:%s]',self::LOGKEY,$acc->id,$acc->DocNumber));
continue;
}
$o->providers()->syncWithoutDetaching([
$this->to->provider->id => [
'ref' => $acc->id,
'synctoken' => $acc->synctoken,
'created_at' => Carbon::create($acc->created_at),
'updated_at' => Carbon::create($acc->updated_at),
'site_id' => $this->to->site_id,
],
]);
Log::alert(sprintf('%s:Invoice updated [%s:%s]',self::LOGKEY,$o->id,$acc->DocNumber));
}
}
}

View File

@ -1,142 +0,0 @@
<?php
namespace App\Jobs;
use Carbon\Carbon;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\Log;
use App\Models\{Account,Invoice,Payment,PaymentItem,ProviderToken,Site};
use Intuit\Models\Payment as PaymentModel;
/**
* Synchronise payments with our payments.
*
* This will:
* + Create/Update the payment in our system
* + Associate the payment to the same invoice in our system
*/
class AccountingPaymentSync implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
private const LOGKEY = 'JPS';
private PaymentModel $pmi;
private ProviderToken $to;
/**
* Create a new job instance.
*
* @return void
*/
public function __construct(ProviderToken $to,PaymentModel $pmi)
{
$this->pmi = $pmi;
$this->to = $to;
}
/**
* Execute the job.
*
* @return void
* @throws \Exception
*/
public function handle()
{
// @todo Can this be automatically determined?
$site = Site::findOrFail($this->to->site_id);
Config::set('site',$site);
// See if we are already linked
if (($x=$this->to->provider->payments->where('pivot.ref',$this->pmi->id))->count() === 1) {
$o = $x->pop();
} else {
// Find the account
$ao = Account::select('accounts.*')
->join('account__provider',['account__provider.account_id'=>'accounts.id'])
->where('provider_oauth_id',$this->to->provider_oauth_id)
->where('ref',$this->pmi->account_ref)
->single();
if (! $ao) {
Log::alert(sprintf('%s:Account not found for payment [%s:%d]',self::LOGKEY,$this->pmi->id,$this->pmi->account_ref));
return;
}
// Create the payment
$o = new Payment;
$o->account_id = $ao->id;
$o->site_id = $ao->site_id; // @todo Automatically determine
}
// Update the payment details
$o->paid_at = $this->pmi->date_paid;
$o->active = TRUE;
$o->checkout_id = 2; // @todo
$o->total_amt = $this->pmi->total_amt;
$o->notes = 'Imported from Intuit';
$o->save();
Log::info(sprintf('%s:Recording payment [%s:%3.2f]',self::LOGKEY,$this->pmi->id,$this->pmi->total_amt));
$o->providers()->syncWithoutDetaching([
$this->to->provider->id => [
'ref' => $this->pmi->id,
'synctoken' => $this->pmi->synctoken,
'created_at'=>Carbon::create($this->pmi->created_at),
'updated_at'=>Carbon::create($this->pmi->updated_at),
'site_id'=>$this->to->site_id,
],
]);
// Load the invoice that this payment pays
$invoices = collect();
foreach ($this->pmi->lines() as $item => $amount) {
$invoice = Invoice::select('invoices.*')
->join('invoice__provider',['invoice__provider.invoice_id'=>'invoices.id'])
->where('provider_oauth_id',$this->to->provider_oauth_id)
->where('ref',$item)
->single();
$invoices->put($item,$invoice);
}
// Delete existing paid invoices that are no longer paid
foreach ($o->items as $pio)
if ($invoices->pluck('id')->search($pio->invoice_id) === FALSE)
$pio->delete();
// Update payment items
foreach ($this->pmi->lines() as $item => $amount) {
if (! $invoices->has($item)) {
Log::alert(sprintf('%s:Invoice [%s] not recorded, payment not assigned',self::LOGKEY,$item));
continue;
}
$io = $invoices->get($item);
// If the payment item already exists
if (($x=$o->items->where('invoice_id',$io->id))->count()) {
$pio = $x->pop();
} else {
$pio = new PaymentItem;
$pio->invoice_id = $io->id;
$pio->site_id = $io->site_id;
}
$pio->amount = $amount;
$o->items()->save($pio);
}
Log::alert(sprintf('%s:Payment updated [%s:%s]',self::LOGKEY,$o->id,$this->pmi->id));
}
}

View File

@ -1,86 +0,0 @@
<?php
namespace App\Jobs;
use Carbon\Carbon;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Log;
use App\Models\{Tax,ProviderToken};
/**
* Synchronise TAX ids with our taxes.
*
* This will only update our records, it wont create new records in the account system, nor in our DB
*/
class AccountingTaxSync implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
private const LOGKEY = 'JTS';
private ProviderToken $to;
/**
* Create a new job instance.
*
* @param ProviderToken $to
*/
public function __construct(ProviderToken $to)
{
$this->to = $to;
}
/**
* Execute the job.
*
* @return void
* @throws \Exception
*/
public function handle()
{
$api = $this->to->API();
$ref = Tax::select(['id','description'])->get();
foreach ($api->getTaxCodes() as $acc) {
$o = NULL;
// See if we are already linked
if (($x=$this->to->provider->taxes->where('pivot.ref',$acc->id))->count() === 1) {
$o = $x->pop();
/*
// If not, see if our reference matches
} elseif (($x=$ref->filter(function($item) use ($acc) { return $item->sid == $acc->ref; }))->count() === 1) {
$o = $x->pop();
*/
// Look based on Name
} elseif (($x=$ref->filter(fn($item)=>$item->description === $acc->name))->count() === 1) {
$o = $x->pop();
} else {
// Log not found
Log::alert(sprintf('%s:Tax not found [%s:%s]',self::LOGKEY,$acc->id,$acc->name));
continue;
}
$o->providers()->syncWithoutDetaching([
$this->to->provider->id => [
'ref' => $acc->id,
'synctoken' => $acc->synctoken,
'created_at' => Carbon::create($acc->created_at),
'updated_at' => Carbon::create($acc->updated_at),
'site_id' => $this->to->site_id,
],
]);
Log::alert(sprintf('%s:Tax updated [%s:%s]',self::LOGKEY,$o->id,$acc->id));
}
}
}

View File

@ -8,81 +8,70 @@ use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Mail;
use App\Classes\External\Supplier as ExternalSupplier;
use App\Mail\TrafficMismatch;
use App\Models\{Rtm,Supplier};
use App\Models\Service\Broadband as ServiceBroadband;
use App\Models\Usage\Broadband as UsageBroadband;
use App\Models\Service\Adsl;
use App\Models\Service\AdslTraffic;
use App\Models\AdslSupplier;
use Illuminate\Support\Facades\Mail;
/**
* Class BroadbandTraffic
* Read and update the traffic for a Broadband supplier
* Read and update the traffic for an Broadband supplier
*
* @package App\Jobs
*/
final class BroadbandTraffic implements ShouldQueue
class BroadbandTraffic implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
private const LOGKEY = 'JBT';
private const class_prefix = 'App\Classes\External\Supplier\\';
protected $aso = NULL; // The supplier we are updating from
private $class_prefix = 'App\Classes\External\Supplier\\';
private const traffic = 'broadband';
public function __construct(private string $supplier) {}
public function __construct(AdslSupplier $o)
{
$this->aso = $o;
}
/**
* Execute the job.
*
* @return void
* @throws \Exception
* @todo The column stats_lastupdate is actually the "next" date that stats should be retrieved. Rename it.
*/
public function handle()
{
$so = Supplier::active()
->where('name','ilike',$this->supplier)
->sole();
Log::info(sprintf('%s:Importing Broadband Traffic from [%s]',self::LOGKEY,$this->aso->name),['m'=>__METHOD__]);
// Wholesaler email
$ro = Rtm::where('parent_id',NULL)->sole();
Log::info(sprintf('%s:Importing Broadband Traffic from [%s]',self::LOGKEY,$so->name));
if ((! $connection=$so->detail->connections->get('broadband')) || (count(array_intersect(array_keys($connection),ExternalSupplier::traffic_connection_keys)) !== 3))
throw new \Exception('No or missing connection details for:'.self::traffic);
// Count of updated records
$u = 0;
// Load our class for this supplier
$class = self::class_prefix.$so->name;
$class = $this->class_prefix.$this->aso->name;
if (class_exists($class)) {
$o = new $class($so);
$o = new $class($this->aso);
} else {
Log::error(sprintf('%s: Class doesnt exist: %s',self::LOGKEY,$class));
Log::error(sprintf('%s: Class doesnt exist: %d',get_class($this),$class));
exit(1);
}
$last_update = Carbon::create(Arr::get($connection,'last'))->addDay();
Arr::set($connection,'last',$last_update->format('Y-m-d'));
// Repeat pull traffic data until yesterday
while ($last_update < Carbon::now()->subDay()) {
Log::notice(sprintf('%s:Next update is [%s]',self::LOGKEY,$last_update->format('Y-m-d')));
while ($this->aso->stats_lastupdate < Carbon::now()->subDay()) {
Log::notice(sprintf('%s:Next update is [%s]',self::LOGKEY,$this->aso->stats_lastupdate->format('Y-m-d')),['m'=>__METHOD__]);
// Delete traffic, since we'll refresh it.
UsageBroadband::where('supplier_id',$so->id)
->where('date',$last_update->format('Y-m-d'))
AdslTraffic::where('supplier_id',$this->aso->id)
->where('date',$this->aso->stats_lastupdate)
->delete();
$c = 0;
foreach ($o->fetch($connection,self::traffic) as $line) {
foreach ($o->fetch() as $line) {
// The first row is our header
if (! $c++) {
$fields = $o->getColumns(preg_replace('/,\s+/',',',$line),collect($o->header()));
@ -90,27 +79,29 @@ final class BroadbandTraffic implements ShouldQueue
}
if (! $fields->count())
abort(500,'? No fields in data export');
abort(500,'? No fields in data exportupda');
$row = str_getcsv(trim($line));
try {
// @todo Put the date format in the DB.
$date = Carbon::createFromFormat('Y-m-d',$row[$o->getColumnKey('Date')]);
// Find the right service dependent on the dates we supplied the service
$oo = ServiceBroadband::where('service_username',$row[$o->getColumnKey('Login')])
->select('service_broadband.*')
->join('services',['service_broadband.service_id'=>'services.id'])
->where('services.start_at','<=',$date)
->where(fn($query)=>
$query->whereNULL('services.stop_at')
->orWhere('services.stop_at','>=',$date)
)
->single();
// Find the right service dependant on the dates we supplied the service
$oo = Adsl::where('service_username',$row[$o->getColumnKey('Login')])
->select(DB::raw('ab_service__adsl.*'))
->join('ab_service','ab_service.id','=','service_id')
->where('ab_service.date_start','<=',$date->format('U'))
->where(function($query) use ($date) {
$query->whereNULL('ab_service.date_end')
->orWhere('ab_service.date_end','<=',$date->format('U'));
})
->get();
$to = new UsageBroadband;
$to->date = $last_update;
$to->supplier_id = $so->id;
$to = new AdslTraffic;
$to->site_id = 1; // @todo TO ADDRESS
$to->date = $this->aso->stats_lastupdate;
$to->supplier_id = $this->aso->id;
$to->up_peak = $row[$o->getColumnKey('Peak upload')];
$to->up_offpeak = $row[$o->getColumnKey('Off peak upload')];
$to->down_peak = $row[$o->getColumnKey('Peak download')];
@ -120,39 +111,33 @@ final class BroadbandTraffic implements ShouldQueue
$to->time = '24:00'; // @todo
// If we have no records
if (! $oo) {
Log::error(sprintf('%s:None or too many services return for [%s]',self::LOGKEY,$row[$o->getColumnKey('Login')]),['date'=>$date]);
if ($oo->count() != 1) {
Log::error(sprintf('%s:Too many services return for [%s]',self::LOGKEY,$row[$o->getColumnKey('Login')]),['m'=>__METHOD__,'date'=>$date,'count'=>$oo->count()]);
$to->service = $row[$o->getColumnKey('Login')];
$to->site_id = 1; // @todo This needs to be worked out a better way
$to->save();
} else {
$to->site_id = $oo->site_id;
$to->service_item_id = $oo->id;
$oo->first()->traffic()->save($to);
}
if ($to->save())
$u++;
$u++;
} catch (\Exception $e) {
Log::error(sprintf('%s:Exception occurred when storing traffic record for [%s].',self::LOGKEY,$row[$o->getColumnKey('Login')]),['row'=>$row,'line'=>$line]);
throw new \Exception('Error while storing traffic data: '.$e->getMessage());
Log::error(sprintf('%s:Exception occurred when storing traffic record for [%s].',self::LOGKEY,$row[$o->getColumnKey('Login')]),['m'=>__METHOD__,'row'=>$row,'line'=>$line]);
throw new \Exception('Error while storing traffic date');
}
}
Log::info(sprintf('%s: Records Imported [%d] for [%s]',self::LOGKEY,$u,$last_update->format('Y-m-d')));
// Save our current progress.
$so->detail->connections = $so->detail->connections->put(self::traffic,array_merge($connection,['last'=>$last_update->format('Y-m-d')]));
$so->detail->save();
// Update our details for the next iteration.
$last_update = $last_update->addDay();
Arr::set($connection,'last',$last_update->format('Y-m-d'));
Log::info(sprintf('%s: Records Imported [%d] for [%s]',self::LOGKEY,$u,$this->aso->stats_lastupdate->format('Y-m-d')),['m'=>__METHOD__]);
if ($u) {
if ($so->trafficMismatch($date)->count())
Mail::to($ro->owner->email)
->send(new TrafficMismatch($so,$date));
$this->aso->stats_lastupdate = $this->aso->stats_lastupdate->addDay();
$this->aso->save();
if ($this->aso->trafficMismatch($date)->count())
Mail::to('deon@graytech.net.au') // @todo To change
->send(new TrafficMismatch($this->aso,$date));
}
}
}

View File

@ -1,232 +0,0 @@
<?php
namespace App\Jobs;
use Carbon\Carbon;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\Log;
use App\Models\{Cost,Service,Site,Supplier};
use App\Traits\Import;
class ImportCosts implements ShouldQueue
{
use Dispatchable,InteractsWithQueue,Queueable,SerializesModels,Import;
private const LOGKEY = 'JIC';
private Cost $co;
private Site $site;
private string $file;
protected Collection $columns;
/**
* Create a new job instance.
*
* @return void
*/
public function __construct(Site $site,Supplier $so,Carbon $invoice_date,string $file)
{
$this->file = $file;
$this->site = $site;
$this->co = Cost::where('site_id',$site->site_id)
->where('supplier_id',$so->id)
->where('billed_at',$invoice_date)
->firstOrNew();
$this->co->active = TRUE;
$this->co->site_id = $site->site_id;
$this->co->billed_at = $invoice_date;
$this->co->supplier_id = $so->id;
$this->co->save();
Cost\Broadband::where('cost_id',$this->co->id)->where('site_id',$site->site_id)->delete();
Cost\Phone::where('cost_id',$this->co->id)->where('site_id',$site->site_id)->delete();
Cost\Generic::where('cost_id',$this->co->id)->where('site_id',$site->site_id)->delete();
// @todo to be stored in supplier config
$headers = [
'INVOICEID'=>'Item ID',
'REF'=>'Reference No',
'IDTAG'=>'ID Tag',
'CATEGORY'=>'Category',
'DESC'=>'Item Description',
'QUANTITY'=>'Quantity',
'PRICEUNIT'=>'Unit Price (inc-GST)',
'PRICETOTAL'=>'Total (inc-GST)'
];
$this->columns = collect($headers)->map(fn($item)=>strtolower($item));
}
/**
* Execute the job.
*
* @return void
* @throws \Exception
*/
public function handle()
{
Config::set('site',$this->site);
$skip = 7; // @todo to be stored in supplier config
$file = fopen('storage/app/'.$this->file,'r');
$haveHeader = FALSE;
$c = 0;
while (! feof($file)) {
$line = stream_get_line($file,0,"\r\n");
if (str_starts_with($line,'#'))
continue;
// Remove any embedded CR and BOM
$line = str_replace("\r",'',$line);
$line = preg_replace('/^\x{feff}/u','',$line);
if (($c++ < $skip) || (! $line))
continue;
// The first line is a header.
if (! $haveHeader) {
Log::debug(sprintf('%s: Input File: %s',get_class($this),$this->file));
Log::debug(sprintf('%s: Processing columns: %s',get_class($this),join('|',$this->setColumns($line)->toArray())));
$haveHeader = TRUE;
continue;
}
// If the line has a , between two (), then convert the comma to a space.
$x = [];
if (preg_match('#\(.+,.+\)#i',$line,$x)) {
$replace = str_replace(',','_',$x[0]);
$line = str_replace($x[0],$replace,$line);
//dd($line,$x);
}
$fields = str_getcsv(trim($line));
if (is_null($x=$this->getColumnKey('DESC')) OR empty($fields[$x]))
continue;
// The first part of our item description is the service number.
// This should go to a "supplier" function, since all suppliers may show different values in description.
$m = [];
$desc = $fields[$x];
// m[1] = Service, m[2] = Desc, m[3] = From Date, m[4] = To Date
preg_match('#^([0-9]{10})\s+-\s+(.*)\(([0-9]+\s+[JFMASOND].*\s+[0-9]+)+\s+-\s+([0-9]+\s+[JFMASOND].*\s+[0-9]+)+\)$#',$fields[$x],$m);
if (count($m) !== 5) {
dump(sprintf('ERROR: Description didnt parse [%s] on line [%d]',$fields[$x],$c));
continue;
}
$cost = ($x=$this->getColumnKey('PRICETOTAL')) ? str_replace([',','$'],'',$fields[$x]) : NULL;
$start_at = Carbon::createFromFormat('d M Y',$m[3]);
$stop_at = Carbon::createFromFormat('d M Y',$m[4]);
$so = Service::search($m[1])->ServiceActive()->with(['type','product.type.supplied'])->single();
if ($so) {
// r[1] = Monthly Charge or Extra Charge,r[2] = "On Plan", r[3] = Plan Info
$r = [];
switch ($so->product->category) {
case 'broadband':
$to = Cost\Broadband::where('site_id',$this->co->site_id)
->where('cost_id',$this->co->id)
->where('service_broadband_id',$so->type->id)
->where('start_at',$start_at)
->where('end_at',$stop_at)
->firstOrNew();
$to->service_broadband_id = $so->type->id;
preg_match('#^(Monthly Internet Charge|Plan Change Fee|Change billing date refund for Monthly Internet Charge On Plan|First 12 Month VISP broadband plan discount|.*)\s?(On Plan)?\s?(.*)#',$m[2],$r);
switch ($r[1]) {
case 'Monthly Internet Charge':
case 'First 12 Month VISP broadband plan discount':
case 'Change billing date refund for Monthly Internet Charge On Plan':
$to->base =+ $cost;
break;
case 'Plan Change Fee':
$to->excess =+ $cost;
break;
default:
dump(['extra charge'=>$r]);
$to->excess =+ $cost;
}
break;
case 'phone':
$to = Cost\Phone::where('site_id',$this->co->site_id)
->where('cost_id',$this->co->id)
->where('service_phone_id',$so->type->id)
->where('start_at',$start_at)
->where('end_at',$stop_at)
->firstOrNew();
$to->service_phone_id = $so->type->id;
preg_match('#^(Residential VOIP Plan Excess Usage|Virtual FAX Number Monthly Rental|Corporate VOIP Plan Monthly Rental|Residential VOIP Plan Monthly Rental|.*)\s?(.*)#',$m[2],$r);
switch ($r[1]) {
case 'Residential VOIP Plan Monthly Rental':
case 'Virtual FAX Number Monthly Rental':
case 'Corporate VOIP Plan Monthly Rental':
$to->base =+ $cost;
break;
case 'Residential VOIP Plan Excess Usage':
$to->excess =+ $cost;
$to->notes = $r[2];
break;
default:
dump(['extra charge'=>$r]);
$to->excess =+ $cost;
}
break;
default:
dump(['so'=>$so,'category'=>$so->product->category,'line'=>$line,'m'=>$m,'r'=>$r]);
throw new \Exception(sprintf('ERROR: Service type not handled for service [%s] (%s) on line [%d]',$m[1],$so->product->category,$c));
}
} else {
dump(['line'=>$line,'sql'=>Service::search($m[1])->ServiceActive()->with(['type','product.type.supplied'])->toSql()]);
$to = Cost\Generic::where('site_id',$this->co->site_id)
->where('cost_id',$this->co->id)
->where('notes',sprintf('%s:%s',$m[1],$m[2]))
->where('start_at',$start_at)
->where('end_at',$stop_at)
->firstOrNew();
$to->excess =+ $cost;
$to->notes = $line;
}
$to->site_id = $this->co->site_id;
$to->cost_id = $this->co->id;
$to->active = TRUE;
$to->start_at = $start_at;
$to->end_at = $stop_at;
// Work out supplier product number
Log::warning(sprintf('%s:Supplier product ID not matched',self::LOGKEY),['r'=>$r]);
//dd($m[2],$cost,$so->product->type->supplied);
// Work out if this base charge, or extra charge
//dd(['M'=>__METHOD__,'fields'=>$fields,'DESC'=>$this->getColumnKey('DESC'),'desc'=>$desc,'m'=>$m,'sql'=>$so->toSql(),'bindings'=>$so->getBindings(),'so'=>$so]);
$to->save();
}
fclose($file);
}
}

View File

@ -1,104 +0,0 @@
<?php
namespace App\Jobs;
use Carbon\Carbon;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Log;
use App\Classes\External\Payments;
use App\Models\{Account,Checkout,Payment};
/**
* Import payments from payment providers
*
* @package App\Jobs
*/
final class PaymentsImport implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
private const LOGKEY = 'JPI';
protected Payments $o; // The payment provider we are updating from
private $class_prefix = 'App\Classes\External\Payments\\';
public function __construct(Payments $o)
{
$this->o = $o;
}
public function handle()
{
Log::info(sprintf('%s:Importing Payment Date from [%s]',self::LOGKEY,get_class($this->o)));
// Get our checkout IDs for this plugin
$cos = Checkout::where('plugin',config('services.ezypay.plugin'))->pluck('id');
foreach ($this->o->getCustomers() as $c) {
if ($c->BillingStatus == 'Inactive') {
Log::debug(sprintf('%s:Ignoring INACTIVE: [%s] %s %s',self::LOGKEY,$c->EzypayReferenceNumber,$c->Firstname,$c->Surname));
continue;
}
// Load Account Details from ReferenceId
$ao = Account::where('site_id',(int)substr($c->ReferenceId,0,2))
->where('id',(int)substr($c->ReferenceId,2,4))
->first();
if (! $ao) {
Log::error(sprintf('%s:Missing: [%s] %s %s (%s)',self::LOGKEY,$c->EzypayReferenceNumber,$c->Firstname,$c->Surname,$c->ReferenceId));
continue;
}
// Find the last payment logged
$last = Carbon::create(Payment::whereIN('checkout_id',$cos)->where('account_id',$ao->id)->max('paid_at'));
$o = $this->o->getDebits([
'customerId'=>$c->Id,
'dateFrom'=>$last->format('Y-m-d'),
'dateTo'=>$last->addQuarter()->format('Y-m-d'),
'pageSize'=>100,
]);
Log::info(sprintf('%s:Loaded [%d] payments for account: [%s]',self::LOGKEY,$o->count(),$ao->id));
// Load the payments
if ($o->count()) {
foreach ($o->reverse() as $p) {
$pd = Carbon::createFromTimeString($p->Date);
// If not success, ignore it.
if ($p->Status != 'Success') {
Log::alert(sprintf('%s:Payment not successful: [%s] %s %s (%s) [%s]',self::LOGKEY,$pd->format('Y-m-d'),$ao->id,$p->Id,$p->Amount,$p->Status));
continue;
}
$lp = $ao->payments->last();
if ($lp AND (($pd == $lp->paid_at) OR ($p->Id == $lp->checkout_data))) {
Log::alert(sprintf('%s:Payment Already Recorded: [%s] %s %s (%s)',self::LOGKEY,$pd->format('Y-m-d'),$ao->id,$p->Id,$p->Amount));
continue;
}
// New Payment
$po = new Payment;
$po->active = TRUE;
$po->site_id = 1; // @todo
$po->paid_at = $pd;
$po->checkout_id = '999'; // @todo
$po->checkout_data = $p->Id;
$po->total_amt = $p->Amount;
$ao->payments()->save($po);
Log::info(sprintf('%s:Recorded: Payment for [%s] %s %s (%s) on %s',self::LOGKEY,$c->EzypayReferenceNumber,$c->Firstname,$c->Surname,$po->id,$pd));
}
}
}
}
}

View File

@ -1,39 +0,0 @@
<?php
namespace App\Jobs;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use App\Models\ProviderToken;
class ProviderTokenRefresh implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
private ProviderToken $to;
/**
* Create a new job instance.
*
* @return void
*/
public function __construct(ProviderToken $to)
{
$this->to = $to;
}
/**
* Execute the job.
*
* @return void
*/
public function handle()
{
$this->to->refreshToken();
}
}

View File

@ -1,81 +0,0 @@
<?php
namespace App\Jobs;
use Carbon\Carbon;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\Log;
use App\Models\{Site,Supplier,TLD};
class SupplierDomainSync implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
private const LOGKEY = 'JSD';
protected Site $site;
protected Supplier $supplier;
protected bool $forceprod;
/**
* Create a new job instance.
*
* @return void
*/
public function __construct(Site $site,Supplier $supplier,bool $forceprod=FALSE)
{
$this->site = $site;
$this->supplier = $supplier;
$this->forceprod = $forceprod;
Config::set('site',$site);
}
/**
* Execute the job.
*
* @return void
*/
public function handle()
{
$registrar_id = ($x=$this->supplier->registrar()) ? $x->id : NULL;
foreach ($this->supplier->API($this->forceprod)->getDomains(['fetchall'=>true]) as $domain) {
// @todo See if we can find this domain by its ID
// Find this domain by it's name
if (! $to=TLD::domaintld($domain->domain_name)) {
Log::alert(sprintf('%s:Domain [%s] from (%s) is not in a TLD that we manage',self::LOGKEY,$this->supplier->name,$domain->domain_name));
} elseif (($domainpart=strtolower($to->domain_part($domain->domain_name))) && (($x=$to->domains->where('domain_name',$domainpart))->count() === 1)) {
$o = $x->pop();
$o->registrar_auth_password = $domain->auth_key;
$o->expire_at = Carbon::create($domain->expiry_date);
$o->registrar_account = $domain->account;
$o->registrar_username = '';
$o->registrar_ns = Supplier\Domain::nameserver_name($domain->nameservers());
if ($registrar_id)
$o->domain_registrar_id = $registrar_id;
if ($o->getDirty()) {
Log::info(sprintf('%s:Updating Domain [%s] from (%s)',self::LOGKEY,$domain->domain_name,$this->supplier->name));
$o->save();
} else {
Log::info(sprintf('%s:No Change to Domain [%s] from (%s)',self::LOGKEY,$domain->domain_name,$this->supplier->name));
}
// Alert an unmanaged name.
} else {
Log::alert(sprintf('%s:Domain [%s] from (%s) is not one managed in OSB',self::LOGKEY,$this->supplier->name,$domain->domain_name));
}
}
}
}

View File

@ -1,31 +0,0 @@
<?php
namespace App\Listeners;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Mail\Events\MessageSent;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Support\Facades\Log;
class LogSentMessage
{
private const LOGKEY = 'LSM';
/**
* Handle the event.
*
* @param MessageSent $event
* @return void
*/
public function handle(MessageSent $event)
{
Log::debug(
sprintf('%s:Email to [%s] with subject [%s] sent [%s]',
self::LOGKEY,
collect($event->data['message']->getTo())->transform(function($item) { return $item->getAddress(); })->join(','),
$event->data['message']->getSubject(),
$event->sent->getMessageId(),
),
['debug'=>$event->sent->getDebug()]);
}
}

View File

@ -1,40 +0,0 @@
<?php
namespace App\Listeners;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\Log;
use App\Events\ProviderPaymentCreated as Event;
use App\Jobs\AccountingPaymentSync as Job;
use App\Models\{ProviderOauth,Site,User};
class ProviderPaymentCreated
{
private const LOGKEY = 'LPC';
/**
* Handle the event.
*
* @param Event $event
* @return void
*/
public function handle(Event $event)
{
$site = Site::findOrFail(1); // @todo This shouldnt be hard coded
Config::set('site',$site);
$uo = User::findOrFail(1); // @todo This shouldnt be hard coded
$so = ProviderOauth::where('name',$event->provider)->singleOrFail();
if (! ($to=$so->token($uo)))
abort(500,sprintf('Unknown Tokens for [%s]',$uo->email));
$api = $to->API();
$acc = $api->getPayment($event->paymentData['id']);
Job::dispatch($to,$acc);
}
}

Some files were not shown because too many files have changed in this diff Show More