diff --git a/app/Http/Controllers/SquareCheckoutController.php b/app/Http/Controllers/SquareCheckoutController.php index 671458599..b517783b3 100644 --- a/app/Http/Controllers/SquareCheckoutController.php +++ b/app/Http/Controllers/SquareCheckoutController.php @@ -111,7 +111,8 @@ public function payTravel(Request $request) ->whereHas( 'travel', static function (Builder $query): void { - $query->whereIn('status', ['approved', 'complete']); + $query->whereIn('status', ['approved', 'complete']) + ->where('fee_amount', '>', 0); } ) ->unpaid() diff --git a/app/Mail/Travel/TravelAssignmentCreated.php b/app/Mail/Travel/TravelAssignmentCreated.php index e6afdb5e5..2af62f49b 100644 --- a/app/Mail/Travel/TravelAssignmentCreated.php +++ b/app/Mail/Travel/TravelAssignmentCreated.php @@ -49,7 +49,25 @@ public function build(): self private function subjectLineCallToAction(): string { - if ($this->assignment->needs_docusign || ! $this->assignment->user->has_emergency_contact_information) { + if ( + $this->assignment->needs_docusign && + $this->assignment->user->has_emergency_contact_information && + $this->assignment->travel->fee_amount === 0 + ) { + if ($this->assignment->travel->needs_airfare_form) { + if ($this->assignment->travel->needs_travel_information_form) { + return 'Forms'; + } else { + return 'Airfare request form'; + } + } else { + if ($this->assignment->travel->needs_travel_information_form) { + return 'Travel information form'; + } else { + return 'Form'; + } + } + } elseif ($this->assignment->needs_docusign || ! $this->assignment->user->has_emergency_contact_information) { return 'Action'; } else { return 'Payment'; diff --git a/app/Models/User.php b/app/Models/User.php index cbd70787a..819c4cd2a 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -1084,7 +1084,8 @@ public function getCurrentTravelAssignmentAttribute(): ?TravelAssignment ->whereHas( 'travel', static function (Builder $query): void { - $query->whereIn('status', ['approved', 'complete']); + $query->whereIn('status', ['approved', 'complete']) + ->where('fee_amount', '>', 0); } ) ->unpaid() diff --git a/app/Nova/Actions/ReviewTrip.php b/app/Nova/Actions/ReviewTrip.php index 71a49687a..8605a9f62 100644 --- a/app/Nova/Actions/ReviewTrip.php +++ b/app/Nova/Actions/ReviewTrip.php @@ -68,20 +68,35 @@ public function handle(ActionFields $fields, Collection $models): ActionResponse $trip->save(); if ($trip->needs_docusign) { - $trip->assignments->each(static function (TravelAssignment $assignment): void { - PrefetchSquareCheckoutLinkForTravelAssignment::dispatch($assignment) - ->chain([ - new SendDocuSignEnvelopeForTravelAssignment($assignment, true), - new SendTravelAssignmentCreatedNotification($assignment), - ]); - }); + if ($trip->fee_amount > 0) { + $trip->assignments->each(static function (TravelAssignment $assignment): void { + PrefetchSquareCheckoutLinkForTravelAssignment::dispatch($assignment) + ->chain([ + new SendDocuSignEnvelopeForTravelAssignment($assignment, true), + new SendTravelAssignmentCreatedNotification($assignment), + ]); + }); + } else { + $trip->assignments->each(static function (TravelAssignment $assignment): void { + SendDocuSignEnvelopeForTravelAssignment::dispatch($assignment, true) + ->chain([ + new SendTravelAssignmentCreatedNotification($assignment), + ]); + }); + } } else { - $trip->assignments->each(static function (TravelAssignment $assignment): void { - PrefetchSquareCheckoutLinkForTravelAssignment::dispatch($assignment) - ->chain([ - new SendTravelAssignmentCreatedNotification($assignment), - ]); - }); + if ($trip->fee_amount > 0) { + $trip->assignments->each(static function (TravelAssignment $assignment): void { + PrefetchSquareCheckoutLinkForTravelAssignment::dispatch($assignment) + ->chain([ + new SendTravelAssignmentCreatedNotification($assignment), + ]); + }); + } else { + $trip->assignments->each(static function (TravelAssignment $assignment): void { + SendTravelAssignmentCreatedNotification::dispatch($assignment); + }); + } } $trip->primaryContact->notify(new TravelApproved($trip)); diff --git a/app/Nova/Travel.php b/app/Nova/Travel.php index b492c5357..fe9fff978 100644 --- a/app/Nova/Travel.php +++ b/app/Nova/Travel.php @@ -21,7 +21,6 @@ use App\Rules\MatrixItineraryBusinessPolicy; use App\Util\DepartmentNumbers; use App\Util\DocuSign; -use App\Util\Matrix; use Carbon\Carbon; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Model; @@ -235,10 +234,10 @@ public function fields(Request $request): array ->rules( 'required', 'integer', - 'min:'.config('travelpolicy.minimum_trip_fee'), + 'min:0', 'max:'.config('travelpolicy.maximum_trip_fee') ) - ->min(config('travelpolicy.minimum_trip_fee')) + ->min(0) ->max(config('travelpolicy.maximum_trip_fee')), Currency::make( @@ -845,46 +844,6 @@ protected static function afterValidation(NovaRequest $request, $validator): voi ); } } - - $totalCost = intval($request->tar_lodging) + - intval($request->tar_registration) + - intval($request->car_rental_cost) + - intval($request->meal_per_diem); - - if ($request->resourceId !== null) { - $trip = \App\Models\Travel::where('id', '=', $request->resourceId)->sole(); - - $airfareCost = $trip->assignments->reduce( - static function (?float $carry, \App\Models\TravelAssignment $assignment): ?float { - $thisAirfareCost = Matrix::getHighestDisplayPrice($assignment->matrix_itinerary); - - if ($thisAirfareCost !== null && $carry !== null && $thisAirfareCost > $carry) { - return $thisAirfareCost; - } elseif ($thisAirfareCost !== null && $carry === null) { - return $thisAirfareCost; - } else { - return $carry; - } - } - ); - - if ($airfareCost !== null && $airfareCost > 0) { - $totalCost += $airfareCost; - } - } - - $feeAmount = intval($request->fee_amount); - - if ($totalCost === 0) { - return; - } - - if ($feeAmount / $totalCost < config('travelpolicy.minimum_trip_fee_cost_ratio')) { - $validator->errors()->add( - 'fee_amount', - trim(view('nova.help.travel.feevalidation', ['totalCost' => $totalCost])->render()) - ); - } } /** diff --git a/app/Nova/TravelAssignment.php b/app/Nova/TravelAssignment.php index 79bf19b16..6f6f56488 100644 --- a/app/Nova/TravelAssignment.php +++ b/app/Nova/TravelAssignment.php @@ -230,33 +230,13 @@ protected static function afterValidation(NovaRequest $request, $validator): voi $businessPolicy = new MatrixItineraryBusinessPolicy($trip->airfare_policy); - $businessPolicyPassed = true; - $businessPolicy->validate( 'matrix_itinerary', $request->matrix_itinerary, - static function (string $message) use ($validator, &$businessPolicyPassed): void { - $businessPolicyPassed = false; + static function (string $message) use ($validator): void { $validator->errors()->add('matrix_itinerary', $message); } ); - - if ($businessPolicyPassed) { - $airfare_cost = Matrix::getHighestDisplayPrice($request->matrix_itinerary); - - if ($airfare_cost === null) { - $validator->errors()->add('matrix_itinerary', 'Internal error determining price for itinerary'); - } - - $total_cost = $trip->tar_lodging + $trip->tar_registration + $airfare_cost; - - if ($trip->fee_amount / $total_cost < config('travelpolicy.minimum_trip_fee_cost_ratio')) { - $validator->errors()->add( - 'matrix_itinerary', - trim(view('nova.help.travel.assignment.feevalidation', ['totalCost' => $total_cost])->render()) - ); - } - } } /** diff --git a/phpstan.neon b/phpstan.neon index b480b77ca..1fd249f81 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -112,9 +112,6 @@ parameters: - '#Only booleans are allowed in a negated boolean, Illuminate\\Support\\Carbon\|null given\.#' - '#Only booleans are allowed in a negated boolean, mixed given\.#' - '#Only booleans are allowed in a ternary operator condition, Illuminate\\Support\\Carbon\|null given\.#' - - '#Only numeric types are allowed in \+, float\|null given on the right side\.#' - - '#Only numeric types are allowed in \+, int\|null given on the left side\.#' - - '#Only numeric types are allowed in \+, int\|null given on the right side\.#' - '#Parameter \#1 ...\$addresses of method Symfony\\Component\\Mime\\Email::replyTo\(\) expects string\|Symfony\\Component\\Mime\\Address, mixed given\.#' - '#Parameter \#1 \$[a-z_]+ of method DocuSign\\eSign\\Model\\[a-zA-Z]+::set[a-zA-Z]+\(\) expects string\|null, false given\.#' - '#Parameter \#1 \$[a-z_]+ of method DocuSign\\eSign\\Model\\[a-zA-Z]+::set[a-zA-Z]+\(\) expects string\|null, int given\.#' @@ -166,7 +163,6 @@ parameters: - '#Parameter \#1 \$id of static method Illuminate\\Database\\Eloquent\\Builder::findByIdentifier\(\) expects string, mixed given\.#' - '#Parameter \#1 \$idempotencyKey of static method Square\\Models\\Builders\\RefundPaymentRequestBuilder::init\(\) expects string, string\|null given\.#' - '#Parameter \#1 \$ids of method Illuminate\\Database\\Eloquent\\Relations\\BelongsToMany::sync\(\) expects array\|Illuminate\\Database\\Eloquent\\Model\|Illuminate\\Support\\Collection, mixed given\.#' - - '#Parameter \#1 \$itinerary of static method App\\Util\\Matrix::getHighestDisplayPrice\(\) expects array\|string\|null, mixed given\.#' - '#Parameter \#1 \$json of function json_decode expects string, mixed given\.#' - '#Parameter \#1 \$json of function json_decode expects string, string\|false given\.#' - '#Parameter \#1 \$locationId of class Square\\Models\\Order constructor expects string, mixed given\.#' diff --git a/resources/views/mail/travel/assignmentcreated.blade.php b/resources/views/mail/travel/assignmentcreated.blade.php index cd03efcd0..ecabe103e 100644 --- a/resources/views/mail/travel/assignmentcreated.blade.php +++ b/resources/views/mail/travel/assignmentcreated.blade.php @@ -4,13 +4,17 @@ You have been assigned to {{ $assignment->travel->name }}. Complete the following tasks as soon as possible so that we can book travel arrangements for you. Visit {{ route('docusign.travel') }} to submit {{ $assignment->travel->needs_airfare_form ? ($assignment->travel->needs_travel_information_form ? 'forms' : 'an airfare request form') : ($assignment->travel->needs_travel_information_form ? 'a travel information form' : '') }} for your trip. +@if($assignment->travel->fee_amount > 0) Make a ${{ intval($assignment->travel->fee_amount) }} payment for the trip fee. You can pay online with a credit or debit card at {{ route('pay.travel') }}. -@else +@endif +@elseif($assignment->travel->fee_amount > 0) You have been assigned to {{ $assignment->travel->name }}. Pay the ${{ intval($assignment->travel->fee_amount) }} trip fee as soon as possible{{ $assignment->travel->return_date > \Carbon\Carbon::now() ? ' so that we can book travel arrangements for you' : '' }}. You can pay online with a credit or debit card at {{ route('pay.travel') }}. @endif +@if($assignment->travel->fee_amount > 0) If you would prefer to pay by cash or check, make arrangements with {!! $assignment->travel->primaryContact->full_name !!}. Write checks to Georgia Tech, with RoboJackets on the memo line. Don't forget to sign it! +@endif @if(!$assignment->user->has_emergency_contact_information && $assignment->travel->return_date > \Carbon\Carbon::now()) You also need to add emergency contact information to your {{ config('app.name') }} profile at {{ route ('profile') }}. diff --git a/resources/views/nova/help/travel/feeamount.blade.php b/resources/views/nova/help/travel/feeamount.blade.php index a03f74331..64abf7534 100644 --- a/resources/views/nova/help/travel/feeamount.blade.php +++ b/resources/views/nova/help/travel/feeamount.blade.php @@ -1 +1 @@ -This is the amount that will be collected from each traveler. The trip fee must be at least {{ (config('travelpolicy.minimum_trip_fee_cost_ratio') * 100) }}% of the per-person cost for this trip. Online payments incur a ~4% processing fee paid by RoboJackets. +This is the amount that will be collected from each traveler. Online payments incur a ~4% processing fee paid by RoboJackets. diff --git a/routes/mailbook.php b/routes/mailbook.php index b6a5eebcf..a93aff3e6 100644 --- a/routes/mailbook.php +++ b/routes/mailbook.php @@ -1019,6 +1019,62 @@ Mailbook::to($user) ->add(TravelAssignmentCreated::class) ->label('Trip Assignment Created') + ->variant('Need Travel Information Form No Payment', static function (): TravelAssignmentCreated { + $user = User::withoutEvents(static function (): User { + $user = User::factory()->make([ + 'first_name' => 'George', + 'preferred_name' => null, + 'last_name' => 'Burdell', + 'gt_email' => 'george.burdell@gatech.edu', + 'primary_affiliation' => 'student', + 'emergency_contact_name' => 'asdf', + 'emergency_contact_phone' => 'asdf', + ]); + $user->save(); + + return $user; + }); + + $officer = User::withoutEvents(static function (): User { + $officer = User::factory()->make([ + 'first_name' => 'Robo', + 'preferred_name' => null, + 'last_name' => 'Buzz', + 'gt_email' => 'robo.buzz@gatech.edu', + ]); + $officer->save(); + + return $officer; + }); + + $travel = Travel::withoutEvents(static fn (): Travel => Travel::firstOrCreate([ + 'name' => 'Motorama 2022', + ], [ + 'destination' => 'mailbook', + 'departure_date' => '2022-02-18', + 'return_date' => '2022-02-21', + 'fee_amount' => 0, + 'forms' => [ + Travel::TRAVEL_INFORMATION_FORM_KEY => true, + ], + 'primary_contact_user_id' => $officer->id, + 'included_with_fee' => 'mailbook', + 'is_international' => false, + 'status' => 'draft', + ])); + + $assignment = TravelAssignment::withoutEvents(static function () use ($user, $travel): TravelAssignment { + $assignment = TravelAssignment::factory()->make([ + 'user_id' => $user->id, + 'travel_id' => $travel->id, + ]); + $assignment->save(); + + return $assignment; + }); + + return new TravelAssignmentCreated($assignment); + }) ->variant('Need Travel Information Form', static function (): TravelAssignmentCreated { $user = User::withoutEvents(static function (): User { $user = User::factory()->make([