2021-02-08 06:11:25 +01:00
< ? php
/**
* Invoice Ninja ( https :// invoiceninja . com ) .
*
* @ link https :// github . com / invoiceninja / invoiceninja source repository
*
2022-04-27 05:20:41 +02:00
* @ copyright Copyright ( c ) 2022. Invoice Ninja LLC ( https :// invoiceninja . com )
2021-02-08 06:11:25 +01:00
*
2021-06-16 08:58:16 +02:00
* @ license https :// www . elastic . co / licensing / elastic - license
2021-02-08 06:11:25 +01:00
*/
namespace App\Jobs\Ninja ;
2021-02-08 11:11:17 +01:00
use App\Models\ClientContact ;
2021-02-08 06:11:25 +01:00
use App\Models\Company ;
2021-02-08 11:11:17 +01:00
use App\Models\CompanyLedger ;
use App\Models\Credit ;
use App\Models\Invoice ;
use App\Models\Payment ;
2021-02-08 06:11:25 +01:00
use Illuminate\Bus\Queueable ;
use Illuminate\Contracts\Queue\ShouldQueue ;
use Illuminate\Foundation\Bus\Dispatchable ;
use Illuminate\Queue\InteractsWithQueue ;
2021-02-08 11:11:17 +01:00
use Illuminate\Queue\Middleware\RateLimited ;
2021-02-08 06:11:25 +01:00
use Illuminate\Queue\SerializesModels ;
2021-02-08 11:11:17 +01:00
use Illuminate\Support\Facades\DB ;
2021-02-08 06:11:25 +01:00
use Turbo124\Beacon\Jobs\Database\MySQL\DbStatus ;
2021-02-10 07:08:16 +01:00
use Illuminate\Support\Facades\Cache ;
2021-02-08 06:11:25 +01:00
class CheckCompanyData implements ShouldQueue
{
use Dispatchable , InteractsWithQueue , Queueable , SerializesModels ;
public $company ;
public $hash ;
2021-02-08 11:11:17 +01:00
public $company_data = [];
2021-02-10 07:08:16 +01:00
public $is_valid ;
2021-02-08 06:11:25 +01:00
/**
* Create a new job instance .
*
* @ return void
*/
2021-02-10 04:18:23 +01:00
public function __construct ( Company $company , string $hash = '' )
2021-02-08 06:11:25 +01:00
{
$this -> company = $company ;
$this -> hash = $hash ;
}
/**
* Execute the job .
*
* @ return void
*/
public function handle ()
{
2021-02-10 07:08:16 +01:00
$this -> is_valid = true ;
2021-02-08 11:11:17 +01:00
$this -> checkInvoiceBalances ();
$this -> checkInvoicePayments ();
$this -> checkPaidToDates ();
$this -> checkClientBalances ();
$this -> checkContacts ();
$this -> checkCompanyData ();
if ( Cache :: has ( $this -> hash ))
$cache_instance = Cache :: get ( $this -> hash );
else
$cache_instance = Cache :: add ( $this -> hash , '' );
2021-02-10 07:08:16 +01:00
$this -> company_data [ 'company_hash' ] = $this -> company -> company_hash ;
2021-02-08 11:11:17 +01:00
Cache :: put ( $this -> hash , $cache_instance , now () -> addMinutes ( 30 ));
nlog ( Cache :: get ( $this -> hash ));
2021-02-10 02:59:30 +01:00
nlog ( $this -> company_data );
2021-02-10 07:08:16 +01:00
if ( ! $this -> is_valid )
2021-02-10 02:59:30 +01:00
$this -> company_data [ 'status' ] = 'errors' ;
else
$this -> company_data [ 'status' ] = 'success' ;
2021-02-10 04:18:23 +01:00
2021-02-10 02:59:30 +01:00
return $this -> company_data ;
2021-02-08 11:11:17 +01:00
}
public function middleware ()
{
return [ new RateLimited ( 'checkdata' )];
}
private function checkInvoiceBalances ()
{
$wrong_balances = 0 ;
$wrong_paid_to_dates = 0 ;
2021-02-10 04:18:23 +01:00
foreach ( $this -> company -> clients -> where ( 'is_deleted' , 0 ) as $client ) {
2021-02-08 11:11:17 +01:00
$invoice_balance = $client -> invoices -> where ( 'is_deleted' , false ) -> where ( 'status_id' , '>' , 1 ) -> sum ( 'balance' );
2021-02-10 02:59:30 +01:00
//$credit_balance = $client->credits->where('is_deleted', false)->sum('balance');
2021-02-08 11:11:17 +01:00
2021-02-10 02:59:30 +01:00
// if($client->balance != $invoice_balance)
// $invoice_balance -= $credit_balance;//doesn't make sense to remove the credit amount
2021-02-08 11:11:17 +01:00
$ledger = CompanyLedger :: where ( 'client_id' , $client -> id ) -> orderBy ( 'id' , 'DESC' ) -> first ();
if ( $ledger && number_format ( $invoice_balance , 4 ) != number_format ( $client -> balance , 4 )) {
2021-02-10 02:59:30 +01:00
2021-02-08 11:11:17 +01:00
$wrong_balances ++ ;
2021-02-10 07:08:16 +01:00
$this -> company_data [] = " # { $client -> id } " . $client -> present () -> name . ' - ' . $client -> number . " - Balance Failure - Invoice Balances = { $invoice_balance } Client Balance = { $client -> balance } Ledger Balance = { $ledger -> balance } " ;
2021-02-08 11:11:17 +01:00
2021-02-10 07:08:16 +01:00
$this -> is_valid = false ;
2021-02-08 11:11:17 +01:00
}
}
2021-02-10 07:08:16 +01:00
$this -> company_data [] = " { $wrong_balances } clients with incorrect balances " ;
2021-02-08 11:11:17 +01:00
}
private function checkInvoicePayments ()
{
$wrong_balances = 0 ;
$wrong_paid_to_dates = 0 ;
2021-02-10 04:18:23 +01:00
$this -> company -> clients -> where ( 'is_deleted' , 0 ) -> each ( function ( $client ) use ( $wrong_balances ) {
2021-02-08 11:11:17 +01:00
$client -> invoices -> where ( 'is_deleted' , false ) -> whereIn ( 'status_id' , '!=' , Invoice :: STATUS_DRAFT ) -> each ( function ( $invoice ) use ( $wrong_balances , $client ) {
2021-06-05 07:58:37 +02:00
$total_amount = $invoice -> payments -> whereIn ( 'status_id' , [ Payment :: STATUS_COMPLETED , Payment :: STATUS_PENDING , Payment :: STATUS_PARTIALLY_REFUNDED ]) -> sum ( 'pivot.amount' );
$total_refund = $invoice -> payments -> whereIn ( 'status_id' , [ Payment :: STATUS_COMPLETED , Payment :: STATUS_PENDING , Payment :: STATUS_PARTIALLY_REFUNDED ]) -> sum ( 'pivot.refunded' );
2021-02-08 11:11:17 +01:00
$total_credit = $invoice -> credits -> sum ( 'amount' );
$total_paid = $total_amount - $total_refund ;
$calculated_paid_amount = $invoice -> amount - $invoice -> balance - $total_credit ;
if (( string ) $total_paid != ( string )( $invoice -> amount - $invoice -> balance - $total_credit )) {
$wrong_balances ++ ;
2021-02-10 07:08:16 +01:00
$this -> company_data [] = $client -> present () -> name . ' - ' . $client -> id . " - Total Amount = { $total_amount } != Calculated Total = { $calculated_paid_amount } - Total Refund = { $total_refund } Total credit = { $total_credit } " ;
2021-02-08 11:11:17 +01:00
2021-02-10 07:08:16 +01:00
$this -> is_valid = false ;
2021-02-08 11:11:17 +01:00
}
});
});
2021-02-10 07:08:16 +01:00
$this -> company_data [] = " { $wrong_balances } clients with incorrect invoice balances " ;
2021-02-08 11:11:17 +01:00
}
private function checkPaidToDates ()
{
$wrong_paid_to_dates = 0 ;
$credit_total_applied = 0 ;
2021-02-10 04:18:23 +01:00
$this -> company -> clients -> where ( 'is_deleted' , 0 ) -> each ( function ( $client ) use ( $wrong_paid_to_dates , $credit_total_applied ) {
2021-02-08 11:11:17 +01:00
$total_invoice_payments = 0 ;
foreach ( $client -> invoices -> where ( 'is_deleted' , false ) -> where ( 'status_id' , '>' , 1 ) as $invoice ) {
2021-02-10 02:59:30 +01:00
2021-02-08 11:11:17 +01:00
$total_amount = $invoice -> payments -> where ( 'is_deleted' , false ) -> whereIn ( 'status_id' , [ Payment :: STATUS_COMPLETED , Payment :: STATUS_PENDING , Payment :: STATUS_PARTIALLY_REFUNDED , Payment :: STATUS_REFUNDED ]) -> sum ( 'pivot.amount' );
$total_refund = $invoice -> payments -> where ( 'is_deleted' , false ) -> whereIn ( 'status_id' , [ Payment :: STATUS_COMPLETED , Payment :: STATUS_PENDING , Payment :: STATUS_PARTIALLY_REFUNDED , Payment :: STATUS_REFUNDED ]) -> sum ( 'pivot.refunded' );
$total_invoice_payments += ( $total_amount - $total_refund );
}
2021-02-10 02:59:30 +01:00
//10/02/21
// foreach ($client->payments as $payment) {
// $credit_total_applied += $payment->paymentables->where('paymentable_type', App\Models\Credit::class)->sum(DB::raw('amount'));
// }
2021-02-08 11:11:17 +01:00
2021-02-10 02:59:30 +01:00
// if ($credit_total_applied < 0) {
// $total_invoice_payments += $credit_total_applied;
// } //todo this is contentious
2021-02-08 11:11:17 +01:00
// nlog("total invoice payments = {$total_invoice_payments} with client paid to date of of {$client->paid_to_date}");
if ( round ( $total_invoice_payments , 2 ) != round ( $client -> paid_to_date , 2 )) {
$wrong_paid_to_dates ++ ;
2021-02-10 07:08:16 +01:00
$this -> company_data [] = $client -> present () -> name . 'id = # ' . $client -> id . " - Paid to date does not match Client Paid To Date = { $client -> paid_to_date } - Invoice Payments = { $total_invoice_payments } " ;
2021-02-08 11:11:17 +01:00
2021-02-10 07:08:16 +01:00
$this -> is_valid = false ;
2021-02-08 11:11:17 +01:00
}
});
$this -> company_data [] = " { $wrong_paid_to_dates } clients with incorrect paid to dates " ;
}
private function checkClientBalances ()
{
$wrong_balances = 0 ;
$wrong_paid_to_dates = 0 ;
2021-02-10 04:18:23 +01:00
foreach ( $this -> company -> clients -> where ( 'is_deleted' , 0 ) as $client ) {
2021-02-08 11:11:17 +01:00
//$invoice_balance = $client->invoices->where('is_deleted', false)->where('status_id', '>', 1)->sum('balance');
$invoice_balance = Invoice :: where ( 'client_id' , $client -> id ) -> where ( 'is_deleted' , false ) -> where ( 'status_id' , '>' , 1 ) -> withTrashed () -> sum ( 'balance' );
$credit_balance = Credit :: where ( 'client_id' , $client -> id ) -> where ( 'is_deleted' , false ) -> withTrashed () -> sum ( 'balance' );
2021-02-10 02:59:30 +01:00
//10/02/21
// Legacy - V4 will add credits to the balance - we may need to reverse engineer this and remove the credits from the client balance otherwise we need this hack here and in the invoice balance check.
// if($client->balance != $invoice_balance)
// $invoice_balance -= $credit_balance;
2021-02-08 11:11:17 +01:00
$ledger = CompanyLedger :: where ( 'client_id' , $client -> id ) -> orderBy ( 'id' , 'DESC' ) -> first ();
if ( $ledger && ( string ) $invoice_balance != ( string ) $client -> balance ) {
$wrong_paid_to_dates ++ ;
2021-02-10 07:08:16 +01:00
$this -> company_data [] = $client -> present () -> name . ' - ' . $client -> id . " - calculated client balances do not match { $invoice_balance } - " . rtrim ( $client -> balance , '0' ) . " " ;
2021-02-08 11:11:17 +01:00
2021-02-10 07:08:16 +01:00
$this -> is_valid = false ;
2021-02-08 11:11:17 +01:00
}
}
2021-02-10 07:08:16 +01:00
$this -> company_data [] = " { $wrong_paid_to_dates } clients with incorrect client balances " ;
2021-02-08 11:11:17 +01:00
}
private function checkContacts ()
{
// check for contacts with the contact_key value set
$contacts = DB :: table ( 'client_contacts' )
2021-02-10 04:18:23 +01:00
-> where ( 'company_id' , $this -> company -> id )
2021-02-08 11:11:17 +01:00
-> whereNull ( 'contact_key' )
-> orderBy ( 'id' )
-> get ([ 'id' ]);
2021-02-10 07:08:16 +01:00
$this -> company_data [] = $contacts -> count () . ' contacts without a contact_key' ;
2021-02-08 11:11:17 +01:00
if ( $contacts -> count () > 0 ) {
2021-02-10 07:08:16 +01:00
$this -> is_valid = false ;
2021-02-08 11:11:17 +01:00
}
2021-02-10 04:18:23 +01:00
// if ($this->option('fix') == 'true') {
// foreach ($contacts as $contact) {
// DB::table('client_contacts')
// ->where('company_id', $this->company->id)
// ->where('id', '=', $contact->id)
// ->whereNull('contact_key')
// ->update([
// 'contact_key' => str_random(config('ninja.key_length')),
// ]);
// }
// }
2021-02-08 11:11:17 +01:00
// check for missing contacts
$clients = DB :: table ( 'clients' )
2021-02-10 07:08:16 +01:00
-> where ( 'clients.company_id' , $this -> company -> id )
2021-02-08 11:11:17 +01:00
-> leftJoin ( 'client_contacts' , function ( $join ) {
$join -> on ( 'client_contacts.client_id' , '=' , 'clients.id' )
-> whereNull ( 'client_contacts.deleted_at' );
})
-> groupBy ( 'clients.id' , 'clients.user_id' , 'clients.company_id' )
-> havingRaw ( 'count(client_contacts.id) = 0' );
2021-02-10 04:18:23 +01:00
// if ($this->option('client_id')) {
// $clients->where('clients.id', '=', $this->option('client_id'));
// }
2021-02-08 11:11:17 +01:00
$clients = $clients -> get ([ 'clients.id' , 'clients.user_id' , 'clients.company_id' ]);
2021-02-10 07:08:16 +01:00
$this -> company_data [] = $clients -> count () . ' clients without any contacts' ;
2021-02-08 11:11:17 +01:00
if ( $clients -> count () > 0 ) {
2021-02-10 07:08:16 +01:00
$this -> is_valid = false ;
2021-02-08 11:11:17 +01:00
}
2021-02-10 04:18:23 +01:00
// if ($this->option('fix') == 'true') {
// foreach ($clients as $client) {
// $contact = new ClientContact();
// $contact->company_id = $client->company_id;
// $contact->user_id = $client->user_id;
// $contact->client_id = $client->id;
// $contact->is_primary = true;
// $contact->send_invoice = true;
// $contact->contact_key = str_random(config('ninja.key_length'));
// $contact->save();
// }
// }
2021-02-08 11:11:17 +01:00
// check for more than one primary contact
$clients = DB :: table ( 'clients' )
2021-02-10 04:18:23 +01:00
-> where ( 'clients.company_id' , $this -> company -> id )
2021-02-08 11:11:17 +01:00
-> leftJoin ( 'client_contacts' , function ( $join ) {
$join -> on ( 'client_contacts.client_id' , '=' , 'clients.id' )
-> where ( 'client_contacts.is_primary' , '=' , true )
-> whereNull ( 'client_contacts.deleted_at' );
})
-> groupBy ( 'clients.id' )
-> havingRaw ( 'count(client_contacts.id) != 1' );
2021-02-10 04:18:23 +01:00
// if ($this->option('client_id')) {
// $clients->where('clients.id', '=', $this->option('client_id'));
// }
2021-02-08 11:11:17 +01:00
$clients = $clients -> get ([ 'clients.id' , DB :: raw ( 'count(client_contacts.id)' )]);
2021-02-10 07:08:16 +01:00
$this -> company_data [] = $clients -> count () . ' clients without a single primary contact' ;
2021-02-08 11:11:17 +01:00
if ( $clients -> count () > 0 ) {
2021-02-10 07:08:16 +01:00
$this -> is_valid = false ;
2021-02-08 11:11:17 +01:00
}
}
private function checkCompanyData ()
{
$tables = [
'activities' => [
'invoice' ,
'client' ,
'client_contact' ,
'payment' ,
],
'invoices' => [
'client' ,
],
'payments' => [
'client' ,
],
'products' => [
],
];
foreach ( $tables as $table => $entityTypes ) {
foreach ( $entityTypes as $entityType ) {
$tableName = $this -> pluralizeEntityType ( $entityType );
$field = $entityType ;
if ( $table == 'companies' ) {
$company_id = 'id' ;
} else {
$company_id = 'company_id' ;
}
$records = DB :: table ( $table )
-> join ( $tableName , " { $tableName } .id " , '=' , " { $table } . { $field } _id " )
-> where ( " { $table } . { $company_id } " , '!=' , DB :: raw ( " { $tableName } .company_id " ))
-> get ([ " { $table } .id " ]);
if ( $records -> count ()) {
2021-02-10 07:08:16 +01:00
$this -> is_valid = false ;
$this -> company_data [] = $records -> count () . " { $table } records with incorrect { $entityType } company id " ;
2021-02-08 11:11:17 +01:00
}
}
}
2021-02-08 06:11:25 +01:00
}
2021-02-10 07:08:16 +01:00
public function pluralizeEntityType ( $type )
{
if ( $type === 'company' ) {
return 'companies' ;
}
return $type . 's' ;
}
2021-02-08 06:11:25 +01:00
}