Skip to content

Instantly share code, notes, and snippets.

@aguestuser
Last active August 29, 2015 14:06
Show Gist options
  • Save aguestuser/22d2bff3003059f304c5 to your computer and use it in GitHub Desktop.
Save aguestuser/22d2bff3003059f304c5 to your computer and use it in GitHub Desktop.
Routine for batch shift reassignment with automated scheduling obstacle detection (and override) for BK Shift on Rails
# *********************
# *****CONTROLLERS*****
# *********************
# *************************************
# SHIFTS CONTROLLER
# app/controllers/shifts_controller.rb
# *************************************
def batch_edit
if params[:ids]
load_shift_batch # loads @shifts
route_batch_edit params[:commit]
else
flash[:error] = "Oops! Looks like you didn't select any shifts to batch edit."
load_shifts
load_table
render 'index'
end
end
def route_batch_edit commit
query = params.extract!(:ids, :base_path).to_query
case commit
when 'Batch Edit'
@errors = []
render 'batch_edit'
when 'Batch Assign'
redirect_to "/assignment/batch_edit?#{query}"
when 'Uniform Assign'
redirect_to "/assignment/batch_edit_uniform?#{query}"
end
end
def batch_update
old_shifts = old_shifts_from params # loads @shifts
new_shifts = new_shifts_from params
@errors = Shift.batch_update(old_shifts, new_shifts)
if @errors.empty?
flash[:success] = "Shifts successfully batch edited"
redirect_to @base_path
else
render "batch_edit"
end
end
# *****************************
# ASSIGNMENTS CONTROLLER
# app/controllers/assignments_controller.rb
# *****************************
#public
def batch_edit
@errors = []
load_shift_batch # loads @shifts (Arr of Shifts)
load_assignment_batch #loads @assignments (Assignments Obj)
render 'batch_edit'
end
def batch_update
assignments = Assignments.from_params params[:wrapped_assignments] #
get_savable assignments
end
def batch_edit_uniform
@errors = []
load_shift_batch # loads @shifts (Arr of Shifts)
load_assignment_batch #loads @assignments (Assignments Obj)
render 'batch_edit_uniform'
end
def batch_update_uniform
assignments = Assignments.new(
{
fresh: new_uniform_assignments_from(params),
old: old_uniform_assignments_from(params)
}
)
get_savable assignments
end
# GET SAVABLE RECURSION CASE 1
def resolve_obstacles
decisions = Assignments.decisions_from params[:decisions]
assignments = Assignments.from_params( JSON.parse(params[:assignments_json] ) )
assignments = assignments.resolve_obstacles_with decisions
get_savable assignments # RECURSE
end
# GET SAVABLE RECURSION CASE 2
def batch_reassign
assignments = Assignments.from_params params[:wrapped_assignments]
get_savable assignments # RECURSE
end
#private
# *** BATCH SAVE ROUTINE ***
def get_savable assignments # RECURSION HOOK
#input: Assignments Obj
#output: Assignments Obj w/ empty .with_obstacles and .requiring_reassignment Arrays
assignments = assignments.find_obstacles if assignments.fresh.any?
if assignments.with_obstacles.any?
request_obstacle_decisions_for assignments # WILL RECURSE
nil
elsif assignments.requiring_reassignment.any?
request_reassignments_for assignments # WILL RECURSE
nil
else # BASE CASE (BREAKS RECURSION)
old_assignments = assignments.unwrap_old
new_assignments = assignments.savable
update_savable old_assignments, new_assignments
end
end
# RECURSION CASE 1
def request_obstacle_decisions_for assignments
@assignments = assignments
render "resolve_obstacles" # view posts to '/assignment/resolve_obstacles'
end
# RECURSION CASE 2
def request_reassignments_for assignments
@errors = []
@assignments = assignments
render "batch_reassign" # view posts to '/assignment/batch_reassign'
end
# *** SAVE HOOK ***
def update_savable old_assignments, new_assignments
now = now_unless_test
new_assignments.each{ |a| a.shift.refresh_urgency now } # will update weekly shifts to emergency and extra as appropriate
if batch_save? old_assignments, new_assignments
message = success_message_from old_assignments.count
email_alert = send_batch_emails new_assignments, old_assignments, current_account
flash[:success] = message << email_alert
redirect_to @base_path
else
request_batch_error_fixes old_assignments, new_assignments
end
end
def batch_save? old_assignments, new_assignments
@errors = Assignment.batch_update(old_assignments, new_assignments)
@errors.empty?
end
def success_message_from count
if count > 1
"Assignments successfully batch edited"
else
"Assignment successfully edited"
end
end
def send_batch_emails new_assignments, old_assignments, current_account
email_count = Assignment.send_emails new_assignments, old_assignments, current_account
if email_count == 0
""
else
" -- #{email_count} emails sent"
end
end
def request_batch_error_fixes old_assignments, new_assignments
@assignments = Assignments.new({
fresh: Assignments.wrap(new_assignments),
})
render "batch_edit"
end
# BATCH PARAM PARSERS
def load_shift_batch
@shifts = Shift.where("id IN (:ids)", { ids: params[:ids] } ).order(:start).to_a
end
def load_assignment_batch
assignments = @shifts.map(&:assignment)
wrapped_assignments = Assignments.wrap assignments
@assignments = Assignments.new( { fresh: wrapped_assignments } )
end
def old_assignments_from new_assignments
if params[:old_assignments_json]
load_old_assignments
else
new_assignments.map{ |ass| Assignment.find(ass.id) }
end
end
def load_old_assignments
Assignments.from_params JSON.parse(params[:old_assignments_json])
end
def new_uniform_assignments_from params
attrs = Assignment.attributes_from(params[:assignment]) # Hash
shift_ids = params[:shift_ids].map(&:to_i) # Array
assignments = shift_ids.map do |shift_id|
attrs['shift_id'] = shift_id
Assignment.new(attrs)
end
Assignments.wrap(assignments)
end
def old_uniform_assignments_from params
assignments = params[:shift_ids].map do |shift_id|
attrs = Shift.find(shift_id).assignment.attributes #.reject{ |k,v| k == 'id' }
Assignment.new(attrs)
end
Assignments.wrap(assignments)
end
# *******************************
# ***** CONTROLLER HELPERS ******
# *******************************
# *********************
# ASSIGNMENTS CLASS
# app/helpers/assignments.rb
# ***************************
class Assignments
include Hashable
attr_accessor :fresh, :old, :with_conflicts, :with_double_bookings, :with_obstacles, :without_obstacles, :requiring_reassignment
def initialize options={}
@old = options[:old] || options[:fresh].clone # Array of WrappedAssignments
# NOTE: above will clone fresh options on first iteration, retain initial value of @old on subsequent (recursive) iterations
@fresh = options[:fresh] || [] # Array of WrapedAssignments
@with_conflicts = options[:with_conflicts] || [] # Arr of WrapedAssignments
@with_double_bookings = options[:with_double_bookings] || [] # Arr of WrapedAssignments
@without_obstacles = options[:without_obstacles] || [] # Arr of WrapedAssignments
@requiring_reassignment = options[:requiring_reassignment] || [] #Arr of WrapedAssignments
end
def with_obstacles
@with_conflicts + @with_double_bookings
end
def find_obstacles
#input: @fresh (implicit - must be loaded) Arr of Assignments
#does:
# sorts assignments from fresh into 3 Arrays:
# (1) @with_conflicts: Arr of Assignments with conflicts
# (2) @with_double_bookings: Arr of Assignmens with double bookings
# (3) @without_obstacles: Arr of Assignments with neither conflicts nor double bookings
# clears @fresh
# output: Assignments Obj
@fresh.each do |wrapped_assignment|
assignment = wrapped_assignment.assignment
if assignment.conflicts.any?
@with_conflicts.push wrapped_assignment
elsif assignment.double_bookings.any?
@with_double_bookings.push wrapped_assignment
else
@without_obstacles.push wrapped_assignment
end
end
@fresh = []
self
end
def resolve_obstacles_with decisions
#input: @with_conflicts (implicit) Array of Assignments, @with_double_bookings (implicit) Array of Assignments
#does:
# builds array of assignments with obstacles
# based on user decisions, sorts them into either
# (1) assignments @requiring_reassignment
# (2) assignments @without_obstacles (after clearing obstacles from assignment object)
# clears @with_conflicts, @with_double_bookings, returns new state of Assignments Object
with_obstacles = self.with_obstacles
with_obstacles.each_with_index do |wrapped_assignment, i|
case decisions[i]
when 'Accept' # move to @requiring_reassignment
self.requiring_reassignment.push wrapped_assignment
when 'Override' # resolve obstacle and move to @without_obstacles
wrapped_assignment.assignment.resolve_obstacle
self.without_obstacles.push wrapped_assignment
end
end
self.with_conflicts = []
self.with_double_bookings = []
self
end
def savable
#input: self (implicit) Assignments Obj, @without_obstacles (implicit) Array of WrappedAssignments
#does: restores Arr of WrappedAssignments without obstacles to original sort and returns unwrapped Arr of Assignments
#output: Array of Assignments
savable = @without_obstacles.sort_by{ |wrapped_assignment| wrapped_assignment.index }
savable.map(&:assignment)
end
def unwrap_old
#input: self (implicit), @old (implicit) Array of WrappedAssignments
#output: Array of Assignments
@old.map(&:assignment)
end
# def to_params
# self.to_json.to_query 'assignments'
# end
# CLASS METHODS
def Assignments.wrap assignments
assignments.each_with_index.map { |assignment, i| WrappedAssignment.new(assignment, i) }
end
def Assignments.wrap_with_indexes assignments, indexes
assignments.each_with_index.map { |assignment, i| WrappedAssignment.new(assignment, indexes[i]) }
end
def Assignments.from_params param_hash
#input Hash of type
# { 'fresh': [
# {
# id: Num,
# assignment:{
# 'id': Num,
# 'rider_id': Num,
# 'shift_id': Num,
# ...(other Assignment attributes)
# }
# }
# 'id': Num
# ],
# 'old': [
# {
# 'id': Num,
# 'assignment':{
# ...(Assignment attributes)...
# }
# }
# ].... (other Arrays of WrappedAssignment attributes)
# }
#does: parses params hash into WrappedAssignments that can be passed as options to initialize an Assignments object
#output: Assignments Obj
options = {}
param_hash.each do |key, wrapped_attr_arr|
index_arr = wrapped_attr_arr.map{ |wrapped_attrs| wrapped_attrs['index'] }
attr_arr = wrapped_attr_arr.map{ |wrapped_attrs| wrapped_attrs['assignment'] }
assignments = attr_arr.map{ |attrs| Assignment.new(attrs) }
options[key.to_sym] = Assignments.wrap_with_indexes assignments, index_arr
end
Assignments.new(options)
end
def Assignments.decisions_from params
#input params[:decisions] (must be present)
decisions = []
params.each { |k,v| decisions[k.to_i] = v }
decisions
end
class WrappedAssignment
attr_accessor :assignment, :index
def initialize assignment, index
@assignment = assignment
@index = index
end
end
end
# ******************************************
# RIDER-SHIFTS CLASS
# app/helpers/rider_shifts.rb
# ************************************************
class RiderShifts
attr_reader :hash
URGENCIES = [ :emergency, :extra, :weekly ]
def initialize assignments
@hash = hash_from assignments
# puts ">>>> @hash"
# pp @hash
end
private
def hash_from assignments
#input: Arr of assignments
#output: Hash of Hashes of type:
# { Num<rider_id>:
# { rider: Rider,
# emergency_ shifts: {
# shifts: Arr of Shifts,
# restaurants: Arr of Restaurants
# }
# extra_shifts: {
# shifts: Arr of Shifts
# restaurants: Arr of Restaurants
# }
# }
grouped_by_rider = group_by_rider assignments
with_parsed_rider_and_shift = parse_rider_and_shifts grouped_by_rider
grouped_by_urgency = group_by_urgency with_parsed_rider_and_shift
with_restaurants = insert_restaurants grouped_by_urgency
# sorted_by_date = sort_by_date grouped_by_urgency
# with_restaurants = insert_restaurants sorted_by_date
end
def group_by_rider assignments
#input: Array of type: [ Assignment, Assignment, ...]
#output: Hash of type: { Num(rider_id): Arr of Assignments }
assignments.group_by{ |a| a.rider.id }
end
def parse_rider_and_shifts assignments
#input: Hash of type: { Num(rider_id): Arr of Assignments }
#output: Hash of Hashes of type: { Num<rider_id>: { rider: Rider, shifts: Arr of Shifts } }
hash = {}
assignments.each do |id,assignments|
hash[id] = { rider: assignments.first.rider, shifts: assignments.map(&:shift) }
end
hash
end
def group_by_urgency assignments
#input: Hash of Hashes of type: { Num<rider_id>: { rider: Rider, shifts: Arr of Shifts } }
#output: Hash of Hashes of type:
# { Num<rider_id>:
# { rider: Rider, emergency: Arr of Shifts, extra: Arr of Shifts, weekly: Arr of Shifts }
# }
hash = {}
assignments.each do |id, rider_hash|
sorted_hash = rider_hash[:shifts].group_by{ |s| s.urgency.text.downcase.to_sym }
hash[id] = { rider: rider_hash[:rider] }
URGENCIES.each { |urgency| hash[id][urgency] = sorted_hash[urgency] }
end
hash
end
def sort_by_date assignments
hash = {}
assignments.each do |id, rider_hash|
URGENCIES.each do |urgency|
rider_hash[urgency].sort_by!{ |shift| shift.start } if rider_hash[urgency]
end
end
hash
end
def insert_restaurants assignments
#input: Hash of Hashes of type:
# { Num<rider_id>:
# { rider: Rider, emergency: Arr of Shifts, extra: Arr of Shifts }
# }
#output: Hash of Hashes of type:
# { Num<rider_id>:
# { rider: Rider,
# emergency_ shifts: {
# shifts: Arr of Shifts,
# restaurants: Arr of Restaurants
# }
# extra_shifts: {
# shifts: Arr of Shifts
# restaurants: Arr of Restaurants
# }
# }
hash = {}
assignments.each do |id, rider_hash|
hash[id] = { rider: rider_hash[:rider] }
URGENCIES.each do |urgency|
shifts = rider_hash[urgency] || []
restaurants = parse_restaurants shifts
hash[id][urgency] = urgency_hash_from shifts, restaurants
end
end
hash
end
def urgency_hash_from shifts, restaurants
{ shifts: shifts , restaurants: restaurants }
end
def parse_restaurants shifts
shifts.map{ |shift| shift.restaurant }.uniq
end
end
# *******************************
# ******** MAILER HELPERS *******
# *******************************
# ******************************************
# DELEGATION EMAIL HELPER
# app/helpers/delegation_email_helper.rb
# ******************************************
class DelegationEmailHelper
attr_accessor :subject, :offering, :confirmation_request
def initialize shifts, type
plural = shifts.count > 1
adj = type.to_s
noun = noun_from type, plural
@subject = subject_from adj, noun, shifts, type
@offering = offering_from adj, noun, type
@confirmation_request = conf_req_from noun, type
end
private
def noun_from type, plural
str = type == :weekly ? "schedule" : "shift"
str << "s" if plural && type != :weekly
str
end
def subject_from adj, noun, shifts, type
"[" << adj.upcase << " " << noun.upcase << "] " << shift_descr_from(shifts, type)
end
def shift_descr_from shifts, type
case type
when :weekly
"-- PLEASE CONFIRM BY SUNDAY"
when :extra
'-- CONFIRMATION REQUIRED'
when :emergency
"-- SHIFT DETAILS ENCLOSED"
end
end
def offering_from adj, noun, type
offer_prefix = offer_prefix_from type
"#{offer_prefix} #{adj} #{noun}:"
end
def offer_prefix_from type
if type == :emergency
"As per our conversation, you are confirmed for the following"
else
"We'd like to offer you the following"
end
end
def conf_req_from noun, type
if type == :emergency
"Have a great shift!"
else
conf_time = conf_time_from type
"Please confirm whether you can work the #{noun} by #{conf_time}"
end
end
def conf_time_from type
type == :weekly ? "12pm this Sunday" : "2pm tomorrow"
end
end
# **************************
# RIDER MAILER
# app/mailers/rider_mailer.rb
class RiderMailer < ActionMailer::Base
default from: "brooklynshift@gmail.com"
helper_method :protect_against_forgery?
def delegation_email rider, shifts, restaurants, account, type
require 'delegation_email_helper'
@rider = rider
@shifts = shifts
@restaurants = restaurants
@staffer = account.user #, staffer_from account
helper = DelegationEmailHelper.new shifts, type
@salutation = "Dear #{rider.first_name}:"
@offering = helper.offering
@confirmation_request = helper.confirmation_request
mail(to: rider.email, subject: helper.subject)
end
# ...
private
def protect_against_forgery?
false
end
end
/ ********************************
/ ****** MAILER TEMPLATES *******
/ ********************************
/ ****************************************
/ RIDER MAILER LAYOUT
/ app/views/layouts/rider_mailer.html.haml
/ ****************************************
!!!
%html
%head
%meta{:content => "text/html; charset=UTF-8", "http-equiv" => "Content-Type"}/
:css
table {
border-collapse:collapse;
margin-left: 2em;
}
th {
background-color: lightgray;
}
th, td {
border: 1px solid black;
margin: 0px;
padding: .5em;
}
.underline {
text-decoration: underline;
}
%body
%p
= @salutation
= yield
/ ****************************************
/ DELEGATION EMAIL TEMPLATE
/ app/views/layouts/rider_mailer.html.haml
/ ****************************************
%p
= @offering
%table
%tr
%th
Time
%th
Restaurant
- @shifts.each do |shift|
%tr
%td
= shift.table_time
%td
= shift.restaurant.name
%p
= @confirmation_request
= render 'mailer/signature'
= render 'briefs'
= render 'reminders'
/ ****************************************
/ SIGNATURE PARTIAL
/ app/views/mailer/_signature.html.haml
/ ****************************************
- #Args: @staffer
%p
%p
Best,
%p
= @staffer.name
%br/
= @staffer.title
%br/
BK Shift, LLC
%br/
= @staffer.phone
%br/
= mail_to @staffer.email
/ ****************************************
/ BRIEFS PARTIAL
/ app/views/rider_mailer/_briefs.html.haml
/ ****************************************
%strong.underline
Restaurant Briefs:
- @shifts.each do |shift|
- r = shift.restaurant
%p
%strong
#{r.name}:
= r.brief
%br/
%strong
Location:
= r.location.full_address
/ ****************************************
/ REMINDERS PARTIAL
/ app/views/rider_mailer/_reminders.html.haml
/ ****************************************
%strong.underline
Reminders:
%ul
%li
Don’t forget to text 347-460-6484 2 hrs before your shift
%li
Please arrive 15 minutes before your scheduled shift
%li
Please note that the DOT requires the use of helmets, front white light, back red light and a bell and/or whistle.
# *******************
# ***** MODELS ******
# *******************
# ************************
# SHIFT MODEL
# app/models/shift.rb
# ************************
# == Schema Information
#
# Table name: shifts
#
# id :integer not null, primary key
# restaurant_id :integer
# start :datetime
# end :datetime
# period :string(255)
# urgency :string(255)
# billing_rate :string(255)
# notes :text
# created_at :datetime
# updated_at :datetime
#
class Shift < ActiveRecord::Base
include Timeboxable, BatchUpdatable
belongs_to :restaurant
has_one :assignment, dependent: :destroy #inverse_of: :shift
accepts_nested_attributes_for :assignment
classy_enum_attr :billing_rate
classy_enum_attr :urgency
validates :restaurant_id, :billing_rate, :urgency,
presence: true
def build_associations
self.assignment = Assignment.new
end
def rider
self.assignment.rider
end
def assigned? #output: bool
!self.assignment.rider.nil?
end
def assign_to(rider, status=:proposed)
#input: Rider, AssignmentStatus(Symbol)
#output: self.Assignment
params = { rider_id: rider.id, status: status }
if self.assigned?
self.assignment.update params
else
self.assignment = Assignment.create! params
end
end
def unassign
self.assignment.update(rider_id: nil, status: :unassigned) if self.assigned?
end
def conflicts_with? conflict
( conflict.end >= self.end && conflict.start < self.end ) ||
( conflict.start <= self.start && conflict.end > self.start )
# ie: if the conflict under examination overlaps with this conflict
end
def double_books_with? shift
( shift.end >= self.end && shift.start < self.end ) ||
( shift.start <= self.start && shift.end > self.start )
# ie: if the shift under examination overlaps with this shift
end
def refresh_urgency now
#input self (implicit), DateTime Obj
#side-effects: updates shift's urgency attribute
#output: self
start = self.start
send_urgency( parse_urgency( now, start ) ) if start > now
self
end
private
def parse_urgency now, start
#input: Datetime, Datetime
#output: Symbol
next_week = start.beginning_of_week != now.beginning_of_week
time_gap = start - now
if next_week
:weekly
elsif time_gap <= 36.hours
:emergency
else
:extra
end
end
def send_urgency urgency
#input: Symbol
self.update(urgency: urgency)
end
end
# ************************
# RIDER MODEL
# app/models/rider.rb
# ************************
# == Schema Information
#
# Table name: riders
#
# id :integer not null, primary key
# active :boolean
# created_at :datetime
# updated_at :datetime
#
class Rider < ActiveRecord::Base
include User, Contactable, Equipable, Locatable # app/models/concerns/
#nested attributes
has_one :qualification_set, dependent: :destroy
accepts_nested_attributes_for :qualification_set
has_one :skill_set, dependent: :destroy
accepts_nested_attributes_for :skill_set
has_one :rider_rating, dependent: :destroy
accepts_nested_attributes_for :rider_rating
#associations
has_many :assignments
has_many :shifts, through: :assignments
has_many :conflicts
validates :active, inclusion: { in: [ true, false ] }
scope :testy, -> { joins(:contact).where("contacts.email = ?", "bkshifttester@gmail.com").first }
scope :active, -> { joins(:contact).where(active: true).order("contacts.name asc") }
scope :inactive, -> { joins(:contact).where(active: false).order("contacts.name asc") }
#public methods
def name
self.contact.name
end
def shifts_on(date) #input: date obj, #output Arr of Assignments (possibly empty)
self.shifts.where( start: (date.beginning_of_day..date.end_of_day) )
end
def conflicts_on(date) #input: date obj, #output Arr of Conflicts (possibly empty)
self.conflicts.where( start: (date.beginning_of_day..date.end_of_day) )
end
def conflicts_between start_t, end_t
#input: Rider(self/implicit), Datetiem, Datetime
#does: builds an array of conflicts belonging to rider within date range btw/ start_t and end_t
#output: Arr
conflicts = self.conflicts
.where( "start > :start AND start < :end", { start: start_t, :end => end_t } )
.order("start asc")
end
#class methods
def Rider.select_options
Rider.all.joins(:contact).order("contacts.name asc").map{ |r| [ r.name, r.id ] }
end
def Rider.email_conflict_requests rider_conflicts, week_start, account
#input: RiderConflicts, Datetime, Account
#output: Str (empty if no emails sent, email alert if emails sent)
count = 0
rider_conflicts.arr.each do |hash|
RiderMailer.request_conflicts(hash[:rider], hash[:conflicts], week_start, account).deliver
count += 1
end
alert = count > 0 ? "#{count} conflict requests successfully sent" : ""
end
end
# ************************
# ASSIGNMENT MODEL
# app/models/rider.rb
# ************************
# == Schema Information
#
# Table name: assignments
#
# id :integer not null, primary key
# shift_id :integer
# rider_id :integer
# status :string(255)
# created_at :datetime
# updated_at :datetime
# override_conflict :boolean
# override_double_booking :boolean
#
class Assignment < ActiveRecord::Base
include BatchUpdatable
belongs_to :shift #, inverse_of: :assignment
belongs_to :rider
classy_enum_attr :status, allow_nil: true, enum: 'AssignmentStatus'
before_validation :set_status, if: :status_nil?
validates :status, presence: true
validate :no_emergency_shift_delegation
#instance methods
def no_emergency_shift_delegation
if self.shift
if self.shift.urgency == :emergency
errors.add(:base, 'Emergency shifts cannot be delegated') unless self.status != :delegated
end
end
end
def conflicts
#input: self (implicit)
#output: Arr of Conflicts
if self.rider.nil?
[]
else
rider_conflicts = get_rider_conflicts
rider_conflicts.select { |conflict| self.shift.conflicts_with? conflict }
end
end
def double_bookings
rider_shifts = get_rider_shifts
if self.rider.nil?
[]
else
rider_shifts.select { |shift| self.shift.double_books_with? shift }
end
end
def resolve_obstacle
self.conflicts.each(&:destroy) if self.conflicts.any?
self
end
def save_success_message
self.rider.nil? ? "Assignment updated (currently unassigned)." : "Assignment updated (Rider: #{self.rider.contact.name}, Status: #{self.status.text})"
end
def try_send_email old_assignment, sender_account
if self.status == :delegated && ( old_assignment.status != :delegated || old_assignment.rider != self.rider )
send_email_from sender_account
true
else
false
end
end
#Class Methods
def Assignment.send_emails new_assignments, old_assignments, sender_account
#input: assignments <Arr of Assignments>, old_assignments <Arr of Assignments>, Account
#does:
# (1) constructs array of newly delegated shifts
# (2) parses list of shifts into sublists for each rider
# (3) parses list of shifts for restaurants
# (4) [ SIDE EFFECT ] sends batch shift delegation email to each rider using params built through (1), (2), and (3)
#output: Int (count of emails sent)
# delegations = Assignment.delegations_from new_assignments, old_assignments # (1)
# rider_shifts = RiderShifts.new(delegations).hash #(2), (3)
emailable_shifts = Assignment.emailable new_assignments, old_assignments
rider_shifts = RiderShifts.new(emailable_shifts).hash #(2), (3)
count = 0
rider_shifts.values.each do |rider_hash| # (4)
[:emergency, :extra, :weekly].each do |urgency|
if rider_hash[urgency][:shifts].any?
Assignment.send_email rider_hash, urgency, sender_account
count += 1
end
end
end
count
end
def Assignment.send_email rider_hash, urgency, sender_account
RiderMailer.delegation_email(
rider_hash[:rider],
rider_hash[urgency][:shifts],
rider_hash[urgency][:restaurants],
sender_account,
urgency
).deliver
end
def Assignment.delegations_from new_assignments, old_assignments
#input: Arr of Assignments, Arr of Assignments
#does: builds array of assignments that were newly delegated when being updated from second argument to first
#output: Arr of Assignments
new_assignments.select.with_index do |a, i|
a.status == :delegated && ( old_assignments[i].status != :delegated || old_assignments[i].rider != a.rider )
end
end
def Assignment.emailable new_assignments, old_assignments
#input: Arr of Assignments, Arr of Assignments
#does: builds array of assignments that were newly delegated when being updated from second argument to first
#output: Arr of Assignments
# raise ( "NEW ASSIGNMENTS: " + new_assignments.inspect + "OLD ASSIGNMENTS: " + old_assignments.inspect )
new_assignments.select.with_index do |a, i|
if a.status == :delegated
old_assignments[i].status != :delegated || old_assignments[i].rider != a.rider
elsif a.status == :confirmed
# raise ( old_assignments[i].rider != a.rider ).inspect
val = ( a.shift.urgency == :emergency && ( old_assignments[i].status != :confirmed || old_assignments[i].rider != a.rider ) )
# raise val.inspect
else
false
end
# a.status == :delegated && ( old_assignments[i].status != :delegated || old_assignments[i].rider != a.rider ) ||
# a.status == :confirmed && ( old_assignments[i].status != :confirmed || old_assignments[i].rider != a.rider )
end
end
private
#instance method helpers
def status_nil?
self.status.nil?
end
def set_status
self.status = :unassigned
end
def get_rider_conflicts
self.rider.conflicts_on self.shift.start
end
def get_rider_shifts
if self.rider
self.rider.shifts_on(self.shift.start).reject{ |s| s.id == self.shift.id }
else
[]
end
end
def send_email_from sender_account
RiderMailer.delegation_email(self.rider, [ self.shift ], [ self.shift.restaurant ], sender_account).deliver
end
end
# **********************
# ROUTES
# app/config/routes.rb
# **********************
BksOnRails::Application.routes.draw do
# ...
get 'shift/batch_edit' => 'shifts#batch_edit'
post 'shift/batch_edit' => 'shifts#batch_update'
get 'assignment/batch_edit' => 'assignments#batch_edit'
post 'assignment/batch_edit' => 'assignments#batch_update'
get 'assignment/batch_edit_uniform' => 'assignments#batch_edit_uniform'
post 'assignment/batch_edit_uniform' => 'assignments#batch_update_uniform'
get 'assignment/resolve_obstacles' => 'assignments#request_obstacle_decisions'
post 'assignment/resolve_obstacles' => 'assignments#resolve_obstacles'
post 'assignment/batch_reassign' => 'assignments#batch_reassign'
# ...
end
# *********************************
# ********** SPEC MACROS **********
# *********************************
# ************************************************
# BATCH ASSIGNMENT SPEC MACROS
# app/spec/support/shift_request_macros.rb
# ************************************************
module ShiftRequestMacros
# ...
def filter_shifts_by_time_inclusively
#set start filter
select '2011', from: 'filter_start_year'
select 'January', from: 'filter_start_month'
select '1', from: 'filter_start_day'
#set end filter
select '2017', from: 'filter_end_year'
select 'January', from: 'filter_end_month'
select '1', from: 'filter_end_day'
click_button 'Filter'
end
# ...
def check_batch_assign_uri
expect(current_path).to eq "/assignment/batch_edit"
expect(URI.parse(current_url).to_s).to include("&ids[]=#{batch[0].id}&ids[]=#{batch[1].id}&ids[]=#{batch[2].id}")
end
def check_uniform_assign_uri
expect(current_path).to eq "/assignment/batch_edit_uniform"
expect(URI.parse(current_url).to_s).to include("&ids[]=#{batch[0].id}&ids[]=#{batch[1].id}&ids[]=#{batch[2].id}")
end
def check_batch_assign_select_values rider, status
rider_id_selector = "#wrapped_assignments_fresh__assignment_rider_id"
status_selector = "#wrapped_assignments_fresh__assignment_status"
expect(page.within("#assignments_fresh_0"){ find(rider_id_selector).find("option[selected]").text }).to eq rider.name
expect(page.within("#assignments_fresh_1"){ find(rider_id_selector).find("option[selected]").text }).to eq rider.name
expect(page.within("#assignments_fresh_2"){ find(rider_id_selector).find("option[selected]").text }).to eq rider.name
expect(page.within("#assignments_fresh_0"){ find(status_selector).find("option[selected]").text }).to eq status
expect(page.within("#assignments_fresh_1"){ find(status_selector).find("option[selected]").text }).to eq status
expect(page.within("#assignments_fresh_2"){ find(status_selector).find("option[selected]").text }).to eq status
end
def assign_batch_to rider, status
3.times do |n|
page.within("#assignments_fresh_#{n}") do
find("#wrapped_assignments_fresh__assignment_rider_id").select rider.name
find("#wrapped_assignments_fresh__assignment_status").select status
end
end
click_button 'Save changes'
end
def check_uniform_assign_shift_list rider, status
expect(page.within("#shifts"){ find("h3").text }).to eq "Shifts"
expect(page.all("#shifts_0 .shift_box")[0].text).to eq "#{batch[0].table_time} @ #{restaurant.name}"
expect(page.all("#shifts_0 .shift_box")[1].text).to eq "Assigned to: #{rider.name} [#{status}]"
expect(page.all("#shifts_1 .shift_box")[0].text).to eq "#{batch[1].table_time} @ #{restaurant.name}"
expect(page.all("#shifts_1 .shift_box")[1].text).to eq "Assigned to: #{rider.name} [#{status}]"
expect(page.all("#shifts_2 .shift_box")[0].text).to eq "#{batch[2].table_time} @ #{restaurant.name}"
expect(page.all("#shifts_2 .shift_box")[1].text).to eq "Assigned to: #{rider.name} [#{status}]"
end
def check_uniform_assign_select_values
expect(page.within("#assignment_form"){ find("#assignment_rider_id").has_css?("option[selected]") } ).to eq false
expect(page.within("#assignment_form"){ find("#assignment_status").find("option[selected]").text }).to eq 'Proposed'
end
def uniform_assign_batch_to rider, status
page.find("#assignment_rider_id").select rider.name
page.find("#assignment_status").select status
click_button 'Save changes'
end
def check_assignments_with_conflicts_list conflict_list_indices, batch_indices
expect(page.within("#assignments_with_conflicts"){ find("h3").text }).to eq "Assignments With Conflicts"
conflict_list_indices.each_with_index do |i,j|
batch_index = batch_indices[j]
expect(page.all("#assignments_with_conflicts_#{i} .shift_box")[0].text).to eq "#{batch[batch_index].table_time} @ #{batch[batch_index].restaurant.name}"
expect(page.all("#assignments_with_conflicts_#{i} .shift_box")[1].text).to eq "Assigned to: #{other_rider.name} [Proposed]"
expect(page.all("#assignments_with_conflicts_#{i} .shift_box")[2].text).to eq conflicts[batch_index].table_time
expect(page.find("#decisions_#{i}_Accept")).to be_checked
expect(page.find("#decisions_#{i}_Override")).to_not be_checked
end
end
def check_assignments_with_double_booking_list double_booking_list_indices, batch_indices
expect(page.within("#assignments_with_double_bookings"){ find("h3").text }).to eq "Assignments With Double Bookings"
double_booking_list_indices.each_with_index do |i,j|
batch_index = batch_indices[j]
expect(page.all("#assignments_with_double_bookings_#{i} .shift_box")[0].text).to eq "#{batch[batch_index].table_time} @ #{batch[batch_index].restaurant.name}"
expect(page.all("#assignments_with_double_bookings_#{i} .shift_box")[1].text).to eq "Assigned to: #{other_rider.name} [Proposed]"
expect(page.all("#assignments_with_double_bookings_#{i} .shift_box")[2].text).to eq "#{double_bookings[batch_index].table_time} @ #{double_bookings[batch_index].restaurant.name}"
expect(page.find("#decisions_0_Accept")).to be_checked
expect(page.find("#decisions_0_Override")).to_not be_checked
end
end
def check_without_obstacles_list list_indices, batch_indices
#input: Array of Nums (indices of assignments_without_obstacles Arr to check for), Array of Nums (indices of batch Shifts Arr to retrieve values from)
expect(page.within("#assignments_without_obstacles"){ find("h3").text }).to eq "Assignments Without Obstacles"
list_indices.each_with_index do |i,j|
batch_index = batch_indices[j]
expect(page.all("#assignments_without_obstacles_#{i} .shift_box")[0].text).to eq "#{batch[batch_index].table_time} @ #{restaurant.name}"
expect(page.all("#assignments_without_obstacles_#{i} .shift_box")[1].text).to eq "Assigned to: #{other_rider.name} [Proposed]"
end
end
def check_reassign_single_shift_list rider, status, batch_index
expect(page.within("#assignments_requiring_reassignment"){ find("h3").text }).to eq "Assignments Requiring Reassignment"
expect(page.find("#assignments_requiring_reassignment_0 .shift_box").text).to eq "#{batch[batch_index].table_time} @ #{batch[batch_index].restaurant.name}"
expect(page.within("#assignments_requiring_reassignment_0"){
find("#wrapped_assignments_fresh__assignment_rider_id").find("option[selected]").text
}).to eq rider.name
expect(page.within("#assignments_requiring_reassignment_0"){
find("#wrapped_assignments_fresh__assignment_status").find("option[selected]").text
}).to eq status
end
def reassign_single_shift_to rider, status
page.within("#assignments_requiring_reassignment_0") { find("#wrapped_assignments_fresh__assignment_rider_id").select rider.name }
page.within("#assignments_requiring_reassignment_0") { find("#wrapped_assignments_fresh__assignment_status").select status }
click_button 'Save changes'
end
def check_reassigned_shift_values rider, status
expect(page.find("#row_1_col_3").text).to eq rider.name
expect(page.find("#row_2_col_3").text).to eq rider.name
expect(page.find("#row_3_col_3").text).to eq rider.name
expect(page.find("#row_1_col_4").text).to eq status
expect(page.find("#row_2_col_4").text).to eq status
expect(page.find("#row_3_col_4").text).to eq status
end
def check_reassigned_shift_values_after_accepting_obstacle rider_1, rider_2, status
expect(page.find("#row_1_col_3").text).to eq rider_2.name
expect(page.find("#row_2_col_3").text).to eq rider_1.name
expect(page.find("#row_3_col_3").text).to eq rider_1.name
expect(page.find("#row_1_col_4").text).to eq status
expect(page.find("#row_2_col_4").text).to eq status
expect(page.find("#row_3_col_4").text).to eq status
end
def select_batch_assign_shifts_from_grid
page.within("#row_1_col_6"){ find("#ids_").set true }
page.within("#row_1_col_8"){ find("#ids_").set true }
page.within("#row_1_col_10"){ find("#ids_").set true }
end
def check_reassigned_shift_values_in_grid rider, status_code
expect(page.find("#row_1_col_6").text).to eq "#{rider.short_name} #{status_code}"
expect(page.find("#row_1_col_8").text).to eq "#{rider.short_name} #{status_code}"
expect(page.find("#row_1_col_10").text).to eq "#{rider.short_name} #{status_code}"
end
def load_batch
let(:start_t){ Time.zone.local(2014,1,1,12) }
let(:end_t){ Time.zone.local(2014,1,1,18) }
let!(:batch)do
3.times.map do |n|
FactoryGirl.build(:shift, :with_restaurant, restaurant: restaurant, start: start_t + n.days, :end => end_t + n.days)
end
end
end
def load_conflicts
let(:conflicts) do
3.times.map do |n|
FactoryGirl.build(:conflict, :with_rider, rider: other_rider, start: batch[n].start, :end => batch[n].end)
end
end
end
def load_double_bookings
let(:double_bookings) do
3.times.map do |n|
FactoryGirl.build(:shift, :with_restaurant, restaurant: restaurant, start: batch[n].start, :end => batch[n].end)
end
end
end
def load_free_rider
let!(:free_rider){ FactoryGirl.create(:rider) }
end
end
# ************************************************
# RIDER MAILER SPEC MACROS
# app/spec/support/rider_mailer_macros.rb
# ************************************************
module RiderMailerMacros
def load_staffers
let(:tess){ FactoryGirl.create(:staffer, :tess) }
let(:justin){ FactoryGirl.create(:staffer, :justin) }
end
def load_delegation_scenario
let!(:rider){ FactoryGirl.create(:rider) }
let!(:restaurant){ FactoryGirl.create(:restaurant) }
let(:now){ Time.zone.local(2014,1,6,11) }
let(:start_t){ now + 1.hour }
let(:end_t){ now + 7.hours }
# let(:start_t){ Time.zone.now.beginning_of_day + 12.hours }
# let(:end_t){ Time.zone.now.beginning_of_day + 18.hours }
let(:extra_shift){ FactoryGirl.create(:shift, :with_restaurant, restaurant: restaurant, start: start_t + 3.days, :end => end_t + 3.days) }
let(:emergency_shift){ FactoryGirl.create(:shift, :with_restaurant, restaurant: restaurant, start: start_t + 1.day, :end => end_t + 1.day) }
before do
rider.contact.update(name: 'A'*10)
restaurant.mini_contact.update(name: 'A'*10)
end
end
def load_batch_delegation_scenario
let!(:other_rider){ FactoryGirl.create(:rider) }
let!(:other_restaurant){ FactoryGirl.create(:restaurant) }
let(:extra_shifts) do
4.times.map do |n|
this_restaurant = is_even?(n) ? restaurant : other_restaurant #even shifts belong to restaurant, odd to other_restaurant
FactoryGirl.create(:shift, :with_restaurant, restaurant: this_restaurant, start: start_t + (n+3).days, :end => end_t + (n+3).days)
end
end
let(:emergency_shifts) do
4.times.map do |n|
m = n < 2 ? n*6 : (6*n+12)
this_restaurant = is_even?(n) ? restaurant : other_restaurant #even shifts belong to restaurant, odd to other_restaurant
FactoryGirl.create(:shift, :with_restaurant, restaurant: this_restaurant, start: start_t + m.hours, :end => end_t + m.hours )
# FactoryGirl.create(:shift, :with_restaurant, restaurant: this_restaurant, start: start_t + ((n+2)/2).days + (n/2*6).hours, :end => end_t + ((n+2)/2).days + (n/2*6).hours)
end
end
let(:mixed_batch) do
4.times.map{ |n| n < 2 ? extra_shifts[n] : emergency_shifts[n] }
end
# let(:mixed_shifts){ [ emergency_shift, extra_shift ] }
# change to include 4 shifts so can be passed to batch delegate
before do
other_rider.contact.update(name: 'A'*9+'a')
other_restaurant.mini_contact.update(name: 'A'*9+'a')
end
end
def load_conflict_request_scenario
let!(:riders){ 3.times.map{ FactoryGirl.create(:rider) } }
let(:week_start){ Time.zone.local(2014,1,6) }
let(:week_end){ Time.zone.local(2014,1,12) }
let(:start_t){ week_start + 12.hours }
let(:end_t){ week_start + 18.hours }
let!(:conflicts) do
arr_1 = 7.times.map do |n|
if n!= 5
start_ = start_t + n.days
end_ = start_ + 6.hours
FactoryGirl.create(:conflict, :with_rider, rider: riders[0], start: start_, :end => end_ )
end
end
arr_2 = 7.times.map do |n|
if n != 6
start_ = end_t + n.days
end_ = start_ + 6.hours
FactoryGirl.create(:conflict, :with_rider, rider: riders[0], start: start_, :end => end_ )
end
end
arr_1 + arr_2
end
let!(:mail_count){ ActionMailer::Base.deliveries.count }
end # load_conflict_request_scenario
def is_even? n
(n+2)%2 == 0
end
def load_schedule_email_scenario
let(:restaurants){ 7.times.map{ |n| FactoryGirl.create(:restaurant) } }
let(:schedule) do
7.times.map { |n| FactoryGirl.create(:shift, :with_restaurant, restaurant: restaurants[n], start: start_t + 12.hours + (7+n).days, :end => start_t + 18.hours + (7+n).days) }
end
end
# def load_delegation_email_scenario
# let(:mail){ RiderMailer.delegation_email rider, shift }
# end
def assign shift, status
visit edit_shift_assignment_path(shift, shift.assignment)
page.find("#assignment_rider_id").select rider.name
page.find("#assignment_status").select status
click_button 'Save changes'
end
def batch_delegate shifts, type
visit shifts_path
#set time filters inclusively
select Time.zone.local(2013).year, from: 'filter_start_year'
select Time.zone.local(2015).year, from: 'filter_end_year'
#filter out all restaurants but test restaurants
Restaurant.all.each { |r| unselect r.name, from: 'filter_restaurants' }
select restaurant.name, from: "filter_restaurants"
select other_restaurant.name, from: "filter_restaurants"
click_button 'Filter'
# sort by restaurant
click_link 'Restaurant'
#select and submit test restaurants' shifts for batch assignment
page.within("#row_1"){ find("#ids_").set true }
page.within("#row_2"){ find("#ids_").set true }
page.within("#row_3"){ find("#ids_").set true }
page.within("#row_4"){ find("#ids_").set true }
click_button 'Batch Assign', match: :first
#assign shifts
assign_extra if type == :extra
assign_emergency if type == :emergency
delegate_emergency if type == :emergency_delegation
assign_mixed if type == :mixed
click_button 'Save changes'
end
def assign_extra
#batch delegate shifts: first two shifts to rider, second two to other_rider
4.times do |n|
the_rider = n < 2 ? rider : other_rider
page.within("#assignments_fresh_#{n}") do
find("#wrapped_assignments_fresh__assignment_rider_id").select the_rider.name
find("#wrapped_assignments_fresh__assignment_status").select 'Delegated'
end
end
end
def assign_emergency
# batch confirm shifts: 0 & 1 to rider, 2 & 3 to other_rider
4.times do |n|
the_rider = n < 2 ? rider : other_rider
page.within("#assignments_fresh_#{n}") do
find("#wrapped_assignments_fresh__assignment_rider_id").select the_rider.name
find("#wrapped_assignments_fresh__assignment_status").select 'Confirmed'
end
end
end
def delegate_emergency
# batch confirm shifts: 0 & 1 to rider, 2 & 3 to other_rider
4.times do |n|
the_rider = n < 2 ? rider : other_rider
page.within("#assignments_fresh_#{n}") do
find("#wrapped_assignments_fresh__assignment_rider_id").select the_rider.name
find("#wrapped_assignments_fresh__assignment_status").select 'Delegated'
end
end
end
def assign_mixed
# batch assign: 0 (confirmed), 1 (delegated) to rider ; 2 (confirmed), 3 (delegated) to other_rider
4.times do |n|
the_rider = is_even?(n) ? rider : other_rider
status = n < 2 ? 'Confirmed' : 'Delegated'
page.within("#assignments_fresh_#{n}") do
find("#wrapped_assignments_fresh__assignment_rider_id").select the_rider.name
find("#wrapped_assignments_fresh__assignment_status").select status
end
end
end
# SINGLE EMAIL MACROS
def check_delegation_email_metadata mail, staffer, type
expect(mail.to).to eq [ rider.email ]
expect(mail.subject).to eq subject_from type
expect(mail.from).to eq [ "brooklynshift@gmail.com" ]
end
def subject_from type
case type
when :extra
'[EXTRA SHIFT] -- CONFIRMATION REQUIRED'
when :emergency
"[EMERGENCY SHIFT] -- SHIFT DETAILS ENCLOSED"
end
end
def check_delegation_email_body mail, staffer, type
actual_body = parse_body_from mail
expected_body = File.read("spec/mailers/sample_emails/single_#{staffer}_#{type}.html")
expect(actual_body).to eq expected_body
end
# BATCH EMAIL MACROS
def check_batch_delegation_email_metadata mails, type
from = [ "brooklynshift@gmail.com" ]
emails = [ rider.email, other_rider.email ]
subject = batch_subject_from type
mails.each_with_index do |mail, i|
expect(mail.from).to eq from
expect(mail.to).to eq [ emails[i] ]
expect(mail.subject).to eq subject
end
end
def check_mixed_batch_delegation_email_metadata mails
from = [ "brooklynshift@gmail.com" ]
emails = [ rider.email, rider.email, other_rider.email, other_rider.email ]
subjects = [ subject_from(:emergency), subject_from(:extra), subject_from(:emergency), subject_from(:extra) ]
mails.each_with_index do |mail, i|
expect(mail.from).to eq from
expect(mail.to).to eq [ emails[i] ]
expect(mail.subject).to eq subjects[i]
# puts ">>>> MAIL #{i} SUBJECT"
# puts mail.subject
# puts ">>>> MAIL #{i} TO"
# puts mail.to
end
end
def batch_subject_from type
case type
when :weekly
"[WEEKLY SCHEDULE] -- PLEASE CONFIRM BY SUNDAY"
when :extra
'[EXTRA SHIFTS] -- CONFIRMATION REQUIRED'
when :emergency
"[EMERGENCY SHIFTS] -- SHIFT DETAILS ENCLOSED"
end
end
def check_batch_delegation_email_body mails, staffer, type
mails.each_with_index do |mail, i|
# puts ">>>>>> MAIL #{i}"
# print mail.body
actual_body = parse_body_from mail
expected_body = File.read( "spec/mailers/sample_emails/batch_#{staffer}_#{type}_#{i}.html" )
expect(actual_body).to eq expected_body
end
end
def check_conflict_request_email_bodies mails, riders
mails.each_with_index do |mail, i|
puts ">>>>>> MAIL #{i}"
print mail.body
actual_body = parse_body_from mail
expected_body = expected_conflict_request_body_for riders[i], i
expect(actual_body).to eq expected_body
end
end
def check_conflict_request_metadata mails, riders
from = [ "brooklynshift@gmail.com" ]
subject = "[SCHEDULING CONFLICT REQUEST] 1/13 - 1/19"
mails.each_with_index do |mail, i|
expect(mail.from).to eq from
expect(mail.to).to eq [ riders[i].email ]
expect(mail.subject).to eq subject
end
end
# HELPERS
def parse_body_from mail
mail.body.encoded.gsub("\r\n", "\n")
end
def expected_conflict_request_body_for rider, i
str = File.read( "spec/mailers/sample_emails/conflicts_request_#{i}.html" )
str.gsub('<RIDER_ID>', "#{rider.id}")
end
end
# ***************************
# ********** SPECS **********
# ***************************
# ************************************************
# BATCH ASSIGNMENT SPECS
# app/spec/requests/shift_pages_spec.rb
# ************************************************
require 'spec_helper'
include CustomMatchers, RequestSpecMacros, ShiftRequestMacros, GridRequestMacros
describe "Shift Requests" do
let!(:restaurant) { FactoryGirl.create(:restaurant) }
let!(:other_restaurant) { FactoryGirl.create(:restaurant) }
let!(:rider){ FactoryGirl.create(:rider) }
let!(:other_rider){ FactoryGirl.create(:rider) }
let(:shift) { FactoryGirl.build(:shift, :with_restaurant, restaurant: restaurant) }
let(:shifts) { 31.times.map { FactoryGirl.create(:shift, :without_restaurant) } }
let(:staffer) { FactoryGirl.create(:staffer) }
before { mock_sign_in staffer }
subject { page }
# ...
describe "BATCH REQUESTS" do
before { restaurant }
let!(:old_count){ Shift.count }
load_batch
# ...
describe "BATCH ASSIGN" do
before do
# initialize rider & shifts, assign shifts to rider
other_rider
batch.each(&:save)
batch.each { |s| s.assignment.update(rider_id: rider.id, status: :confirmed) }
end
describe "from SHIFTS INDEX" do
before do
# select shifts for batch assignment
visit shifts_path
filter_shifts_by_time_inclusively
page.within("#row_1"){ find("#ids_").set true }
page.within("#row_2"){ find("#ids_").set true }
page.within("#row_3"){ find("#ids_").set true }
end
describe "with STANDARD batch edit" do
before { click_button 'Batch Assign', match: :first }
describe "batch edit assignment page" do
it "should have correct URI" do
check_batch_assign_uri
end
it { should have_h1 'Batch Assign Shifts' }
it { should have_content(restaurant.name) }
it "should have correct select values" do
check_batch_assign_select_values rider, 'Confirmed'
end
end
describe "EXECUTING batch assignment" do
describe "WITHOUT OBSTACLES" do
before { assign_batch_to other_rider, 'Proposed' }
describe "after editing" do
it "should redirect to the correct page" do
expect(current_path).to eq "/shifts/"
end
describe "index page" do
before { filter_shifts_by_time_inclusively }
it "should show new values of edited shifts" do
check_reassigned_shift_values other_rider, 'Proposed'
end
end
end # "after editing"
end # "WITHOUT OBSTACLES"
describe "WITH CONFLICT" do
load_conflicts
before do
conflicts[0].save
assign_batch_to other_rider, 'Proposed'
end
describe "Resolve Obstacles page" do
describe "CONTENTS" do
it "should be the Resolve Obstacles page" do
expect(current_path).to eq "/assignment/batch_edit"
expect(page).to have_h1 'Resolve Scheduling Obstacles'
end
it "should correctly list Assignments With Conflicts" do
check_assignments_with_conflicts_list [0], [0]
end
it "should not list Assignments With Double Bookings" do
expect(page).not_to have_selector("#assignments_with_double_bookings")
end
it "should correctly list Assignments Without Obstacles" do
check_without_obstacles_list [0,1], [1,2]
end
end # "CONTENTS"
describe "OVERRIDING" do
before do
choose "decisions_0_Override"
click_button 'Submit'
end
describe "after submission" do
before { filter_shifts_by_time_inclusively }
it "should redirect to the index page" do
expect(current_path).to eq "/shifts/"
expect(page).to have_h1 'Shifts'
end
it "should show new values for reassigned shifts" do
check_reassigned_shift_values other_rider, 'Proposed'
end
end # "after submission (on shifts index)"
end # "OVERRIDING"
describe "ACCEPTING" do
load_free_rider
before do
choose 'decisions_0_Accept'
click_button 'Submit'
end
describe "after submission" do
describe "batch reassign page" do
it "should be the batch reassign page" do
expect(current_path).to eq '/assignment/resolve_obstacles'
expect(page).to have_h1 'Batch Reassign Shifts'
end
it "should correctly list Assignements Requiring Reassignment" do
check_reassign_single_shift_list other_rider, 'Proposed', 0
end
it "should not list Assignments With Double Bookings" do
expect(page).not_to have_selector("#assignments_with_double_bookings")
end
it "should correctly list Assignments Without Obstacles" do
check_without_obstacles_list [0,1], [1,2]
end
end
describe "executing REASSIGNMENT TO FREE RIDER" do
before { reassign_single_shift_to free_rider, 'Proposed' }
describe "after submission" do
it "should redirect to the correct page" do
expect(current_path).to eq "/shifts/"
expect(page).to have_h1 'Shifts'
end
describe "index page" do
before { filter_shifts_by_time_inclusively }
it "shoud show new values for reassigned shifts" do
check_reassigned_shift_values_after_accepting_obstacle other_rider, free_rider, 'Proposed'
end
end #"index page"
end # "after submission"
end # "executing REASSIGNMENT TO FREE RIDER"
describe "executing REASSIGNMENT TO RIDER WITH CONFLICT" do
before{ click_button 'Save changes' }
it "should redirect to resolve obstacles page" do
expect(current_path).to eq "/assignment/batch_reassign"
expect(page).to have_h1 'Resolve Scheduling Obstacles'
end
end #"executing REASSIGNMENT TO RIDER WITH CONFLICT"
describe "executing REASSIGNMENT TO RIDER WITH DOUBLE BOOKING" do
load_double_bookings
before do
double_bookings[0].save
double_bookings[0].assign_to free_rider
reassign_single_shift_to free_rider, 'Confirmed'
end
it "should redirect to resolve obstacles page" do
expect(current_path).to eq "/assignment/batch_reassign"
expect(page).to have_h1 'Resolve Scheduling Obstacles'
end
end #"executing REASSIGNMENT TO RIDER WITH CONFLICT"
end # "after submission"
end # "ACCEPTING"
end # "Resove Obstacles Page"
end # "WITH CONFLICT"
describe "WITH 2 CONFLICTS" do
load_conflicts
before do
conflicts[0..1].each(&:save)
assign_batch_to other_rider, 'Proposed'
end
describe "Resolve Obstacles page" do
describe "CONTENTS" do
it "should be the Resolve Obstacles page" do
expect(current_path).to eq "/assignment/batch_edit"
expect(page).to have_h1 'Resolve Scheduling Obstacles'
end
it "should correctly list Assignments With Conflicts" do
check_assignments_with_conflicts_list [0,1], [0,1]
end
it "should not list Assignments With Double Bookings" do
expect(page).not_to have_selector("#assignments_with_double_bookings")
end
it "should correctly list Assignments Without Obstacles" do
check_without_obstacles_list [0], [2]
end
end # "CONTENTS"
end # "Resolve Obstacles page"
end # "WITH 2 CONFLICTS"
describe "WITH 3 CONFLICTS" do
load_conflicts
before do
conflicts.each(&:save)
assign_batch_to other_rider, 'Proposed'
end
describe "Resolve Obstacles page" do
describe "CONTENTS" do
it "should be the Resolve Obstacles page" do
expect(current_path).to eq "/assignment/batch_edit"
expect(page).to have_h1 'Resolve Scheduling Obstacles'
end
it "should correctly list Assignments With Conflicts" do
check_assignments_with_conflicts_list [0,1,2], [0,1,2]
end
it "should not list Assignments With Double Bookings" do
expect(page).not_to have_selector("#assignments_with_double_bookings")
end
it "should not list Assignments Without Obstacles" do
expect(page).not_to have_selector("#assignments_without_obstacles")
end
end # "CONTENTS"
end # "Resolve Obstacles page"
end # "WITH 3 CONFLICTS"
describe "WITH DOUBLE BOOKING" do
load_double_bookings
before do
double_bookings[0].save
double_bookings[0].assign_to other_rider
assign_batch_to other_rider, 'Proposed'
end
describe "Resolve Obstacles page" do
describe "CONTENTS" do
it "should be the Resolve Obstacles page" do
expect(current_path).to eq "/assignment/batch_edit"
expect(page).to have_h1 'Resolve Scheduling Obstacles'
end
it "should not list Assignments With Conflicts" do
expect(page).not_to have_selector("#assignments_with_conflicts")
end
it "should correctly list Assignments With Double Bookings" do
check_assignments_with_double_booking_list [0], [0]
end
it "should correctly list Assignments Without Obstacles" do
check_without_obstacles_list [0,1], [1,2]
end
end # "CONTENTS"
describe "OVERRIDING" do
before do
choose "decisions_0_Override"
click_button 'Submit'
end
describe "after submission" do
it "should redirect to the correct page" do
expect(current_path).to eq "/shifts/"
expect(page).to have_h1 'Shifts'
end
describe "index page" do
before { filter_shifts_by_time_inclusively }
it "shoud show new values for reassigned shifts" do
check_reassigned_shift_values other_rider, 'Proposed'
end
end # "index page"
end # "after submission"
end # "OVERRIDING"
describe "ACCEPTING" do
load_free_rider
before do
choose 'decisions_0_Accept'
click_button 'Submit'
end
describe "after submission" do
describe "batch reassign page" do
it "should redirect to the correct page" do
expect(current_path).to eq '/assignment/resolve_obstacles'
expect(page).to have_h1 'Batch Reassign Shifts'
end
it "should correctly list Assignements Requiring Reassignment" do
check_reassign_single_shift_list other_rider, 'Proposed', 0
end
it "should not list Assignments With Double Bookings" do
expect(page).not_to have_selector("#assignments_with_double_bookings")
end
it "should correctly list Assignemnts Without Obstacles" do
check_without_obstacles_list [0,1], [1,2]
end
end
describe "executing REASSIGNMENT TO FREE RIDER" do
before { reassign_single_shift_to free_rider, 'Proposed' }
describe "after submission" do
it "should redirect to the correct page" do
expect(current_path).to eq "/shifts/"
expect(page).to have_h1 'Shifts'
end
describe "index page" do
before { filter_shifts_by_time_inclusively }
it "shoud show new values for reassigned shifts" do
check_reassigned_shift_values_after_accepting_obstacle other_rider, free_rider, 'Proposed'
end
end #"index page"
end # "after submission"
end # "executing REASSIGNMENT TO FREE RIDER"
end # "after submission"
end # "ACCEPTING"
end # "Resolve Obstacles page"
end # "WITH DOUBLE BOOKING"
describe "WITH 2 DOUBLE BOOKINGS" do
load_double_bookings
before do
double_bookings[0..1].each do |shift|
shift.save
shift.assign_to other_rider
end
assign_batch_to other_rider, 'Proposed'
end
describe "Resolve Obstacles page" do
describe "CONTENTS" do
it "should be the Resolve Obstacles page" do
expect(current_path).to eq "/assignment/batch_edit"
expect(page).to have_h1 'Resolve Scheduling Obstacles'
end
it "should not list Assignments With Conflicts" do
expect(page).not_to have_selector("#assignments_with_conflicts")
end
it "should correctly list Assignments With Double Bookings" do
check_assignments_with_double_booking_list [0,1], [0,1]
end
it "should correctly list Assignments Without Obstacles" do
check_without_obstacles_list [0], [2]
end
end # "CONTENTS"
end # "Resolve Obstacles page"
end # "WITH 2 DOUBLE BOOKINGS"
describe "WITH 3 DOUBLE BOOKINGS" do
load_double_bookings
before do
double_bookings.each do |shift|
shift.save
shift.assign_to other_rider
end
assign_batch_to other_rider, 'Proposed'
end
describe "Resolve Obstacles page" do
describe "CONTENTS" do
it "should be the Resolve Obstacles page" do
expect(current_path).to eq "/assignment/batch_edit"
expect(page).to have_h1 'Resolve Scheduling Obstacles'
end
it "should not list Assignments With Conflicts" do
expect(page).not_to have_selector("#assignments_with_conflicts")
end
it "should correctly list Assignments With Double Bookings" do
check_assignments_with_double_booking_list [0,1,2], [0,1,2]
end
it "should not list Assignments Without Obstacles" do
expect(page).not_to have_selector("#assignments_without_obstacles")
end
end # "CONTENTS"
end # "Resolve Obstacles page"
end # "WITH 2 DOUBLE BOOKINGS"
describe "WITH CONFLICT AND DOUBLE BOOKING" do
load_conflicts
load_double_bookings
before do
conflicts[0].save
double_bookings[1].save
double_bookings[1].assign_to other_rider
assign_batch_to other_rider, 'Proposed'
end
describe "Resolve Obstacles Page" do
describe "CONTENTS" do
it "should be the Resolve Obstacles page" do
expect(current_path).to eq "/assignment/batch_edit"
expect(page).to have_h1 'Resolve Scheduling Obstacles'
end
it "should correctly list Assignments With Conflicts" do
check_assignments_with_conflicts_list [0], [0]
end
it "should correctly list Assignments With Double Bookings" do
check_assignments_with_double_booking_list [0], [1]
end
it "should correctly list Assignments Without Obstacles" do
check_without_obstacles_list [0], [2]
end
end # "CONTENTS"
describe "OVERRIDING BOTH" do
before do
choose "decisions_0_Override"
choose "decisions_1_Override"
click_button 'Submit'
end
describe "after submission" do
before { filter_shifts_by_time_inclusively }
it "should redirect to the index page" do
expect(current_path).to eq "/shifts/"
expect(page).to have_h1 'Shifts'
end
it "should show new values for reassigned shifts" do
check_reassigned_shift_values other_rider, 'Proposed'
end
end # "after submission (on shifts index)"
end # "OVERRIDING BOTH"
describe "OVERRIDING CONFLICT / ACCEPTING DOUBLE BOOKING" do
before do
choose "decisions_0_Override"
choose "decisions_1_Accept"
click_button 'Submit'
end
describe "after submission" do
describe "batch reassign page" do
it "should be the batch reassign page" do
expect(current_path).to eq '/assignment/resolve_obstacles'
expect(page).to have_h1 'Batch Reassign Shifts'
end
it "should correctly list Assignments Requiring Reassignment" do
check_reassign_single_shift_list other_rider, 'Proposed', 1
end
it "should correctly list Assignments Without Obstacles" do
check_without_obstacles_list [0,1], [2,0]
end
end
end # "after submission"
end # "OVERRIDING CONFLICT / ACCEPTING DOUBLE BOOKING"
describe "ACCEPTING CONFLICT / OVERRIDING DOUBLE BOOKING" do
before do
choose "decisions_0_Accept"
choose "decisions_1_Override"
click_button 'Submit'
end
describe "after submission" do
describe "batch reassign page" do
it "should be the batch reassign page" do
expect(current_path).to eq '/assignment/resolve_obstacles'
expect(page).to have_h1 'Batch Reassign Shifts'
end
it "should correctly list Assignments Requiring Reassignment" do
check_reassign_single_shift_list other_rider, 'Proposed', 0
end
it "should correctly list Assignments Without Obstacles" do
check_without_obstacles_list [0,1], [2,1]
end
end # "batch reassign page"
end # "after submission"
end # "OVERRIDING CONFLICT / ACCEPTING DOUBLE BOOKING"
end # "Resolve Obstacles Page"
end # "WITH CONFLICT AND DOUBLE BOOKING"
end # "EXECUTING batch assignment"
end # "with STANDARD batch edit"
describe "with UNIFORM batch edit" do
before { click_button 'Uniform Assign', match: :first }
describe "Uniform Assign Shifts page" do
it "should have correct URI and Header" do
check_uniform_assign_uri
expect(page).to have_h1 "Uniform Assign Shifts"
end
it "should list Shifts correctly" do
check_uniform_assign_shift_list rider, 'Confirmed'
end
it "should have correct form values" do
check_uniform_assign_select_values
end
end
describe "EXECUTING batch assignment" do
describe "WITHOUT OBSTACLES" do
before { uniform_assign_batch_to other_rider, 'Cancelled (Rider)' }
describe "after editing" do
it "should redirect to the correct page" do
expect(current_path).to eq "/shifts/"
expect(page).to have_h1 'Shifts'
end
describe "index page" do
before { filter_shifts_by_time_inclusively }
it "should show new values for re-assigned shifts" do
check_reassigned_shift_values other_rider, 'Cancelled (Rider)'
end
end # "index page"
end # "after editing"
end # "WITHOUT OBSTACLES"
describe "WITH CONFLICT" do
load_conflicts
before do
conflicts[0].save
uniform_assign_batch_to other_rider, 'Proposed'
end
it "should redirect to the Resolve Obstacles page" do
expect(current_path).to eq "/assignment/batch_edit_uniform"
expect(page).to have_h1 'Resolve Scheduling Obstacles'
end
end # "WITH CONFLICT"
describe "WITH DOUBLE BOOKING" do
load_double_bookings
before do
double_bookings[0].save
double_bookings[0].assign_to other_rider
uniform_assign_batch_to other_rider, 'Proposed'
end
it "should be the Resolve Obstacles page" do
expect(current_path).to eq "/assignment/batch_edit_uniform"
expect(page).to have_h1 'Resolve Scheduling Obstacles'
end
end # "WITH DOUBLE BOOKING"
end # "EXECUTING batch assignment"
end # "Uniform Assign Shifts page"
end # "with UNIFORM batch edit"
describe "from GRID" do
before do
restaurant.mini_contact.update(name: 'A'*10)
visit shift_grid_path
filter_grid_for_jan_2014
end
describe "page contents" do
describe "batch edit form" do
it { should have_button 'Batch Assign' }
it "should have correct form action" do
expect(page.find("form.batch")['action']).to eq '/shift/batch_edit'
end
end
describe "grid rows" do
it "should have correct cells in first row" do
expect(page.find("#row_1_col_1").text).to eq 'A'*10
expect(page.find("#row_1_col_6").text).to eq rider.short_name + " [c]"
expect(page.find("#row_1_col_8").text).to eq rider.short_name + " [c]"
expect(page.find("#row_1_col_10").text).to eq rider.short_name + " [c]"
end
end
end
describe "STANDARD batch assignment" do
before do
select_batch_assign_shifts_from_grid
click_button 'Batch Assign'
end
describe "batch assign page" do
it "should have correct URI" do
check_batch_assign_uri
end
it "should have correct assignment values" do
check_batch_assign_select_values rider, 'Confirmed'
end
end
describe "executing batch assignment" do
before { assign_batch_to rider, 'Proposed' }
describe "after editing" do
it "should redirect to the correct page" do
expect(current_path).to eq "/grid/shifts"
end
describe "page contents" do
before { filter_grid_for_jan_2014 }
it "should have new assignment values" do
check_reassigned_shift_values_in_grid other_rider, '[p]'
end
end
end
end
end
describe "UNIFORM batch assignment" do
before do
select_batch_assign_shifts_from_grid
click_button 'Uniform Assign'
end
describe "uniform assign page" do
it "should have correct uri" do
check_uniform_assign_uri
end
it { should have_h1 'Uniform Assign Shifts' }
it { should have_content restaurant.name }
it "should have correct form values" do
check_uniform_assign_select_values
end
end
describe "executing batch edit" do
before { uniform_assign_batch_to other_rider, 'Cancelled (Rider)' }
describe "after editing" do
it "should redirect to the correct page" do
expect(current_path).to eq "/grid/shifts"
end
describe "index page" do
before { filter_grid_for_jan_2014 }
it "should show new values for re-assigned shifts" do
check_reassigned_shift_values_in_grid other_rider, '[xf]'
end
end
end
end
end
end
end
end
end
# ************************************************
# RIDER MAILER SPECS
# app/spec/mailers/rider_mailer_spec.rb
# ************************************************
require 'spec_helper'
include RequestSpecMacros, RiderMailerMacros
describe "Rider Mailer Requests" do
load_staffers
describe "DELEGATION EMAIL" do
load_delegation_scenario
describe "as Tess" do
before { mock_sign_in tess }
describe "for extra shift" do
let!(:mail_count){ ActionMailer::Base.deliveries.count }
before { assign extra_shift, 'Delegated' }
let(:mail){ ActionMailer::Base.deliveries.last }
it "should send an email" do
expect(ActionMailer::Base.deliveries.count).to eq (mail_count + 1)
end
it "should render correct email metadata" do
check_delegation_email_metadata mail, :tess, :extra
end
it "should render correct email body" do
check_delegation_email_body mail, :tess, :extra
end
end
describe "for emergency shift" do
let!(:mail_count){ ActionMailer::Base.deliveries.count }
before { assign emergency_shift, 'Confirmed' }
let(:mail){ ActionMailer::Base.deliveries.last }
it "should send an email" do
expect(ActionMailer::Base.deliveries.count).to eq (mail_count + 1)
end
it "should render correct email metadata" do
check_delegation_email_metadata mail, :tess, :emergency
end
it "should render correct email body" do
check_delegation_email_body mail, :tess, :emergency
end
end
describe "trying to delegate an emergency shift" do
before { assign emergency_shift, 'Delegated' }
it "should redirect to error-handling page" do
expect(page).to have_h1 'Batch Assign Shifts'
end
it "should list shifts with errors correctly" do
expect(page.within("#assignments_fresh_0"){ find(".field_with_errors").text }).to include(rider.name)
end
end # "trying to delegate an emergency shift"
end # "as Tess"
describe "as Justin" do
before { mock_sign_in justin }
describe "for extra shift" do
let!(:mail_count){ ActionMailer::Base.deliveries.count }
before { assign extra_shift, 'Delegated' }
let(:mail){ ActionMailer::Base.deliveries.last }
it "should send an email" do
expect(ActionMailer::Base.deliveries.count).to eq (mail_count + 1)
end
it "should render correct email metadata" do
check_delegation_email_metadata mail, :justin, :extra
end
it "should render correct email body" do
check_delegation_email_body mail, :justin, :extra
end
end
end #"as Justin"
end # "ASSIGNMENT EMAIL"
describe "BATCH DELEGATION EMAILS" do
load_delegation_scenario
load_batch_delegation_scenario
describe "as Tess" do
before { mock_sign_in tess }
describe "for EXTRA shifts" do
let!(:mail_count){ ActionMailer::Base.deliveries.count }
before { batch_delegate extra_shifts, :extra }
let(:mails){ ActionMailer::Base.deliveries.last(2) }
it "should send 2 emails" do
expect( ActionMailer::Base.deliveries.count ).to eq mail_count + 2
end
it "should format email metadata correctly" do
check_batch_delegation_email_metadata mails, :extra
end
it "should format email body correctly" do
check_batch_delegation_email_body mails, :tess, :extra
end
end # "for EXTRA shifts"
describe "for EMERGENCY shifts" do
let!(:mail_count){ ActionMailer::Base.deliveries.count }
before { batch_delegate emergency_shifts, :emergency }
let(:mails){ ActionMailer::Base.deliveries.last(2) }
it "should send 2 emails" do
expect( ActionMailer::Base.deliveries.count ).to eq mail_count + 2
end
it "should format email metadata correctly" do
check_batch_delegation_email_metadata mails, :emergency
end
it "should format email body correctly" do
check_batch_delegation_email_body mails, :tess, :emergency
end
end # "for EMERGENCY shifts"
describe "for MIXED BATCH of shifts" do
let!(:mail_count){ ActionMailer::Base.deliveries.count }
before { batch_delegate mixed_batch, :mixed }
let(:mails){ ActionMailer::Base.deliveries.last(4) }
it "should send 4 emails" do
expect( ActionMailer::Base.deliveries.count ).to eq mail_count + 4
end
it "should format email metadata correctly" do
check_mixed_batch_delegation_email_metadata mails
end
it "should format email body correctly" do
check_batch_delegation_email_body mails, :tess, :mixed
end
end # "for "for MIXED BATCH of shifts"
describe "trying to DELEGATE EMERGENCY shifts" do
before { batch_delegate emergency_shifts, :emergency_delegation }
it "should redirect to error-handling page" do
expect(page).to have_h1 'Batch Assign Shifts'
end
it "should list shifts with errors correctly" do
expect(page.within("#assignments_fresh_0"){ find(".field_with_errors").text }).to include(rider.name)
expect(page.within("#assignments_fresh_1"){ find(".field_with_errors").text }).to include(rider.name)
expect(page.within("#assignments_fresh_2"){ find(".field_with_errors").text }).to include(rider.name)
expect(page.within("#assignments_fresh_3"){ find(".field_with_errors").text }).to include(rider.name)
end
end # "trying to DELEGATE EMERGENCY shifts"
end # "as Tess"
end # "BATCH ASSIGNMENT EMAILS"
# ....
end
/ ***********************
/ ******** VIEWS ********
/ ***********************
/ **********************************************
/ SHIFTS INDEX VIEW
/ app/views/shifts/index.html.haml
/ **********************************************
- provide(:title, 'Shifts')
/Arguments
- if @caller
- header_suffix = " for #{@caller_obj.name}"
- span = "span8 offset2"
- else
- header_suffix = ""
- span = "span10 offset1"
/Header
%h1= "Shifts" + header_suffix
/Hot Links
- if can? :manage, Shift
= render 'hot_links', entity: @caller_obj
/Filters
.row
%div{ class: span+' filters' }
= render 'filter_form'
/Batch Edit Form Wrapper
.row.batch_form
= form_tag '/shift/batch_edit', method: :get, class: 'batch' do
/Submit Buttons
.center
= submit_tag 'Batch Edit', class: 'btn btn-primary'
= submit_tag 'Batch Assign', class: 'btn btn-primary'
= submit_tag 'Uniform Assign', class: 'btn btn-primary'
= hidden_field_tag :base_path, @base_path
/Pagination
.center
= will_paginate @shifts
/Table
.row
%div{ :class => span }
= render 'layouts/table', table: @shift_table
/Pagination
.center
= will_paginate @shifts
/Submit Buttons
.center
= submit_tag 'Batch Edit', class: 'btn btn-primary'
= submit_tag 'Batch Assign', class: 'btn btn-primary'
/Hot Links
- if can? :manage, Shift
%p.center
= render 'hot_links', entity: @caller_obj
/ **********************************************
/ TABLE LAYOUT
/ app/views/layouts/_table.html.haml
/ **********************************************
.table
/Headers
.row.header
- table.headers.each_with_index do |header, i|
%div{ id: "row_0_col_#{i+1}", :class => "span#{table.spans[i]}" }
= sort_if_sortable header
/Data Rows
- table.rows.each_with_index do |row, i|
.row
%div{ id: "row_#{i+1}" }
/Checkboxes (optional)
.checkbox
= checkbox_if_checkable row
/Cells
- row[:cells].each_with_index do |cell, j|
%div{ id: "row_#{i+1}_col_#{j+1}", :class => "span#{table.spans[j]}" }
= link_if_linkable cell
/Action Dropdown
.span1.action.dropdown
%a.dropdown-toggle{"data-toggle" => "dropdown", :href => "#"}
Action
%b.caret
/Action Options
%ul.dropdown-menu
- row[:actions].each do |action|
%li= link_to action[:val], action[:href], method: action[:method], data: action[:data]
/ **********************************************
/ BATCH EDIT ASSIGNMENTS VIEW
/ app/views/assignments/batch_edit.html.haml
/ **********************************************
- provide(:title, 'Batch Assign Shifts')
%h1 Batch Assign Shifts
.span8.offset2.profile
= form_tag '/assignment/batch_edit', method: :post do
= render 'shared/batch_error_messages', errors: @errors
/Fresh
- @assignments.fresh.each_with_index do |wrapped_assignment, i|
%div{ id: "assignments_fresh_#{i}" }
= render 'batch_fields', assignment: wrapped_assignment.assignment, index: wrapped_assignment.index
%hr/
/Old
= render 'old_assignment_hidden_fields'
/Base Path
= render 'shared/base_path_field'
%p.center
= submit_tag 'Save changes', class: 'btn btn-primary'
/ **********************************************
/ BATCH UNIFORM EDIT ASSIGNMENTS VIEW
/ app/views/assignments/batch_uniform_edit.html.haml
/ **********************************************
- provide(:title, 'Uniform Assign Shifts')
%h1 Uniform Assign Shifts
.span8.offset2.profile
= form_tag '/assignment/batch_edit_uniform', method: :post do
/Shifts
#shifts
%h3 Shifts
- @shifts.each_with_index do |shift, i|
%div{ id: "shifts_#{i}" }
%p.shift_box
= render 'assignments/shift_include', shift: shift
%p.shift_box
= render 'assignment_include', assignment: shift.assignment
= hidden_field_tag "shift_ids[]", shift.id
= hidden_field_tag "ids[]", shift.assignment.id
= hidden_field_tag :base_path, @base_path
%hr/
/Assignment Form
#assignment_form
%h3 Assign All Shifts To
.row
/Rider
.span4
.center
= label_tag :rider
= select_tag 'assignment[rider_id]', options_for_select(Rider.select_options, nil ), include_blank: true
/Status
.span4
.center
= label_tag :status
= select_tag 'assignment[status]', options_for_select(AssignmentStatus.select_options, :proposed)
%p.center
= submit_tag 'Save changes', class: 'btn btn-primary'
/ **********************************************
/ ASSIGNMENTS BATCH FILEDS PARTIAL
/ app/views/assignments/_batch_fields.html.haml
/ **********************************************
- #args: assignment, index
- name = lambda { |attr| "wrapped_assignments[fresh][][assignment][#{attr}]" }
- error_class = @errors.find{ |e| e[:record].shift_id == assignment.shift_id } ? 'field_with_errors' : ''
/Shift Box
%p.shift_box
= render 'assignments/shift_include', shift: assignment.shift
/Index
= hidden_field_tag "wrapped_assignments[fresh][][index]", index
/Assignment
.row
/Shift
= hidden_field_tag name.call('shift_id'), assignment.shift_id
%div{ class: error_class }
/Rider
.span4
.center
= label_tag :rider
- unless @caller == :rider
= select_tag name.call('rider_id'), options_for_select(Rider.select_options, assignment.rider.nil? ? nil : assignment.rider.id ), include_blank: true
- else
= assignment.rider.name
/Status
.span4
.center
= label_tag :status
= select_tag name.call('status'), options_for_select(AssignmentStatus.select_options, assignment.status)
/ **********************************************
/ SHIFT INCLUDE PARTIAL
/ app/views/assignments/_shift_include.html.haml
/ **********************************************
#{shift.table_time} @ #{link_to shift.restaurant.mini_contact.name, restaurant_path(shift.restaurant.mini_contact.name)}
/ **********************************************
/ ASSIGNMENT INCLUDE PARTIAL
/ app/views/assignments/_assignment_include.html.haml
/ **********************************************
- #arg: assignment
%strong
Assigned to:
= (link_to assignment.rider.name, rider_path(assignment.rider)) + " [#{assignment.status.text}]"
/ **********************************************
/ RESOLVE CONFLICTS VIEW
/ app/views/assignments/resolve_obstacles.html.haml
/ **********************************************
- #args: Assignments (.with_obstacles, .without_obstacles)
- provide(title: "Resolve Scheduling Obstacles")
%h1 Resolve Scheduling Obstacles
.row
.span8.offset2.profile
= form_tag '/assignment/resolve_obstacles', method: :post do
/Assignments
= hidden_field_tag :assignments_json, @assignments.to_json
/Decisions...
/... about Conflicts
- if @assignments.with_conflicts.any?
%div{ id: "assignments_with_conflicts" }
%h3 Assignments With Conflicts
- @assignments.with_conflicts.each_with_index do |wrapped_assignment, i|
%div{ id: "assignments_with_conflicts_#{i}" }
= render 'conflict_alert', assignment: wrapped_assignment.assignment
= render 'decision_radios', i: i
%p.center
%i="(NOTE: Selecting 'Yes' will delete all rider conflicts during this period)"
%hr/
/... about Double Bookings
- if @assignments.with_double_bookings.any?
%div{ id: "assignments_with_double_bookings" }
%h3 Assignments With Double Bookings
- offset = @assignments.with_conflicts.count
- @assignments.with_double_bookings.each_with_index do |wrapped_assignment, i|
%div{ id: "assignments_with_double_bookings_#{i}" }
= render 'double_booking_alert', assignment: wrapped_assignment.assignment
= render 'decision_radios', i: i + offset
%hr/
/Assignemnts Without Obstacles (display only)
- if @assignments.without_obstacles.any?
%div{ id: "assignments_without_obstacles" }
%h3 Assignments Without Obstacles
- @assignments.without_obstacles.each_with_index do |wrapped_assignment, i|
- assignment = wrapped_assignment.assignment
%div{ id: "assignments_without_obstacles_#{i}" }
.shift_box
= render 'shift_include', shift: assignment.shift
.shift_box
= render 'assignment_include', assignment: assignment
%hr/
/Submit
%p.center
= submit_tag "Submit", class: 'btn btn-primary'
/ **********************************************
/ CONFLICT ALERT PARTIAL
/ app/views/assignments/_conflict_alert.html.haml
/ **********************************************
- a = assignment
%p.shift_box
= render 'assignments/shift_include', shift: a.shift
%p.shift_box
= render 'assignments/assignment_include', assignment: a
.center
%strong
CONFLICTS WITH:
%p.shift_box
= render 'assignments/conflicts_include', conflicts: a.conflicts
%p.center
%strong
Do you want to assign it anyway?
/ **********************************************
/ CONFLICTS INCLUDE PARTIAL
/ app/views/assignments/_conflict_alert.html.haml
/ **********************************************
- conflicts.each do |conflict|
#{conflict.table_time}
%br/
/ **********************************************
/ DOUBLE BOOKING ALERT PARTIAL
/ app/views/assignments/_double_booking_alert.html.haml
/ **********************************************
- a = assignment
%p.shift_box
= render 'assignments/shift_include', shift: a.shift
%p.shift_box
= render 'assignments/assignment_include', assignment: a
.center
%strong
DOUBLE BOOKS WITH:
%p.shift_box
= render 'assignments/double_bookings_include', double_bookings: a.double_bookings
%p.center
%strong
Do you want to assign it anyway?
/ **********************************************
/ DOUBLE BOOKINGS INCLUDE PARTIAL
/ app/views/assignments/_double_bookings_include.html.haml
/ **********************************************
- double_bookings.each do |double_booking|
= render 'assignments/shift_include', shift: double_booking
%br/
/ **********************************************
/ DECISION RADIOS PARTIAL
/ app/views/assignments/_decision_radios.html.haml
/ **********************************************
- #args: i
.radio
= radio_button_tag "decisions[#{i}]", 'Accept', true
= label_tag :no
.radio
= radio_button_tag "decisions[#{i}]", 'Override', false
= label_tag :yes
- # arg: @assignments (Assignments Obj)
- provide(title: "Batch Reassign Shifts")
/ **********************************************
/ BATCH REASSIGN VIEW
/ app/views/assignments/batch_reassign.html.haml
/ **********************************************
%h1 Batch Reassign Shifts
.row
.span8.offset2.profile
= form_tag '/assignment/batch_reassign', method: :post do
/Requiring Reassignment
%div{ id: "assignments_requiring_reassignment" }
%h3 Assignments Requiring Reassignment
- @assignments.requiring_reassignment.each_with_index do |wrapped_assignment, i|
%div{ id: "assignments_requiring_reassignment_#{i}" }
.field_with_errors
= render 'batch_fields', assignment: wrapped_assignment.assignment, index: wrapped_assignment.index
%hr/
/No Obstacles
- if @assignments.without_obstacles.any?
%div{ id: "assignments_without_obstacles" }
%h3 Assignments Without Obstacles
- @assignments.without_obstacles.each_with_index do |wrapped_assignment, i|
- assignment = wrapped_assignment.assignment
- index = wrapped_assignment.index
%div{ id: "assignments_without_obstacles_#{i}" }
.shift_box
= render 'shift_include', shift: assignment.shift
.shift_box
= render 'assignment_include', assignment: assignment
= render 'batch_attribute_hidden_fields', assignment: assignment, assignments_key: 'without_obstacles', index: index
%hr/
/Old
= render 'old_assignment_hidden_fields'
/Base Path
= render 'shared/base_path_field'
/Submit
%p.center
= submit_tag 'Save changes', class: 'btn btn-primary'
/ ***********************************************************
/ BATCH ATTRIBUTE HIDDEN FIELDS PARTIAL
/ app/views/assignments/_old_assignment_hidden_fields.html.haml
/ ***********************************************************
- #arg: assignments_key, assignment, index
- name = lambda { |attr| "wrapped_assignments[#{assignments_key}][][assignment][#{attr}]" }
/Index
= hidden_field_tag "wrapped_assignments[#{assignments_key}][][index]", index
/Assignment
- assignment.attributes.keys.each do |attr|
= hidden_field_tag name.call(attr), assignment.send(attr)
/ ***********************************************************
/ OLD ASSIGNMENTS HIDDEN FIELDS PARTIAL
/ app/views/assignments/_old_assignment_hidden_fields.html.haml
/ ***********************************************************
- @assignments.old.each do |wrapped_assignment|
= render 'batch_attribute_hidden_fields', assignment: wrapped_assignment.assignment, index: wrapped_assignment.index, assignments_key: 'old'
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment