-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1654 from uOttawa-Makerspace/feat/locker-management
Locker Management
- Loading branch information
Showing
41 changed files
with
1,133 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,146 @@ | ||
# frozen_string_literal: true | ||
|
||
class LockerRentalsController < ApplicationController | ||
before_action :current_user | ||
before_action :signed_in, except: %i[stripe_success stripe_cancelled] | ||
# Also sets @locker_rental | ||
before_action :check_permission, | ||
except: %i[index new stripe_success stripe_cancelled] | ||
|
||
def index | ||
# Only admin can see index list | ||
#redirect_to :new_locker_rental unless current_user.admin? | ||
|
||
@own_locker_rentals = current_user.locker_rentals | ||
@locker_types = LockerType.all | ||
@locker_rentals = | ||
LockerRental.includes(:locker_type, :rented_by).order( | ||
locker_type_id: :asc | ||
) | ||
end | ||
|
||
def show | ||
@stripe_checkout_session = | ||
Stripe::Checkout::Session.create( | ||
success_url: stripe_success_locker_rentals_url, | ||
cancel_url: stripe_cancelled_locker_rentals_url, | ||
mode: "payment", | ||
line_items: @locker_rental.locker_type.generate_line_items, | ||
billing_address_collection: "required", | ||
client_reference_id: "locker-rental-#{@locker_rental.id}" | ||
) | ||
end | ||
|
||
def new | ||
@locker_rental = LockerRental.new | ||
# Only locker types enabled by admin | ||
new_instance_attributes | ||
end | ||
|
||
def create | ||
@locker_rental = LockerRental.new(locker_rental_params) | ||
# if not admin member or has debug value set | ||
# then force to wait for admin approval | ||
if !current_user.admin? || params.dig(:locker_rental, :ask) | ||
@locker_rental.state = :reviewing | ||
@locker_rental.rented_by = current_user | ||
end | ||
|
||
if @locker_rental.save | ||
redirect_back fallback_location: :new_locker_rental | ||
else | ||
new_instance_attributes | ||
render :new, status: :unprocessable_entity | ||
end | ||
end | ||
|
||
def update | ||
@locker_rental = LockerRental.find(params[:id]) | ||
|
||
# NOTE move this line to model maybe? | ||
# if changing state to 'active' | ||
if locker_rental_params[:state] == "active" | ||
# default to end of semester | ||
@locker_rental.owned_until ||= end_of_this_semester | ||
# Get first available locker specifier if not set | ||
@locker_rental.locker_specifier ||= | ||
@locker_rental.locker_type.get_available_lockers.first | ||
end | ||
|
||
if @locker_rental.update(locker_rental_params) | ||
flash[:notice] = "Locker rental updated" | ||
else | ||
flash[:alert] = "Failed to update locker rental" + helpers.tag.br + | ||
@locker_rental.errors.full_messages.join(helpers.tag.br) | ||
end | ||
|
||
redirect_back fallback_location: :locker_rentals | ||
end | ||
|
||
def stripe_success | ||
end | ||
|
||
def stripe_cancelled | ||
end | ||
|
||
private | ||
|
||
def check_permission | ||
# If user gives a request id | ||
if params[:id].present? | ||
@locker_rental = LockerRental.find(params[:id]) | ||
# Allow if getting own locker rental | ||
return if @locker_rental.rented_by == current_user | ||
end | ||
# Always allow admin | ||
return if current_user.admin? | ||
|
||
# Else redirect to only page with no auth (new page takes no ID) | ||
redirect_to :new_locker_rental | ||
end | ||
|
||
def new_instance_attributes | ||
@available_locker_types = LockerType.available | ||
# Who users can request as | ||
# because we want to localize later | ||
# FIXME this is not used for anything, pretty useless | ||
@available_fors = { | ||
staff: ("CEED staff member" if current_user.staff?), | ||
student: ("GNG student" if current_user.student?), | ||
general: "community member" | ||
}.compact.invert | ||
# Don't allow new request if user already has an active or pending request | ||
@pending_locker_request = current_user.locker_rentals.under_review.first | ||
end | ||
|
||
def locker_rental_params | ||
if current_user.admin? && !params.dig(:locker_rental, :ask) | ||
admin_params = | ||
params.require(:locker_rental).permit( | ||
:locker_type_id, | ||
# admin can assign and approve requests | ||
:rented_by_id, | ||
:locker_specifier, | ||
:state, | ||
:owned_until, | ||
:notes | ||
) | ||
# FIXME replace that search with a different one, return ID instead | ||
# If username is given (since search can do that) | ||
rented_by_user = | ||
User.find_by(username: params.dig(:locker_rental, :rented_by_username)) | ||
if rented_by_user | ||
# then convert to user id | ||
admin_params.reverse_merge!(rented_by_id: rented_by_user.id) | ||
end | ||
return admin_params | ||
else | ||
# people pick where they want a locker | ||
# prevent user from editing rental notes after first submit | ||
params | ||
.require(:locker_rental) | ||
.permit(:locker_type_id) | ||
.tap { |p| p.permit(:notes) unless params[:id] } | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
class LockerTypesController < AdminAreaController | ||
def new | ||
@locker_type = LockerType.new | ||
end | ||
|
||
def create | ||
# the params might come with an :amount field, use that | ||
# to create locker instances | ||
@locker_type = LockerType.new(locker_type_params) | ||
if @locker_type.save | ||
flash[:notice] = "Locker type #{@locker_type.short_form} created" | ||
redirect_to lockers_path | ||
else | ||
render :new, status: :unprocessable_entity | ||
end | ||
end | ||
|
||
def edit | ||
@locker_type = LockerType.find(params[:id]) | ||
end | ||
|
||
def update | ||
@locker_type = LockerType.find(params[:id]) | ||
if @locker_type.update(locker_type_params) | ||
redirect_to lockers_path, notice: "Locker updated" | ||
else | ||
render :edit, status: :unprocessable_entity | ||
end | ||
end | ||
|
||
def destroy | ||
@locker_type = LockerType.find(params[:id]) | ||
if @locker_type.destroy | ||
flash[:notice] = "Locker type removed" | ||
else | ||
flash[ | ||
:alert | ||
] = "Failed to delete locker type, records probably exist in history" | ||
end | ||
redirect_to lockers_path | ||
end | ||
|
||
private | ||
|
||
def locker_type_params | ||
params.require(:locker_type).permit( | ||
:short_form, | ||
:description, | ||
:available, | ||
:available_for, | ||
:quantity, | ||
:cost | ||
) | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
class LockersController < AdminAreaController | ||
before_action :current_user | ||
before_action :signed_in | ||
|
||
before_action do | ||
unless current_user.admin? | ||
flash[:alert] = "You cannot access this area" | ||
redirect_back(fallback_location: root_path) | ||
end | ||
end | ||
|
||
helper_method :rental_state_icon | ||
|
||
def index | ||
@locker_types = LockerType.all | ||
@locker_requests_pending = LockerRental.under_review.take 5 | ||
|
||
# For the locker type form | ||
@locker_type = LockerType.new | ||
# For the locker rental form | ||
@locker_rental = LockerRental.new | ||
end | ||
|
||
private | ||
|
||
def rental_state_icon(state) | ||
case state | ||
when "active" | ||
"fa-lock" | ||
when "cancelled" | ||
"fa-clock-o text-danger" | ||
else | ||
"" | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
module LockerRentalsHelper | ||
def locker_rental_actions(rental, for_admin = true) | ||
end_rental_button = | ||
button_to( | ||
t("lockers.actions.cancel_rental"), | ||
locker_rental_path(rental), | ||
data: { | ||
confirm: "Are you sure you want to cancel this locker rental?" | ||
}, | ||
params: { | ||
locker_rental: { | ||
state: :cancelled | ||
} | ||
}, | ||
method: :put, | ||
class: "btn btn-danger" | ||
) | ||
ask_payment_button = | ||
button_to( | ||
"Send to checkout", | ||
locker_rental_path(rental), | ||
data: { | ||
confirm: "Are you sure you want to send request to checkout?" | ||
}, | ||
params: { | ||
locker_rental: { | ||
state: :await_payment | ||
} | ||
}, | ||
method: :put, | ||
class: "btn btn-success" | ||
) | ||
instant_approve_button = | ||
button_to( | ||
"Assign now", | ||
locker_rental_path(rental), | ||
data: { | ||
confirm: "Are you sure you want to assign locker immediately?" | ||
}, | ||
params: { | ||
locker_rental: { | ||
state: :active | ||
} | ||
}, | ||
method: :put, | ||
class: "btn btn-info" | ||
) | ||
|
||
return end_rental_button unless for_admin | ||
|
||
case rental.state.to_sym | ||
when :reviewing | ||
tag.div class: "btn-group" do | ||
ask_payment_button + instant_approve_button + end_rental_button | ||
end | ||
when :await_payment | ||
tag.div class: "btn-group" do | ||
instant_approve_button + end_rental_button | ||
end | ||
when :active | ||
tag.div class: "btn-group" do | ||
end_rental_button | ||
end | ||
#when :cancelled | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
import TomSelect from "tom-select"; | ||
|
||
const locker_specifier = new TomSelect("#locker_rental_locker_specifier", { | ||
searchPlaceholder: "Select locker...", | ||
// Hide options if not selected by locker_type | ||
render: { | ||
optgroup: function (data) { | ||
let optgroup = document.createElement("div"); | ||
optgroup.className = "optgroup"; | ||
optgroup.appendChild(data.options); | ||
return data.group.disabled == true ? null : optgroup; | ||
}, | ||
}, | ||
}); | ||
|
||
// Add the disabled and hidden attribute to all optgroups | ||
// except for selected type | ||
function disableAllExcept(shortForm) { | ||
locker_specifier.input.querySelectorAll("optgroup").forEach((optgroup) => { | ||
optgroup.disabled = !(optgroup.label == shortForm); | ||
optgroup.hidden = !(optgroup.label == shortForm); | ||
}); | ||
|
||
// Some options are now removed, reselect new value | ||
try { | ||
let newVal = locker_specifier.input.querySelector( | ||
`optgroup[label="${shortForm}"]` | ||
).children[0].value; | ||
locker_specifier.setValue(newVal); | ||
} catch (e) {} | ||
// re-render | ||
locker_specifier.sync(); | ||
} | ||
|
||
const locker_type = new TomSelect("#locker_rental_locker_type_id", { | ||
onChange: (value) => { | ||
disableAllExcept(locker_type.options[value].text); | ||
}, | ||
}); | ||
|
||
locker_type.trigger("change", locker_type.getValue()); | ||
// window.locker_type = locker_type | ||
window.locker_specifier = locker_specifier; |
Oops, something went wrong.