2016-03-25 01:18:05 -07:00
< ? php
2021-06-10 20:15:52 +00:00
2016-03-25 01:18:05 -07:00
namespace App\Models ;
2022-08-29 11:26:47 -07:00
use App\Helpers\Helper ;
2025-08-28 18:23:26 +01:00
use App\Models\Traits\CompanyableTrait ;
2025-06-27 19:32:22 +01:00
use App\Models\Traits\HasUploads ;
2025-10-21 16:45:58 +01:00
use App\Models\Traits\Loggable ;
2018-07-16 23:13:07 +02:00
use App\Models\Traits\Searchable ;
2016-12-23 20:52:00 -05:00
use App\Presenters\Presentable ;
2017-01-10 18:19:18 -06:00
use Carbon\Carbon ;
2025-10-03 14:13:28 +01:00
use Illuminate\Database\Eloquent\Casts\Attribute ;
2021-06-10 20:19:27 +00:00
use Illuminate\Database\Eloquent\Factories\HasFactory ;
2016-03-25 01:18:05 -07:00
use Illuminate\Database\Eloquent\SoftDeletes ;
2025-08-28 18:23:26 +01:00
use Illuminate\Support\Facades\DB ;
2026-03-06 03:33:04 +00:00
use Illuminate\Support\Facades\Gate ;
2017-01-10 18:19:18 -06:00
use Illuminate\Support\Facades\Session ;
2016-09-06 21:39:42 -05:00
use Watson\Validating\ValidatingTrait ;
2016-03-25 01:18:05 -07:00
2025-09-11 10:23:53 -07:00
2016-03-25 01:18:05 -07:00
class License extends Depreciable
{
2021-06-10 20:17:44 +00:00
use HasFactory ;
2021-06-10 20:16:56 +00:00
protected $presenter = \App\Presenters\LicensePresenter :: class ;
2018-05-08 00:14:38 -07:00
2016-03-25 01:18:05 -07:00
use SoftDeletes ;
use CompanyableTrait ;
2025-06-27 19:32:22 +01:00
use HasUploads ;
2016-12-23 20:52:00 -05:00
use Loggable , Presentable ;
2016-03-25 01:18:05 -07:00
protected $injectUniqueIdentifier = true ;
use ValidatingTrait ;
2017-11-09 13:27:58 -08:00
// We set these as protected dates so that they will be easily accessible via Carbon
2021-06-10 20:17:18 +00:00
2016-03-25 01:18:05 -07:00
public $timestamps = true ;
protected $guarded = 'id' ;
protected $table = 'licenses' ;
2020-04-22 06:37:40 -07:00
2023-10-31 12:24:48 +00:00
2020-04-22 06:37:40 -07:00
protected $casts = [
2023-04-11 23:35:41 -07:00
'purchase_date' => 'date' ,
'expiration_date' => 'date' ,
'termination_date' => 'date' ,
2020-04-22 06:37:40 -07:00
'category_id' => 'integer' ,
'company_id' => 'integer' ,
];
2021-06-10 20:15:52 +00:00
protected $rules = [
2026-01-05 12:51:49 +00:00
'name' => 'required|string|max:255' ,
2025-06-24 13:53:11 +01:00
'seats' => 'required|min:1|integer|limit_change:10000' , // limit_change is a "pseudo-rule" that translates into 'between', see prepareLimitChangeRule() below
2017-09-27 22:11:20 -07:00
'license_email' => 'email|nullable|max:120' ,
'license_name' => 'string|nullable|max:100' ,
'notes' => 'string|nullable' ,
2018-05-16 18:35:11 -07:00
'category_id' => 'required|exists:categories,id' ,
2016-12-14 11:12:21 -08:00
'company_id' => 'integer|nullable' ,
2025-09-03 15:06:27 +01:00
'purchase_cost' => 'numeric|nullable|gte:0|max:99999999999999999.99' ,
2024-07-24 20:13:01 +01:00
'purchase_date' => 'date_format:Y-m-d|nullable|max:10|required_with:depreciation_id' ,
2023-04-12 08:26:36 -07:00
'expiration_date' => 'date_format:Y-m-d|nullable|max:10' ,
'termination_date' => 'date_format:Y-m-d|nullable|max:10' ,
2024-02-28 11:08:47 +00:00
'min_amt' => 'numeric|nullable|gte:0' ,
2021-06-10 20:15:52 +00:00
];
2016-03-25 01:18:05 -07:00
2021-06-10 20:15:52 +00:00
/**
* The attributes that are mass assignable .
*
* @ var array
*/
2017-01-10 18:19:18 -06:00
protected $fillable = [
'company_id' ,
2018-02-25 15:10:02 -05:00
'depreciation_id' ,
'expiration_date' ,
'license_email' ,
'license_name' , //actually licensed_to
'maintained' ,
'manufacturer_id' ,
2018-05-04 21:01:38 -07:00
'category_id' ,
2018-02-25 15:10:02 -05:00
'name' ,
'notes' ,
'order_number' ,
'purchase_cost' ,
'purchase_date' ,
'purchase_order' ,
'reassignable' ,
'seats' ,
'serial' ,
'supplier_id' ,
'termination_date' ,
2024-09-17 22:16:41 +01:00
'created_by' ,
2024-02-28 11:08:47 +00:00
'min_amt' ,
2017-01-10 18:19:18 -06:00
];
2018-07-16 23:13:07 +02:00
use Searchable ;
2021-06-10 20:15:52 +00:00
2018-07-16 23:13:07 +02:00
/**
* The attributes that should be included when searching the model .
2021-06-10 20:15:52 +00:00
*
2018-07-16 23:13:07 +02:00
* @ var array
*/
protected $searchableAttributes = [
2021-06-10 20:15:52 +00:00
'name' ,
'serial' ,
'notes' ,
'order_number' ,
'purchase_order' ,
'purchase_cost' ,
2018-07-16 23:13:07 +02:00
'purchase_date' ,
'expiration_date' ,
];
/**
* The relations and their attributes that should be included when searching the model .
2021-06-10 20:15:52 +00:00
*
2018-07-16 23:13:07 +02:00
* @ var array
*/
protected $searchableRelations = [
2023-04-18 02:06:32 -07:00
'manufacturer' => [ 'name' ],
'company' => [ 'name' ],
'category' => [ 'name' ],
'depreciation' => [ 'name' ],
2025-06-30 11:27:30 +01:00
'supplier' => [ 'name' ],
2018-08-01 00:06:41 -07:00
];
2024-04-22 17:58:49 -07:00
protected $appends = [ 'free_seat_count' ];
2018-07-16 23:13:07 +02:00
2018-08-01 00:06:41 -07:00
/**
* Update seat counts when the license is updated
*
* @ author A . Gianotto < snipe @ snipe . net >
2025-07-09 21:48:53 +01:00
* @ since [ v3 . 0 ]
2018-08-01 00:06:41 -07:00
*/
2017-01-10 18:19:18 -06:00
public static function boot ()
{
parent :: boot ();
// We need to listen for created for the initial setup so that we have a license ID.
2025-07-09 21:48:53 +01:00
static :: created (
function ( $license ) {
$newSeatCount = $license -> getAttributes ()[ 'seats' ];
2021-06-10 20:15:52 +00:00
2025-07-09 21:48:53 +01:00
return static :: adjustSeatCount ( $license , 0 , $newSeatCount );
}
);
2017-01-10 18:19:18 -06:00
// However, we listen for updating to be able to prevent the edit if we cannot delete enough seats.
2025-07-09 21:48:53 +01:00
static :: updating (
function ( $license ) {
$newSeatCount = $license -> getAttributes ()[ 'seats' ];
//$oldSeatCount = isset($license->getOriginal()['seats']) ? $license->getOriginal()['seats'] : 0;
/*
That previous method * did * mostly work , but if you ever managed to get your $license -> seats value out of whack
with your actual count of license_seats * records * , you would never manage to get back 'into whack' .
The below method actually grabs a count of existing license_seats records , so it will be more accurate .
This means that if your license_seats are out of whack , you can change the quantity and hit 'save' and it
will manage to 'true up' and make your counts line up correctly .
*/
$oldSeatCount = $license -> license_seats_count ;
2021-06-10 20:15:52 +00:00
2025-07-09 21:48:53 +01:00
return static :: adjustSeatCount ( $license , $oldSeatCount , $newSeatCount );
}
);
2017-01-10 18:19:18 -06:00
}
2026-03-06 03:33:04 +00:00
public function isDeletable ()
{
return Gate :: allows ( 'delete' , $this )
&& ( $this -> free_seats_count == $this -> seats )
&& ( $this -> deleted_at == '' );
}
2025-10-03 14:13:58 +01:00
protected function terminatesFormattedDate () : Attribute
{
return Attribute :: make (
get : fn ( mixed $value , array $attributes ) => $attributes [ 'termination_date' ] ? Helper :: getFormattedDateObject ( $attributes [ 'termination_date' ], 'date' , false ) : null ,
);
}
2025-10-03 15:03:14 +01:00
protected function terminatesDiffInDays () : Attribute
{
return Attribute :: make (
2025-10-03 16:00:58 +01:00
get : fn ( mixed $value , array $attributes ) => $attributes [ 'termination_date' ] ? Carbon :: now () -> diffInDays ( $attributes [ 'termination_date' ]) : null ,
2025-10-03 15:03:14 +01:00
);
}
2025-10-03 14:13:58 +01:00
protected function terminatesDiffForHumans () : Attribute
{
return Attribute :: make (
get : fn ( mixed $value , array $attributes ) => $attributes [ 'termination_date' ] ? Carbon :: parse ( $attributes [ 'termination_date' ]) -> diffForHumans () : null ,
);
}
2025-10-03 15:03:14 +01:00
2025-06-24 13:53:11 +01:00
public function prepareLimitChangeRule ( $parameters , $field )
{
$actual_seat_count = $this -> licenseseats () -> count (); //we use the *actual* seat count here, in case your license has gone wonky
$lower_bound = $actual_seat_count - $parameters [ 0 ];
$upper_bound = $actual_seat_count + $parameters [ 0 ];
return [ " between " , ( $lower_bound <= 0 ? 1 : $lower_bound ), $upper_bound ];
2017-01-10 18:19:18 -06:00
}
2018-08-01 00:06:41 -07:00
/**
* Balance seat counts
*
* @ author A . Gianotto < snipe @ snipe . net >
2025-07-09 21:48:53 +01:00
* @ since [ v3 . 0 ]
2018-08-01 00:06:41 -07:00
* @ return \Illuminate\Database\Eloquent\Relations\Relation
*/
2017-01-10 18:19:18 -06:00
public static function adjustSeatCount ( $license , $oldSeats , $newSeats )
{
// If the seats haven't changed, continue on happily.
2021-06-10 20:15:52 +00:00
if ( $oldSeats == $newSeats ) {
2017-01-10 18:19:18 -06:00
return true ;
}
// On Create, we just make one for each of the seats.
$change = abs ( $oldSeats - $newSeats );
if ( $oldSeats > $newSeats ) {
2018-05-04 21:01:38 -07:00
2017-01-10 18:19:18 -06:00
// Need to delete seats... lets see if if we have enough.
2025-06-24 13:53:11 +01:00
$seatsAvailableForDelete = $license -> licenseseats () -> whereNull ( 'assigned_to' ) -> whereNull ( 'asset_id' ) -> limit ( $change );
2017-01-10 18:19:18 -06:00
if ( $change > $seatsAvailableForDelete -> count ()) {
Session :: flash ( 'error' , trans ( 'admin/licenses/message.assoc_users' ));
2021-06-10 20:15:52 +00:00
2017-01-10 18:19:18 -06:00
return false ;
}
2025-06-24 13:53:11 +01:00
$seatsAvailableForDelete -> delete ();
2017-01-10 18:19:18 -06:00
// Log Deletion of seats.
$logAction = new Actionlog ;
2021-06-10 20:15:52 +00:00
$logAction -> item_type = self :: class ;
2017-01-10 18:19:18 -06:00
$logAction -> item_id = $license -> id ;
2024-09-19 17:31:46 +01:00
$logAction -> created_by = auth () -> id () ? : 1 ; // We don't have an id while running the importer from CLI.
2024-10-30 19:43:54 -07:00
$logAction -> note = " deleted { $change } seats " ;
2021-06-10 20:15:52 +00:00
$logAction -> target_id = null ;
2025-12-16 14:55:00 -08:00
$logAction -> quantity = $change ;
2017-01-10 18:19:18 -06:00
$logAction -> logaction ( 'delete seats' );
2021-06-10 20:15:52 +00:00
2017-01-10 18:19:18 -06:00
return true ;
}
// Else we're adding seats.
2022-01-10 13:54:57 -05:00
//Create enough seats for the change.
$licenseInsert = [];
for ( $i = $oldSeats ; $i < $newSeats ; $i ++ ) {
$licenseInsert [] = [
2024-09-17 22:16:41 +01:00
'created_by' => auth () -> id (),
2022-01-10 13:54:57 -05:00
'license_id' => $license -> id ,
'created_at' => now (),
'updated_at' => now ()
];
}
//Chunk and use DB transactions to prevent timeouts.
2022-04-12 21:04:57 +01:00
2025-07-09 21:48:53 +01:00
collect ( $licenseInsert ) -> chunk ( 1000 ) -> each (
function ( $chunk ) {
DB :: transaction (
function () use ( $chunk ) {
LicenseSeat :: insert ( $chunk -> toArray ());
}
);
}
);
2022-01-10 13:54:57 -05:00
// On initial create, we shouldn't log the addition of seats.
2017-01-10 18:19:18 -06:00
if ( $license -> id ) {
//Log the addition of license to the log.
$logAction = new Actionlog ();
2021-06-10 20:15:52 +00:00
$logAction -> item_type = self :: class ;
2017-01-10 18:19:18 -06:00
$logAction -> item_id = $license -> id ;
2024-09-19 17:31:46 +01:00
$logAction -> created_by = auth () -> id () ? : 1 ; // Importer.
2024-10-30 19:43:54 -07:00
$logAction -> note = " added { $change } seats " ;
2021-06-10 20:15:52 +00:00
$logAction -> target_id = null ;
2025-12-16 14:55:00 -08:00
$logAction -> quantity = $change ;
2017-01-10 18:19:18 -06:00
$logAction -> logaction ( 'add seats' );
}
2021-06-10 20:15:52 +00:00
2017-01-10 18:19:18 -06:00
return true ;
}
2018-08-01 00:06:41 -07:00
/**
* Sets the attribute for whether or not the license is maintained
*
* @ author A . Gianotto < snipe @ snipe . net >
2025-07-09 21:48:53 +01:00
* @ since [ v1 . 0 ]
2018-08-01 00:06:41 -07:00
* @ return mixed
*/
2017-01-10 18:19:18 -06:00
public function setMaintainedAttribute ( $value )
{
$this -> attributes [ 'maintained' ] = filter_var ( $value , FILTER_VALIDATE_BOOLEAN );
}
2018-08-01 00:06:41 -07:00
/**
* Sets the reassignable attribute
*
* @ author A . Gianotto < snipe @ snipe . net >
2025-07-09 21:48:53 +01:00
* @ since [ v1 . 0 ]
2018-08-01 00:06:41 -07:00
* @ return mixed
*/
2017-01-10 18:19:18 -06:00
public function setReassignableAttribute ( $value )
{
$this -> attributes [ 'reassignable' ] = filter_var ( $value , FILTER_VALIDATE_BOOLEAN );
}
2018-08-01 00:06:41 -07:00
/**
* Sets expiration date attribute
*
* @ author A . Gianotto < snipe @ snipe . net >
2025-07-09 21:48:53 +01:00
* @ since [ v1 . 0 ]
2018-08-01 00:06:41 -07:00
* @ return mixed
*/
2016-12-26 18:17:46 -05:00
public function setExpirationDateAttribute ( $value )
{
2016-12-29 14:02:18 -08:00
if ( $value == '' || $value == '0000-00-00' ) {
2016-12-26 18:17:46 -05:00
$value = null ;
2017-01-10 18:19:18 -06:00
} else {
$value = ( new Carbon ( $value )) -> toDateString ();
2016-12-26 18:17:46 -05:00
}
$this -> attributes [ 'expiration_date' ] = $value ;
}
2018-08-01 00:06:41 -07:00
/**
* Sets termination date attribute
*
* @ author A . Gianotto < snipe @ snipe . net >
2025-07-09 21:48:53 +01:00
* @ since [ v2 . 0 ]
2018-08-01 00:06:41 -07:00
* @ return mixed
*/
2016-12-26 18:17:46 -05:00
public function setTerminationDateAttribute ( $value )
{
2016-12-29 14:02:18 -08:00
if ( $value == '' || $value == '0000-00-00' ) {
2016-12-26 18:17:46 -05:00
$value = null ;
2017-01-10 18:19:18 -06:00
} else {
$value = ( new Carbon ( $value )) -> toDateString ();
2016-12-26 18:17:46 -05:00
}
$this -> attributes [ 'termination_date' ] = $value ;
}
2025-09-10 12:08:14 -07:00
public function isInactive () : bool
2025-09-15 20:05:35 +01:00
{
$day = now () -> startOfDay ();
2025-09-10 12:08:14 -07:00
2025-09-15 20:05:35 +01:00
$expired = $this -> expiration_date && $this -> asDateTime ( $this -> expiration_date ) -> startofDay () -> lessThanOrEqualTo ( $day );
2025-09-10 12:08:14 -07:00
2025-09-15 20:05:35 +01:00
$terminated = $this -> termination_date && $this -> asDateTime ( $this -> termination_date ) -> startofDay () -> lessThanOrEqualTo ( $day );
2025-09-10 12:08:14 -07:00
2025-09-15 13:43:08 +01:00
return $this -> isExpired () || $this -> isTerminated ();
2025-09-15 20:05:35 +01:00
}
2025-09-15 13:43:08 +01:00
public function isExpired () : bool
{
$day = now () -> startOfDay ();
$expired = $this -> expiration_date && $this -> asDateTime ( $this -> expiration_date ) -> startofDay () -> lessThanOrEqualTo ( $day );
return $expired ;
}
public function isTerminated () : bool
{
$day = now () -> startOfDay ();
$terminated = $this -> termination_date && $this -> asDateTime ( $this -> termination_date ) -> startofDay () -> lessThanOrEqualTo ( $day );
return $terminated ;
2025-09-15 13:22:25 +01:00
}
2024-04-22 17:58:49 -07:00
/**
* Sets free_seat_count attribute
*
* @ author G . Martinez
2025-07-09 21:48:53 +01:00
* @ since [ v6 . 3 ]
2024-04-22 17:58:49 -07:00
* @ return mixed
*/
2025-07-09 21:48:53 +01:00
public function getFreeSeatCountAttribute ()
{
2024-04-22 17:58:49 -07:00
return $this -> attributes [ 'free_seat_count' ] = $this -> remaincount ();
}
2016-12-26 18:17:46 -05:00
2018-08-01 00:06:41 -07:00
/**
* Establishes the license -> company relationship
*
* @ author A . Gianotto < snipe @ snipe . net >
2025-07-09 21:48:53 +01:00
* @ since [ v2 . 0 ]
2018-08-01 00:06:41 -07:00
* @ return \Illuminate\Database\Eloquent\Relations\Relation
*/
2016-03-25 01:18:05 -07:00
public function company ()
{
2021-06-10 20:16:56 +00:00
return $this -> belongsTo ( \App\Models\Company :: class , 'company_id' );
2016-03-25 01:18:05 -07:00
}
2018-08-01 00:06:41 -07:00
/**
* Establishes the license -> category relationship
*
* @ author A . Gianotto < snipe @ snipe . net >
2025-07-09 21:48:53 +01:00
* @ since [ v4 . 4.0 ]
2018-08-01 00:06:41 -07:00
* @ return \Illuminate\Database\Eloquent\Relations\Relation
*/
2018-05-04 21:01:38 -07:00
public function category ()
{
2026-02-23 11:41:45 +00:00
return $this -> belongsTo ( \App\Models\Category :: class , 'category_id' ) -> withTrashed ();
2018-05-04 21:01:38 -07:00
}
2018-08-01 00:06:41 -07:00
/**
* Establishes the license -> manufacturer relationship
*
* @ author A . Gianotto < snipe @ snipe . net >
2025-07-09 21:48:53 +01:00
* @ since [ v2 . 0 ]
2018-08-01 00:06:41 -07:00
* @ return \Illuminate\Database\Eloquent\Relations\Relation
*/
2016-08-16 20:49:54 -05:00
public function manufacturer ()
{
2026-02-23 11:41:45 +00:00
return $this -> belongsTo ( \App\Models\Manufacturer :: class , 'manufacturer_id' ) -> withTrashed ();
2016-08-16 20:49:54 -05:00
}
2018-08-01 00:06:41 -07:00
/**
* Determine whether the user should be emailed on checkin / checkout
*
* @ author A . Gianotto < snipe @ snipe . net >
2025-07-09 21:48:53 +01:00
* @ since [ v2 . 0 ]
2021-06-10 20:15:52 +00:00
* @ return bool
2018-08-01 00:06:41 -07:00
*/
2018-05-04 21:01:38 -07:00
public function checkin_email ()
{
2023-09-06 11:54:11 -06:00
if ( $this -> category ) {
return $this -> category -> checkin_email ;
}
return false ;
2018-05-04 21:01:38 -07:00
}
2025-09-23 18:40:03 -07:00
public function checkouts ()
{
return $this -> assetlog () -> where ( 'action_type' , '=' , 'checkout' )
-> orderBy ( 'created_at' , 'desc' )
-> withTrashed ();
}
2018-08-01 00:06:41 -07:00
/**
* Determine whether the user should be required to accept the license
*
* @ author A . Gianotto < snipe @ snipe . net >
2025-07-09 21:48:53 +01:00
* @ since [ v4 . 0 ]
2021-06-10 20:15:52 +00:00
* @ return bool
2018-08-01 00:06:41 -07:00
*/
2018-05-04 21:01:38 -07:00
public function requireAcceptance ()
{
2023-09-06 11:54:11 -06:00
if ( $this -> category ) {
return $this -> category -> require_acceptance ;
}
return false ;
2018-05-04 21:01:38 -07:00
}
2016-03-25 01:18:05 -07:00
/**
2018-08-01 00:06:41 -07:00
* Establishes the license -> assigned user relationship
*
* @ author A . Gianotto < snipe @ snipe . net >
2025-07-09 21:48:53 +01:00
* @ since [ v2 . 0 ]
2018-08-01 00:06:41 -07:00
* @ return \Illuminate\Database\Eloquent\Relations\Relation
2016-03-25 01:18:05 -07:00
*/
public function assignedusers ()
{
2022-10-17 13:10:09 +07:00
return $this -> belongsToMany ( \App\Models\User :: class , 'license_seats' , 'license_id' , 'assigned_to' );
2016-03-25 01:18:05 -07:00
}
/**
2018-08-01 00:06:41 -07:00
* Establishes the license -> action logs relationship
*
* @ author A . Gianotto < snipe @ snipe . net >
2025-07-09 21:48:53 +01:00
* @ since [ v2 . 0 ]
2018-08-01 00:06:41 -07:00
* @ return \Illuminate\Database\Eloquent\Relations\Relation
*/
2016-03-25 01:18:05 -07:00
public function assetlog ()
{
2021-06-10 20:16:56 +00:00
return $this -> hasMany ( \App\Models\Actionlog :: class , 'item_id' )
2021-06-10 20:15:52 +00:00
-> where ( 'item_type' , '=' , self :: class )
2016-03-25 01:18:05 -07:00
-> orderBy ( 'created_at' , 'desc' );
}
/**
2018-08-01 00:06:41 -07:00
* Establishes the license -> admin user relationship
*
* @ author A . Gianotto < snipe @ snipe . net >
2025-07-09 21:48:53 +01:00
* @ since [ v2 . 0 ]
2018-08-01 00:06:41 -07:00
* @ return \Illuminate\Database\Eloquent\Relations\Relation
*/
2016-03-25 01:18:05 -07:00
public function adminuser ()
{
2026-02-23 10:49:44 +00:00
return $this -> belongsTo ( \App\Models\User :: class , 'created_by' ) -> withTrashed ();
2016-03-25 01:18:05 -07:00
}
/**
2018-08-01 00:06:41 -07:00
* Returns the total number of all license seats
*
* @ todo this can probably be refactored at some point . We don ' t need counting methods .
*
* @ author A . Gianotto < snipe @ snipe . net >
2025-07-09 21:48:53 +01:00
* @ since [ v2 . 0 ]
2018-08-01 00:06:41 -07:00
* @ return int
*/
2016-03-25 01:18:05 -07:00
public static function assetcount ()
{
return LicenseSeat :: whereNull ( 'deleted_at' )
2023-04-18 02:06:32 -07:00
-> count ();
2016-03-25 01:18:05 -07:00
}
/**
2018-08-01 00:06:41 -07:00
* Return the number of seats for this asset
*
* @ todo this can also probably be refactored at some point .
*
* @ author A . Gianotto < snipe @ snipe . net >
2025-07-09 21:48:53 +01:00
* @ since [ v2 . 0 ]
2018-08-01 00:06:41 -07:00
* @ return \Illuminate\Database\Eloquent\Relations\Relation
*/
2016-03-25 01:18:05 -07:00
public function totalSeatsByLicenseID ()
{
return LicenseSeat :: where ( 'license_id' , '=' , $this -> id )
2023-04-18 02:06:32 -07:00
-> whereNull ( 'deleted_at' )
-> count ();
2016-03-25 01:18:05 -07:00
}
2018-08-01 00:06:41 -07:00
/**
* Establishes the license -> seat relationship
*
* We do this to eager load the " count " of seats from the controller .
* Otherwise calling " count() " on each model results in n + 1 sadness .
*
* @ author A . Gianotto < snipe @ snipe . net >
2025-07-09 21:48:53 +01:00
* @ since [ v2 . 0 ]
2018-08-01 00:06:41 -07:00
* @ return \Illuminate\Database\Eloquent\Relations\Relation
*/
2023-04-18 02:06:32 -07:00
public function licenseSeatsRelation ()
2016-10-28 16:15:13 -05:00
{
return $this -> hasMany ( LicenseSeat :: class ) -> whereNull ( 'deleted_at' ) -> selectRaw ( 'license_id, count(*) as count' ) -> groupBy ( 'license_id' );
}
2018-08-01 00:06:41 -07:00
/**
2023-04-18 02:06:32 -07:00
* Sets the license seat count attribute
2018-08-01 00:06:41 -07:00
*
* @ author A . Gianotto < snipe @ snipe . net >
2025-07-09 21:48:53 +01:00
* @ since [ v2 . 0 ]
2018-08-01 00:06:41 -07:00
* @ return int
*/
2023-04-18 02:06:32 -07:00
public function getLicenseSeatsCountAttribute ()
{
if ( $this -> licenseSeatsRelation -> first ()) {
return $this -> licenseSeatsRelation -> first () -> count ;
}
return 0 ;
}
/**
* Returns the number of total available seats across all licenses
*
* @ author A . Gianotto < snipe @ snipe . net >
2025-07-09 21:48:53 +01:00
* @ since [ v2 . 0 ]
2023-04-18 02:06:32 -07:00
* @ return int
*/
public static function availassetcount ()
2016-03-25 01:18:05 -07:00
{
return LicenseSeat :: whereNull ( 'assigned_to' )
2023-04-18 01:01:44 -07:00
-> whereNull ( 'asset_id' )
-> whereNull ( 'deleted_at' )
-> count ();
2016-03-25 01:18:05 -07:00
}
2024-04-22 17:58:49 -07:00
/**
* Returns the available seats remaining
*
* @ author A . Gianotto < snipe @ snipe . net >
2025-07-09 21:48:53 +01:00
* @ since [ v2 . 0 ]
2024-04-22 17:58:49 -07:00
* @ return int
*/
2023-04-18 02:06:32 -07:00
2016-03-25 01:18:05 -07:00
/**
2018-08-01 00:06:41 -07:00
* Returns the number of total available seats for this license
*
* @ author A . Gianotto < snipe @ snipe . net >
2025-07-09 21:48:53 +01:00
* @ since [ v2 . 0 ]
2018-08-01 00:06:41 -07:00
* @ return \Illuminate\Database\Eloquent\Relations\Relation
2016-03-25 01:18:05 -07:00
*/
2016-10-28 16:15:13 -05:00
public function availCount ()
2016-03-25 01:18:05 -07:00
{
2023-04-18 02:06:32 -07:00
return $this -> licenseSeatsRelation ()
2017-11-02 19:16:09 -07:00
-> whereNull ( 'asset_id' )
-> whereNull ( 'assigned_to' )
2025-01-22 10:46:28 -08:00
-> where ( 'unreassignable_seat' , '=' , false )
2017-11-02 21:07:59 -07:00
-> whereNull ( 'deleted_at' );
2016-10-28 16:15:13 -05:00
}
2026-03-06 03:33:04 +00:00
/**
* This is really dumb - needs to be refactored , since we have ~ 3 diff methods that do almost the same thing
*
* @ author A . Gianotto < snipe @ snipe . net >
* @ since [ v2 . 0 ]
* @ return \Illuminate\Database\Eloquent\Relations\Relation
*/
public function numRemaining ()
{
return $this -> licenseSeatsRelation ()
-> whereNull ( 'asset_id' )
-> whereNull ( 'assigned_to' )
-> where ( 'unreassignable_seat' , '=' , false )
-> whereNull ( 'deleted_at' )
-> count ();
}
2018-08-01 00:06:41 -07:00
/**
* Sets the available seats attribute
*
* @ author A . Gianotto < snipe @ snipe . net >
2025-07-09 21:48:53 +01:00
* @ since [ v3 . 0 ]
2018-08-01 00:06:41 -07:00
* @ return mixed
*/
2016-10-28 16:15:13 -05:00
public function getAvailSeatsCountAttribute ()
{
if ( $this -> availCount -> first ()) {
return $this -> availCount -> first () -> count ;
}
return 0 ;
2016-03-25 01:18:05 -07:00
}
/**
2018-08-01 00:06:41 -07:00
* Retuns the number of assigned seats for this asset
2016-03-25 01:18:05 -07:00
*
2018-08-01 00:06:41 -07:00
* @ author A . Gianotto < snipe @ snipe . net >
2025-07-09 21:48:53 +01:00
* @ since [ v3 . 0 ]
2018-08-01 00:06:41 -07:00
* @ return \Illuminate\Database\Eloquent\Relations\Relation
2016-03-25 01:18:05 -07:00
*/
2016-10-28 16:15:13 -05:00
public function assignedCount ()
2016-03-25 01:18:05 -07:00
{
2025-07-09 21:48:53 +01:00
return $this -> licenseSeatsRelation () -> where (
function ( $query ) {
$query -> whereNotNull ( 'assigned_to' )
-> orWhereNotNull ( 'asset_id' );
}
);
2016-10-28 16:15:13 -05:00
}
2016-03-25 01:18:05 -07:00
2018-08-01 00:06:41 -07:00
/**
* Sets the assigned seats attribute
*
* @ author A . Gianotto < snipe @ snipe . net >
2025-07-09 21:48:53 +01:00
* @ since [ v1 . 0 ]
2018-08-01 00:06:41 -07:00
* @ return int
*/
2016-10-28 16:15:13 -05:00
public function getAssignedSeatsCountAttribute ()
{
if ( $this -> assignedCount -> first ()) {
return $this -> assignedCount -> first () -> count ;
}
2016-03-25 01:18:05 -07:00
2016-10-28 16:15:13 -05:00
return 0 ;
2016-03-25 01:18:05 -07:00
}
2025-01-22 10:46:28 -08:00
/**
* Calculates the number of unreassignable seats
*
* @ author G . Martinez
* @ since [ v7 . 1.15 ]
*/
public static function unReassignableCount ( $license ) : int
{
$count = 0 ;
if ( ! $license -> reassignable ) {
2025-09-11 10:23:53 -07:00
$count = LicenseSeat :: query () -> where ( 'unreassignable_seat' , '=' , true )
2025-01-22 10:46:28 -08:00
-> where ( 'license_id' , '=' , $license -> id )
-> count ();
}
return $count ;
}
2018-08-01 00:06:41 -07:00
/**
* Calculates the number of remaining seats
*
* @ author A . Gianotto < snipe @ snipe . net >
2025-07-09 21:48:53 +01:00
* @ since [ v1 . 0 ]
2018-08-01 00:06:41 -07:00
* @ return int
*/
2025-01-22 10:46:28 -08:00
public function remaincount () : int
2016-03-25 01:18:05 -07:00
{
2016-10-28 16:15:13 -05:00
$total = $this -> licenseSeatsCount ;
2021-06-10 20:15:52 +00:00
$taken = $this -> assigned_seats_count ;
2025-01-22 10:46:28 -08:00
$unreassignable = self :: unReassignableCount ( $this );
2025-01-13 14:31:54 -08:00
$diff = ( $total - $taken - $unreassignable );
2021-06-10 20:15:52 +00:00
2024-04-22 17:58:49 -07:00
return ( int ) $diff ;
2016-03-25 01:18:05 -07:00
}
/**
2018-08-01 00:06:41 -07:00
* Returns the total number of seats for this license
*
* @ author A . Gianotto < snipe @ snipe . net >
2025-07-09 21:48:53 +01:00
* @ since [ v1 . 0 ]
2018-08-01 00:06:41 -07:00
* @ return int
2016-03-25 01:18:05 -07:00
*/
public function totalcount ()
{
2021-06-10 20:15:52 +00:00
$avail = $this -> availSeatsCount ;
$taken = $this -> assignedcount ();
$diff = ( $avail + $taken );
2016-03-25 01:18:05 -07:00
return $diff ;
}
/**
2018-08-01 00:06:41 -07:00
* Establishes the license -> seats relationship
*
* @ author A . Gianotto < snipe @ snipe . net >
2025-07-09 21:48:53 +01:00
* @ since [ v1 . 0 ]
2018-08-01 00:06:41 -07:00
* @ return \Illuminate\Database\Eloquent\Relations\Relation
2016-03-25 01:18:05 -07:00
*/
public function licenseseats ()
{
2021-06-10 20:16:56 +00:00
return $this -> hasMany ( \App\Models\LicenseSeat :: class );
2016-03-25 01:18:05 -07:00
}
2018-08-01 00:06:41 -07:00
/**
* Establishes the license -> supplier relationship
*
* @ author A . Gianotto < snipe @ snipe . net >
2025-07-09 21:48:53 +01:00
* @ since [ v1 . 0 ]
2018-08-01 00:06:41 -07:00
* @ return \Illuminate\Database\Eloquent\Relations\Relation
*/
2016-03-25 01:18:05 -07:00
public function supplier ()
{
2021-06-10 20:16:56 +00:00
return $this -> belongsTo ( \App\Models\Supplier :: class , 'supplier_id' );
2016-03-25 01:18:05 -07:00
}
2018-08-01 00:06:41 -07:00
/**
* Gets the next available free seat - used by
2017-09-25 21:40:43 -07:00
* the API to populate next_seat
2018-08-01 00:06:41 -07:00
*
* @ author A . Gianotto < snipe @ snipe . net >
2025-07-09 21:48:53 +01:00
* @ since [ v3 . 0 ]
2018-08-01 00:06:41 -07:00
* @ return mixed
2017-09-25 21:40:43 -07:00
*/
2016-03-25 01:18:05 -07:00
public function freeSeat ()
{
2017-11-20 19:21:05 -08:00
return $this -> licenseseats ()
2023-04-18 02:06:32 -07:00
-> whereNull ( 'deleted_at' )
2025-01-16 12:24:32 -08:00
-> where ( 'unreassignable_seat' , '=' , false )
2023-04-18 02:06:32 -07:00
-> where ( function ( $query ) {
$query -> whereNull ( 'assigned_to' )
-> whereNull ( 'asset_id' );
})
-> orderBy ( 'id' , 'asc' )
-> first ();
2016-03-25 01:18:05 -07:00
}
2018-08-01 00:06:41 -07:00
/**
* Establishes the license -> free seats relationship
*
* @ author A . Gianotto < snipe @ snipe . net >
2025-07-09 21:48:53 +01:00
* @ since [ v1 . 0 ]
2018-08-01 00:06:41 -07:00
* @ return \Illuminate\Database\Eloquent\Relations\Relation
*/
2017-11-02 19:16:09 -07:00
public function freeSeats ()
{
2021-06-10 20:16:56 +00:00
return $this -> hasMany ( \App\Models\LicenseSeat :: class ) -> whereNull ( 'assigned_to' ) -> whereNull ( 'deleted_at' ) -> whereNull ( 'asset_id' );
2017-11-02 19:16:09 -07:00
}
2025-09-15 13:22:25 +01:00
public function scopeActiveLicenses ( $query )
{
2025-09-17 11:44:28 +01:00
return $query -> whereNull ( 'licenses.deleted_at' )
2025-09-15 13:22:25 +01:00
// The termination date is null or within range
-> where ( function ( $query ) {
$query -> whereNull ( 'termination_date' )
-> orWhereDate ( 'termination_date' , '>' , [ Carbon :: now ()]);
})
-> where ( function ( $query ) {
$query -> whereNull ( 'expiration_date' )
-> orWhereDate ( 'expiration_date' , '>' , [ Carbon :: now ()]);
});
}
2025-10-03 16:00:58 +01:00
/**
* Expiried / terminated licenses scope
*
* @ author A . Gianotto < snipe @ snipe . net >
* @ since [ v1 . 0 ]
* @ return \Illuminate\Database\Eloquent\Relations\Relation
* @ see \App\Console\Commands\SendExpiringLicenseNotifications
*/
public function scopeExpiredLicenses ( $query )
{
return $query -> whereDate ( 'termination_date' , '<=' , Carbon :: now ()) // The termination date is null or within range
-> orWhere ( function ( $query ) {
$query -> whereDate ( 'expiration_date' , '<=' , Carbon :: now ());
})
2025-10-08 15:28:48 +01:00
-> whereNull ( 'licenses.deleted_at' );
2025-10-03 16:00:58 +01:00
}
2025-10-03 14:13:58 +01:00
/**
* Expiring / terminating licenses scope
*
* This checks if :
*
* 1 ) The license has not been deleted
* 2 ) The expiration date is between now and the number of days specified
* 3 ) There is an expiration date set and the termination date has not passed
* 4 ) The license termination date is null or has not passed
*
* @ author A . Gianotto < snipe @ snipe . net >
* @ since [ v1 . 0 ]
* @ return \Illuminate\Database\Eloquent\Relations\Relation
* @ see \App\Console\Commands\SendExpiringLicenseNotifications
*/
2025-11-24 11:41:50 -08:00
public function scopeExpiringLicenses ( $query , $days = 60 , $includeExpired = false )
2025-09-15 13:22:25 +01:00
{
2025-10-03 14:13:58 +01:00
return $query // The termination date is null or within range
-> where ( function ( $query ) use ( $days ) {
$query -> whereNull ( 'termination_date' )
-> orWhereBetween ( 'termination_date' , [ Carbon :: now (), Carbon :: now () -> addDays ( $days )]);
})
2025-11-24 11:41:50 -08:00
-> where ( function ( $query ) use ( $days , $includeExpired ) {
2025-10-03 14:13:58 +01:00
$query -> whereNotNull ( 'expiration_date' )
2025-10-03 16:00:58 +01:00
// Handle expiring licenses without termination dates
2025-11-24 11:41:50 -08:00
-> where ( function ( $query ) use ( $days , $includeExpired ) {
2025-10-03 14:13:58 +01:00
$query -> whereNull ( 'termination_date' )
2025-11-24 11:41:50 -08:00
-> whereBetween ( 'expiration_date' , [ Carbon :: now (), Carbon :: now () -> addDays ( $days )])
//include expired licenses if requested
-> when ( $includeExpired , function ( $query ) use ( $days ) {
$query -> orwhereDate ( 'expiration_date' , '<=' , Carbon :: now ());
});
2025-10-03 14:13:58 +01:00
})
2025-10-03 16:00:58 +01:00
// Handle expiring licenses with termination dates in the future
2025-10-03 14:13:58 +01:00
-> orWhere ( function ( $query ) use ( $days ) {
$query -> whereBetween ( 'termination_date' , [ Carbon :: now (), Carbon :: now () -> addDays ( $days )]);
});
2025-10-03 16:00:58 +01:00
});
2025-09-15 13:22:25 +01:00
}
2016-11-11 19:46:18 -08:00
/**
* Query builder scope to order on manufacturer
*
2025-07-09 21:48:53 +01:00
* @ param \Illuminate\Database\Query\Builder $query Query builder instance
* @ param string $order Order
2016-11-11 19:46:18 -08:00
*
2018-08-01 00:06:41 -07:00
* @ return \Illuminate\Database\Query\Builder Modified query builder
2016-11-11 19:46:18 -08:00
*/
public function scopeOrderManufacturer ( $query , $order )
{
return $query -> leftJoin ( 'manufacturers' , 'licenses.manufacturer_id' , '=' , 'manufacturers.id' ) -> select ( 'licenses.*' )
-> orderBy ( 'manufacturers.name' , $order );
}
2017-08-10 14:38:04 -07:00
/**
* Query builder scope to order on supplier
*
2025-07-09 21:48:53 +01:00
* @ param \Illuminate\Database\Query\Builder $query Query builder instance
* @ param string $order Order
2017-08-10 14:38:04 -07:00
*
* @ return \Illuminate\Database\Query\Builder Modified query builder
*/
public function scopeOrderSupplier ( $query , $order )
{
return $query -> leftJoin ( 'suppliers' , 'licenses.supplier_id' , '=' , 'suppliers.id' ) -> select ( 'licenses.*' )
-> orderBy ( 'suppliers.name' , $order );
}
2016-11-11 19:46:18 -08:00
/**
* Query builder scope to order on company
*
2025-07-09 21:48:53 +01:00
* @ param \Illuminate\Database\Query\Builder $query Query builder instance
* @ param text $order Order
2016-11-11 19:46:18 -08:00
*
2018-08-01 00:06:41 -07:00
* @ return \Illuminate\Database\Query\Builder Modified query builder
2016-11-11 19:46:18 -08:00
*/
public function scopeOrderCompany ( $query , $order )
{
return $query -> leftJoin ( 'companies as companies' , 'licenses.company_id' , '=' , 'companies.id' ) -> select ( 'licenses.*' )
-> orderBy ( 'companies.name' , $order );
}
2024-07-26 10:24:13 +01:00
/**
* Query builder scope to order on the user that created it
*/
2024-09-19 16:45:51 +01:00
public function scopeOrderByCreatedBy ( $query , $order )
2024-07-26 10:24:13 +01:00
{
2024-09-19 16:45:51 +01:00
return $query -> leftJoin ( 'users as admin_sort' , 'licenses.created_by' , '=' , 'admin_sort.id' ) -> select ( 'licenses.*' ) -> orderBy ( 'admin_sort.first_name' , $order ) -> orderBy ( 'admin_sort.last_name' , $order );
2024-07-26 10:24:13 +01:00
}
2024-10-30 19:43:54 -07:00
}