Skip to content

Commit

Permalink
Merge branch '347-event-form-enhancements'
Browse files Browse the repository at this point in the history
Conflicts:
	app/models/service/LicenseService.scala
  • Loading branch information
sery0ga committed Apr 26, 2015
2 parents d5e1594 + ac7d789 commit 91284f8
Show file tree
Hide file tree
Showing 25 changed files with 1,003 additions and 349 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,5 @@ dist
run-server.sh
.tags*
.ensime
.ensime_lucene/
.ensime_lucene/
TODO
85 changes: 85 additions & 0 deletions app/assets/event/form.js
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,75 @@ function updateInvoicingOrganisations(organisations, selectedId) {
}
}

/**
* Calculates number of total hours for event based on its number of days and
* hours per day
*/
function calculateTotalHours() {
var end = $('#schedule_end').data("DateTimePicker").getDate();
var start = $('#schedule_start').data('DateTimePicker').getDate();
var days = end.diff(start, 'days') + 1;
return $('#schedule_hoursPerDay').val() * days;
}
/**
* Updates number of total hours for event based on its number
* of days and hours per day
*/
function updateTotalHours() {
var totalHours = calculateTotalHours();
$('#schedule_totalHours').val(totalHours);
}

/**
* Checks if the entered number of total hours is move than minimum threshold
* and shows an alert
* @param hours Number of entered total hours
*/
function checkTotalHours(hours) {
var idealHours = calculateTotalHours();
var threshold = 0.2;
var difference = (idealHours - hours) / parseFloat(idealHours);
if (difference > threshold) {
$('#totalHours-alert').show();
} else {
$('#totalHours-alert').hide();
}
}

/**
* Checks if the given url points to an existing page and notifies a user about
* the results of the check
*
* @param url {string} The url of interest
* @param element {string} jQuery selector
*/
function checkUrl(url, element) {
var field = element + '_field';
if ($.trim(url).length == 0 || (url.substring(0, 6) == "mailto")) {
$(field).removeClass('has-error');
$(field).removeClass('has-success');
$(element).siblings('span').each(function() {
$(this).text("Web site URL");
});
} else {
var fullUrl = jsRoutes.controllers.Urls.validate(url).url;
$.post(fullUrl, {}, null, "json").done(function(data) {
if (data.result == "invalid") {
$(field).addClass('has-error');
$(element).siblings('span').each(function() {
$(this).text("URL is not correct");
});
} else {
$(field).removeClass('has-error');
$(field).addClass('has-success');
$(element).siblings('span').each(function() {
$(this).text("URL is correct");
});
}
});
}
}

$(document).ready( function() {

/**
Expand Down Expand Up @@ -258,6 +327,19 @@ $(document).ready( function() {
});
$("#schedule_start").on("dp.change", function (e) {
$('#schedule_end').data("DateTimePicker").setMinDate(e.date);
updateTotalHours();
});
$("#schedule_end").on("dp.change", function(e) {
updateTotalHours();
});
$('#schedule_totalHours').on('change', function(e) {
checkTotalHours($(this).val());
});
$('#details_webSite').on('change', function(e) {
checkUrl($(this).val(), '#details_webSite');
});
$('#details_registrationPage').on('change', function(e) {
checkUrl($(this).val(), '#details_registrationPage');
});
var brandId = $('#brandId').find(':selected').val();
getEventTypes(brandId, $('#currentEventTypeId').attr('value'));
Expand All @@ -275,7 +357,10 @@ $(document).ready( function() {
$("#title").on('keyup', function() {
$("#eventTypeId").unbind('change');
});
updateTotalHours(8);
}
checkTotalHours($('#schedule_totalHours').val());

if ($("#confirmed").attr("checked") != "checked") {
$("#confirmed-alert").hide();
}
Expand Down
165 changes: 96 additions & 69 deletions app/controllers/Events.scala
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import models.UserRole.Role._
import models.brand.EventType
import models.event.Comparator
import models.event.Comparator.FieldChange
import models.service.{ EventService, Services }
import models.service.Services
import models.{ Location, Schedule, _ }
import org.joda.time.{ DateTime, LocalDate }
import play.api.data.Forms._
Expand All @@ -42,7 +42,7 @@ import play.api.mvc._
import services.integrations.Integrations
import views.Countries

object Events extends Controller
trait Events extends Controller
with Security
with Services
with Integrations {
Expand Down Expand Up @@ -74,37 +74,6 @@ object Events extends Controller
}
}

val eventTypeFormatter = new Formatter[Long] {

def bind(key: String, data: Map[String, String]): Either[Seq[FormError], Long] = {
// "data" lets you access all form data values
try {
val eventTypeId = data.get("eventTypeId").get.toLong
try {
val brandId = data.get("brandId").get.toLong
if (EventType.exists(eventTypeId)) {
val event = eventTypeService.find(eventTypeId).get
if (event.brandId == brandId) {
Right(eventTypeId)
} else {
Left(List(FormError("eventTypeId", "Selected event type doesn't belong to a selected brand")))
}
} else {
Left(List(FormError("eventTypeId", "Unknown event type")))
}
} catch {
case e: IllegalArgumentException Left(List(FormError("brandCode", "Select a brand")))
}
} catch {
case e: IllegalArgumentException Left(List(FormError("eventTypeId", "Select an event type")))
}
}

def unbind(key: String, value: Long): Map[String, String] = {
Map(key -> value.toString)
}
}

/**
* HTML form mapping for an event’s invoice.
*/
Expand All @@ -124,9 +93,8 @@ object Events extends Controller
*/
def eventForm(implicit user: UserIdentity) = Form(mapping(
"id" -> ignored(Option.empty[Long]),
"eventTypeId" -> of(eventTypeFormatter),
"brandId" -> longNumber(min = 1).verifying(
"error.brand.invalid", (brandId: Long) Brand.canManage(brandId, user.account)),
"eventTypeId" -> longNumber(min = 1),
"brandId" -> longNumber(min = 1),
"title" -> nonEmptyText(1, 254),
"language" -> mapping(
"spoken" -> language,
Expand Down Expand Up @@ -154,7 +122,7 @@ object Events extends Controller
"updated" -> ignored(DateTime.now()),
"updatedBy" -> ignored(user.fullName),
"facilitatorIds" -> list(longNumber).verifying(
"error.event.nofacilitators", (ids: List[Long]) ids.nonEmpty))(
Messages("error.event.nofacilitators"), (ids: List[Long]) ids.nonEmpty))(
{ (id, eventTypeId, brandId, title, language, location, details, schedule, notPublic, archived, confirmed,
invoice, created, createdBy, updated, updatedBy, facilitatorIds)
{
Expand Down Expand Up @@ -217,19 +185,19 @@ object Events extends Controller
form.fold(
formWithErrors formError(user, formWithErrors, None),
x {
val validLicensees = License.licensees(x.brandId)
if (x.facilitatorIds.forall(id { validLicensees.exists(_.id.get == id) })) {
val event = x.insert
validateEvent(x, user.account) map { errors
formError(user,
form.withError("facilitatorIds", Messages("error.event.invalidLicense")),
None)
} getOrElse {
val event = eventService.insert(x)
val activity = event.activity(
user.person,
Activity.Predicate.Created).insert
sendEmailNotification(event,
List.empty,
activity)
sendEmailNotification(event, List.empty, activity)
Redirect(routes.Events.index()).flashing("success" -> activity.toString)
} else formError(user,
form.withError("facilitatorIds", Messages("error.event.invalidLicense")),
None)

}
})
}

Expand Down Expand Up @@ -450,32 +418,33 @@ object Events extends Controller
val form = eventForm.bindFromRequest
form.fold(
formWithErrors formError(user, formWithErrors, Some(id)),
event {
val validLicensees = License.licensees(event.brandId)
if (event.facilitatorIds.forall(id { validLicensees.exists(_.id.get == id) })) {
received {
validateEvent(received, user.account) map { errors
formError(user,
form.withError("facilitatorIds", Messages("error.event.invalidLicense")),
Some(id))
} getOrElse {
val existingEvent = eventService.find(id).get

val updatedEvent = event.copy(id = Some(id))
updatedEvent.invoice_=(event.invoice.copy(id = existingEvent.invoice.id))
updatedEvent.facilitatorIds_=(event.facilitatorIds)
val updated = received.copy(id = Some(id))
updated.invoice_=(received.invoice.copy(id = existingEvent.invoice.id))
updated.facilitatorIds_=(received.facilitatorIds)

// it's important to compare before updating as with lazy
// initialization invoice and facilitators data
// for an old event will be destroyed
val changes = Comparator.compare(existingEvent, updatedEvent)
updatedEvent.update
val changes = Comparator.compare(existingEvent, updated)
eventService.update(updated)

val activity = updatedEvent.activity(
val activity = updated.activity(
user.person,
Activity.Predicate.Updated).insert
sendEmailNotification(updatedEvent,
sendEmailNotification(updated,
changes,
activity)

Redirect(routes.Events.details(id)).flashing("success" -> activity.toString)
} else formError(user,
form.withError("facilitatorIds", Messages("error.event.invalidLicense")),
Some(id))
}
})
}

Expand All @@ -485,16 +454,15 @@ object Events extends Controller
*/
def confirm(id: Long) = SecuredDynamicAction("event", DynamicRole.Facilitator) { implicit request
implicit handler implicit user
eventService.find(id).map {
event
val updatedEvent = event.copy(id = Some(id)).copy(confirmed = true)
updatedEvent.invoice_=(event.invoice.copy(id = event.invoice.id))
updatedEvent.facilitatorIds_=(event.facilitatorIds)
updatedEvent.update
val activity = event.activity(
user.person,
Activity.Predicate.Confirmed).insert
Redirect(routes.Events.details(id)).flashing("success" -> activity.toString)
eventService.find(id).map { event
val updated = event.copy(id = Some(id)).copy(confirmed = true)
updated.invoice_=(event.invoice.copy(id = event.invoice.id))
updated.facilitatorIds_=(event.facilitatorIds)
eventService.update(updated)
val activity = event.activity(
user.person,
Activity.Predicate.Confirmed).insert
Redirect(routes.Events.details(id)).flashing("success" -> activity.toString)
}.getOrElse(NotFound)
}

Expand Down Expand Up @@ -542,6 +510,63 @@ object Events extends Controller
}.getOrElse(NotFound)
}

/**
* Returns none if the given event is valid; otherwise returns a list with errors
*
* @param event Event
* @param account User account
*/
protected def validateEvent(event: Event, account: UserAccount): Option[List[(String, String)]] = {
if (checker(account).isBrandFacilitator(event.brandId)) {
val licenseErrors = validateLicenses(event) map { x List(x) } getOrElse List()
val eventTypeErrors = validateEventType(event) map { x List(x) } getOrElse List()
val errors = licenseErrors ++ eventTypeErrors
if (errors.isEmpty)
None
else
Some(errors)
} else {
Some(List(("brandId", "error.brand.invalid")))
}
}

/**
* Returns error if none of facilitators has a valid license
*
* @param event Event object
*/
protected def validateLicenses(event: Event): Option[(String, String)] = {
val validLicensees = licenseService.licensees(event.brandId)
if (event.facilitatorIds.forall(id validLicensees.exists(_.id.get == id))) {
None
} else {
Some(("facilitatorIds", "error.event.invalidLicense"))
}
}

/**
* Returns error if event type doesn't exist or doesn't belong to the brand
*
* @param event Event object
*/
protected def validateEventType(event: Event): Option[(String, String)] = {
val eventType = eventTypeService.find(event.eventTypeId)
eventType map { x
if (x.brandId != event.brandId)
Some(("eventTypeId", "error.eventType.wrongBrand"))
else
None
} getOrElse Some(("eventTypeId", "error.eventType.notFound"))
}

/**
* Returns new resource checker
*
* @param account User account
*/
protected def checker(account: UserAccount): DynamicResourceChecker =
new DynamicResourceChecker(account)

/**
* Returns event form with highlighted errors
* @param user User object
Expand Down Expand Up @@ -579,3 +604,5 @@ object Events extends Controller
}
}
}

object Events extends Events

0 comments on commit 91284f8

Please sign in to comment.