Skip to content

Commit

Permalink
Merge pull request #4499 from RoboJackets/kristaps/free-trips
Browse files Browse the repository at this point in the history
Allow $0 trip fees
  • Loading branch information
kberzinch authored Mar 21, 2024
2 parents ed359ac + ae963d4 commit eadffc9
Show file tree
Hide file tree
Showing 10 changed files with 116 additions and 86 deletions.
3 changes: 2 additions & 1 deletion app/Http/Controllers/SquareCheckoutController.php
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
20 changes: 19 additions & 1 deletion app/Mail/Travel/TravelAssignmentCreated.php
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
3 changes: 2 additions & 1 deletion app/Models/User.php
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
41 changes: 28 additions & 13 deletions app/Nova/Actions/ReviewTrip.php
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down
45 changes: 2 additions & 43 deletions app/Nova/Travel.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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())
);
}
}

/**
Expand Down
22 changes: 1 addition & 21 deletions app/Nova/TravelAssignment.php
Original file line number Diff line number Diff line change
Expand Up @@ -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())
);
}
}
}

/**
Expand Down
4 changes: 0 additions & 4 deletions phpstan.neon
Original file line number Diff line number Diff line change
Expand Up @@ -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\.#'
Expand Down Expand Up @@ -166,7 +163,6 @@ parameters:
- '#Parameter \#1 \$id of static method Illuminate\\Database\\Eloquent\\Builder<App\\Models\\User>::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<Illuminate\\Database\\Eloquent\\Model>::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\.#'
Expand Down
6 changes: 5 additions & 1 deletion resources/views/mail/travel/assignmentcreated.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -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') }}.
Expand Down
2 changes: 1 addition & 1 deletion resources/views/nova/help/travel/feeamount.blade.php
Original file line number Diff line number Diff line change
@@ -1 +1 @@
This is the amount that will be collected from <strong>each</strong> 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 <a href="https://squareup.com/us/en/payments/our-fees">~4% processing fee</a> paid by RoboJackets.
This is the amount that will be collected from <strong>each</strong> traveler. Online payments incur a <a href="https://squareup.com/us/en/payments/our-fees">~4% processing fee</a> paid by RoboJackets.
56 changes: 56 additions & 0 deletions routes/mailbook.php
Original file line number Diff line number Diff line change
Expand Up @@ -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' => '[email protected]',
'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' => '[email protected]',
]);
$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([
Expand Down

0 comments on commit eadffc9

Please sign in to comment.