Search, Paging, and "As of" Date #10

Merged
danieljsummers merged 15 commits from search into master 2019-03-21 00:19:02 +00:00
13 changed files with 402 additions and 223 deletions
Showing only changes of commit f71c3b8bb7 - Show all commits

View File

@ -98,11 +98,11 @@ type AppDbContext with
| query when activeOnly -> | query when activeOnly ->
let asOf = theDate.AddDays(-(float grp.preferences.daysToExpire)).Date let asOf = theDate.AddDays(-(float grp.preferences.daysToExpire)).Date
query.Where(fun pr -> query.Where(fun pr ->
(pr.updatedDate > asOf ( pr.updatedDate > asOf
|| pr.doNotExpire || pr.expiration = Manual
|| RequestType.Recurring = pr.requestType || pr.requestType = LongTermRequest
|| RequestType.Expecting = pr.requestType) || pr.requestType = Expecting)
&& not pr.isManuallyExpired) && pr.expiration <> Forced)
| query -> query | query -> query
|> reqSort grp.preferences.requestSort |> reqSort grp.preferences.requestSort
|> function |> function

View File

@ -6,35 +6,6 @@ open NodaTime
open System open System
open System.Collections.Generic open System.Collections.Generic
(*-- CONSTANTS --*)
/// Constants to use for the e-mail type parameter
[<RequireQualifiedAccess>]
module EmailType =
/// HTML e-mail
[<Literal>]
let Html = "Html"
/// Plain Text e-mail
[<Literal>]
let PlainText = "PlainText"
/// E-mail with the list as an attached PDF
[<Literal>]
let AttachedPdf = "AttachedPdf"
/// These values match those in the RequestType document store
[<RequireQualifiedAccess>]
module RequestType =
/// Current Requests (follow expiration rules)
let Current = "Current"
/// Long-Term / Recurring Requests (do not automatically expire)
let Recurring = "Recurring"
/// Praise Reports (follow expiration rules)
let Praise = "Praise"
/// Expectant Mothers (do not automatically expire)
let Expecting = "Expecting"
/// Announcements (follow expiration rules)
let Announcement = "Announcement"
(*-- SUPPORT TYPES --*) (*-- SUPPORT TYPES --*)
/// How as-of dates should (or should not) be displayed with requests /// How as-of dates should (or should not) be displayed with requests
@ -61,6 +32,82 @@ with
| LongDate -> "L" | LongDate -> "L"
/// Acceptable e-mail formats
type EmailFormat =
/// HTML e-mail
| HtmlFormat
/// Plain-text e-mail
| PlainTextFormat
with
/// Convert to a DU case from a single-character string
static member fromCode code =
match code with
| "H" -> HtmlFormat
| "P" -> PlainTextFormat
| _ -> invalidArg "code" (sprintf "Unknown code %s" code)
/// Convert this DU case to a single-character string
member this.code =
match this with
| HtmlFormat -> "H"
| PlainTextFormat -> "P"
/// Expiration for requests
type Expiration =
/// Follow the rules for normal expiration
| Automatic
/// Do not expire via rules
| Manual
/// Force immediate expiration
| Forced
with
/// Convert to a DU case from a single-character string
static member fromCode code =
match code with
| "A" -> Automatic
| "M" -> Manual
| "F" -> Forced
| _ -> invalidArg "code" (sprintf "Unknown code %s" code)
/// Convert this DU case to a single-character string
member this.code =
match this with
| Automatic -> "A"
| Manual -> "M"
| Forced -> "F"
/// Types of prayer requests
type PrayerRequestType =
/// Current requests
| CurrentRequest
/// Long-term/ongoing request
| LongTermRequest
/// Expectant couples
| Expecting
/// Praise reports
| PraiseReport
/// Announcements
| Announcement
with
/// Convert to a DU case from a single-character string
static member fromCode code =
match code with
| "C" -> CurrentRequest
| "L" -> LongTermRequest
| "E" -> Expecting
| "P" -> PraiseReport
| "A" -> Announcement
| _ -> invalidArg "code" (sprintf "Unknown code %s" code)
/// Convert this DU case to a single-character string
member this.code =
match this with
| CurrentRequest -> "C"
| LongTermRequest -> "L"
| Expecting -> "E"
| PraiseReport -> "P"
| Announcement -> "A"
/// How requests should be sorted /// How requests should be sorted
type RequestSort = type RequestSort =
/// Sort by date, then by requestor/subject /// Sort by date, then by requestor/subject
@ -96,6 +143,26 @@ module Converters =
|> LeafExpressionConverter.QuotationToExpression |> LeafExpressionConverter.QuotationToExpression
|> unbox<Expression<Func<string, AsOfDateDisplay>>> |> unbox<Expression<Func<string, AsOfDateDisplay>>>
let private emailFromDU =
<@ Func<EmailFormat, string>(fun (x : EmailFormat) -> x.code) @>
|> LeafExpressionConverter.QuotationToExpression
|> unbox<Expression<Func<EmailFormat, string>>>
let private emailToDU =
<@ Func<string, EmailFormat>(EmailFormat.fromCode) @>
|> LeafExpressionConverter.QuotationToExpression
|> unbox<Expression<Func<string, EmailFormat>>>
let private expFromDU =
<@ Func<Expiration, string>(fun (x : Expiration) -> x.code) @>
|> LeafExpressionConverter.QuotationToExpression
|> unbox<Expression<Func<Expiration, string>>>
let private expToDU =
<@ Func<string, Expiration>(Expiration.fromCode) @>
|> LeafExpressionConverter.QuotationToExpression
|> unbox<Expression<Func<string, Expiration>>>
let private sortFromDU = let private sortFromDU =
<@ Func<RequestSort, string>(fun (x : RequestSort) -> x.code) @> <@ Func<RequestSort, string>(fun (x : RequestSort) -> x.code) @>
|> LeafExpressionConverter.QuotationToExpression |> LeafExpressionConverter.QuotationToExpression
@ -106,10 +173,32 @@ module Converters =
|> LeafExpressionConverter.QuotationToExpression |> LeafExpressionConverter.QuotationToExpression
|> unbox<Expression<Func<string, RequestSort>>> |> unbox<Expression<Func<string, RequestSort>>>
let private typFromDU =
<@ Func<PrayerRequestType, string>(fun (x : PrayerRequestType) -> x.code) @>
|> LeafExpressionConverter.QuotationToExpression
|> unbox<Expression<Func<PrayerRequestType, string>>>
let private typToDU =
<@ Func<string, PrayerRequestType>(PrayerRequestType.fromCode) @>
|> LeafExpressionConverter.QuotationToExpression
|> unbox<Expression<Func<string, PrayerRequestType>>>
/// Conversion between a string and an AsOfDateDisplay DU value /// Conversion between a string and an AsOfDateDisplay DU value
type AsOfDateDisplayConverter () = type AsOfDateDisplayConverter () =
inherit ValueConverter<AsOfDateDisplay, string> (asOfFromDU, asOfToDU) inherit ValueConverter<AsOfDateDisplay, string> (asOfFromDU, asOfToDU)
/// Conversion between a string and an EmailFormat DU value
type EmailFormatConverter () =
inherit ValueConverter<EmailFormat, string> (emailFromDU, emailToDU)
/// Conversion between a string and an Expiration DU value
type ExpirationConverter () =
inherit ValueConverter<Expiration, string> (expFromDU, expToDU)
/// Conversion between a string and an AsOfDateDisplay DU value
type PrayerRequestTypeConverter () =
inherit ValueConverter<PrayerRequestType, string> (typFromDU, typToDU)
/// Conversion between a string and a RequestSort DU value /// Conversion between a string and a RequestSort DU value
type RequestSortConverter () = type RequestSortConverter () =
inherit ValueConverter<RequestSort, string> (sortFromDU, sortToDU) inherit ValueConverter<RequestSort, string> (sortFromDU, sortToDU)
@ -227,7 +316,7 @@ and [<CLIMutable; NoComparison; NoEquality>] ListPreferences =
/// The password used for "small group login" (view-only request list) /// The password used for "small group login" (view-only request list)
groupPassword : string groupPassword : string
/// The default e-mail type for this class /// The default e-mail type for this class
defaultEmailType : string defaultEmailType : EmailFormat
/// Whether this class makes its request list public /// Whether this class makes its request list public
isPublic : bool isPublic : bool
/// The time zone which this class uses (use tzdata names) /// The time zone which this class uses (use tzdata names)
@ -255,7 +344,7 @@ and [<CLIMutable; NoComparison; NoEquality>] ListPreferences =
textFontSize = 12 textFontSize = 12
requestSort = SortByDate requestSort = SortByDate
groupPassword = "" groupPassword = ""
defaultEmailType = EmailType.Html defaultEmailType = HtmlFormat
isPublic = false isPublic = false
timeZoneId = "America/Denver" timeZoneId = "America/Denver"
timeZone = TimeZone.empty timeZone = TimeZone.empty
@ -333,7 +422,7 @@ and [<CLIMutable; NoComparison; NoEquality>] ListPreferences =
m.Property(fun e -> e.defaultEmailType) m.Property(fun e -> e.defaultEmailType)
.HasColumnName("DefaultEmailType") .HasColumnName("DefaultEmailType")
.IsRequired() .IsRequired()
.HasDefaultValue EmailType.Html .HasDefaultValue HtmlFormat
|> ignore |> ignore
m.Property(fun e -> e.isPublic) m.Property(fun e -> e.isPublic)
.HasColumnName("IsPublic") .HasColumnName("IsPublic")
@ -359,6 +448,8 @@ and [<CLIMutable; NoComparison; NoEquality>] ListPreferences =
|> ignore |> ignore
mb.Model.FindEntityType(typeof<ListPreferences>).FindProperty("requestSort") mb.Model.FindEntityType(typeof<ListPreferences>).FindProperty("requestSort")
.SetValueConverter(Converters.RequestSortConverter ()) .SetValueConverter(Converters.RequestSortConverter ())
mb.Model.FindEntityType(typeof<ListPreferences>).FindProperty("defaultEmailType")
.SetValueConverter(Converters.EmailFormatConverter ())
mb.Model.FindEntityType(typeof<ListPreferences>).FindProperty("asOfDateDisplay") mb.Model.FindEntityType(typeof<ListPreferences>).FindProperty("asOfDateDisplay")
.SetValueConverter(Converters.AsOfDateDisplayConverter ()) .SetValueConverter(Converters.AsOfDateDisplayConverter ())
@ -374,7 +465,7 @@ and [<CLIMutable; NoComparison; NoEquality>] Member =
/// The e-mail address for the member /// The e-mail address for the member
email : string email : string
/// The type of e-mail preferred by this member (see <see cref="EmailTypes"/> constants) /// The type of e-mail preferred by this member (see <see cref="EmailTypes"/> constants)
format : string option format : string option // TODO - do I need a custom formatter for this?
/// The small group to which this member belongs /// The small group to which this member belongs
smallGroup : SmallGroup smallGroup : SmallGroup
} }
@ -405,64 +496,62 @@ and [<CLIMutable; NoComparison; NoEquality>] Member =
/// This represents a single prayer request /// This represents a single prayer request
and [<CLIMutable; NoComparison; NoEquality>] PrayerRequest = and [<CLIMutable; NoComparison; NoEquality>] PrayerRequest =
{ /// The Id of this request { /// The Id of this request
prayerRequestId : PrayerRequestId prayerRequestId : PrayerRequestId
/// The type of the request /// The type of the request
requestType : string requestType : PrayerRequestType
/// The user who entered the request /// The user who entered the request
userId : UserId userId : UserId
/// The small group to which this request belongs /// The small group to which this request belongs
smallGroupId : SmallGroupId smallGroupId : SmallGroupId
/// The date/time on which this request was entered /// The date/time on which this request was entered
enteredDate : DateTime enteredDate : DateTime
/// The date/time this request was last updated /// The date/time this request was last updated
updatedDate : DateTime updatedDate : DateTime
/// The name of the requestor or subject, or title of announcement /// The name of the requestor or subject, or title of announcement
requestor : string option requestor : string option
/// The text of the request /// The text of the request
text : string text : string
/// Whether this request is exempt from standard expiration rules
doNotExpire : bool
/// Whether the chaplain should be notified for this request /// Whether the chaplain should be notified for this request
notifyChaplain : bool notifyChaplain : bool
/// Whether this request has been expired manually
isManuallyExpired : bool
/// The user who entered this request /// The user who entered this request
user : User user : User
/// The small group to which this request belongs /// The small group to which this request belongs
smallGroup : SmallGroup smallGroup : SmallGroup
/// Is this request expired?
expiration : Expiration
} }
with with
/// An empty request /// An empty request
static member empty = static member empty =
{ prayerRequestId = Guid.Empty { prayerRequestId = Guid.Empty
requestType = RequestType.Current requestType = CurrentRequest
userId = Guid.Empty userId = Guid.Empty
smallGroupId = Guid.Empty smallGroupId = Guid.Empty
enteredDate = DateTime.MinValue enteredDate = DateTime.MinValue
updatedDate = DateTime.MinValue updatedDate = DateTime.MinValue
requestor = None requestor = None
text = "" text = ""
doNotExpire = false notifyChaplain = false
notifyChaplain = false user = User.empty
isManuallyExpired = false smallGroup = SmallGroup.empty
user = User.empty expiration = Automatic
smallGroup = SmallGroup.empty
} }
/// Is this request expired? /// Is this request expired?
member this.isExpired (curr : DateTime) expDays = member this.isExpired (curr : DateTime) expDays =
match this.isManuallyExpired with match this.expiration with
| true -> true // Manual expiration | Forced -> true
| false -> | Manual -> false
let nonExpiringTypes = [ RequestType.Recurring; RequestType.Expecting ] | Automatic ->
match this.doNotExpire || List.contains this.requestType nonExpiringTypes with match this.requestType with
| true -> false // No expiration | LongTermRequest
| false -> curr.AddDays(-(float expDays)) > this.updatedDate // Automatic expiration | Expecting -> false
| _ -> curr.AddDays(-(float expDays)) > this.updatedDate // Automatic expiration
/// Is an update required for this long-term request? /// Is an update required for this long-term request?
member this.updateRequired curr expDays updWeeks = member this.updateRequired curr expDays updWeeks =
match this.isExpired curr expDays with match this.isExpired curr expDays with
| true -> false | true -> false
| _ -> curr.AddDays(-(float (updWeeks * 7))) > this.updatedDate | false -> curr.AddDays(-(float (updWeeks * 7))) > this.updatedDate
/// Configure EF for this entity /// Configure EF for this entity
static member internal configureEF (mb : ModelBuilder) = static member internal configureEF (mb : ModelBuilder) =
@ -477,12 +566,15 @@ and [<CLIMutable; NoComparison; NoEquality>] PrayerRequest =
m.Property(fun e -> e.updatedDate).HasColumnName "UpdatedDate" |> ignore m.Property(fun e -> e.updatedDate).HasColumnName "UpdatedDate" |> ignore
m.Property(fun e -> e.requestor).HasColumnName "Requestor" |> ignore m.Property(fun e -> e.requestor).HasColumnName "Requestor" |> ignore
m.Property(fun e -> e.text).HasColumnName("Text").IsRequired() |> ignore m.Property(fun e -> e.text).HasColumnName("Text").IsRequired() |> ignore
m.Property(fun e -> e.doNotExpire).HasColumnName "DoNotExpire" |> ignore
m.Property(fun e -> e.notifyChaplain).HasColumnName "NotifyChaplain" |> ignore m.Property(fun e -> e.notifyChaplain).HasColumnName "NotifyChaplain" |> ignore
m.Property(fun e -> e.isManuallyExpired).HasColumnName "IsManuallyExpired" |> ignore) m.Property(fun e -> e.expiration).HasColumnName "Expiration" |> ignore)
|> ignore |> ignore
mb.Model.FindEntityType(typeof<PrayerRequest>).FindProperty("requestType")
.SetValueConverter(Converters.PrayerRequestTypeConverter ())
mb.Model.FindEntityType(typeof<PrayerRequest>).FindProperty("requestor") mb.Model.FindEntityType(typeof<PrayerRequest>).FindProperty("requestor")
.SetValueConverter(OptionConverter<string> ()) .SetValueConverter(OptionConverter<string> ())
mb.Model.FindEntityType(typeof<PrayerRequest>).FindProperty("expiration")
.SetValueConverter(Converters.ExpirationConverter ())
/// This represents a small group (Sunday School class, Bible study group, etc.) /// This represents a small group (Sunday School class, Bible study group, etc.)

View File

@ -50,17 +50,16 @@ type MemberTable =
} }
type PrayerRequestTable = type PrayerRequestTable =
{ prayerRequestId : OperationBuilder<AddColumnOperation> { prayerRequestId : OperationBuilder<AddColumnOperation>
doNotExpire : OperationBuilder<AddColumnOperation> enteredDate : OperationBuilder<AddColumnOperation>
enteredDate : OperationBuilder<AddColumnOperation> expiration : OperationBuilder<AddColumnOperation>
isManuallyExpired : OperationBuilder<AddColumnOperation> notifyChaplain : OperationBuilder<AddColumnOperation>
notifyChaplain : OperationBuilder<AddColumnOperation> requestType : OperationBuilder<AddColumnOperation>
requestType : OperationBuilder<AddColumnOperation> requestor : OperationBuilder<AddColumnOperation>
requestor : OperationBuilder<AddColumnOperation> smallGroupId : OperationBuilder<AddColumnOperation>
smallGroupId : OperationBuilder<AddColumnOperation> text : OperationBuilder<AddColumnOperation>
text : OperationBuilder<AddColumnOperation> updatedDate : OperationBuilder<AddColumnOperation>
updatedDate : OperationBuilder<AddColumnOperation> userId : OperationBuilder<AddColumnOperation>
userId : OperationBuilder<AddColumnOperation>
} }
type SmallGroupTable = type SmallGroupTable =
@ -104,12 +103,12 @@ type InitialDatabase () =
schema = "pt", schema = "pt",
columns = columns =
(fun table -> (fun table ->
{ churchId = table.Column<Guid> (name = "ChurchId", nullable = false) { churchId = table.Column<Guid> (name = "ChurchId", nullable = false)
city = table.Column<string> (name = "City", nullable = false) city = table.Column<string> (name = "City", nullable = false)
hasInterface = table.Column<bool> (name = "HasVirtualPrayerRoomInterface", nullable = false) hasInterface = table.Column<bool> (name = "HasVirtualPrayerRoomInterface", nullable = false)
interfaceAddress = table.Column<string> (name = "InterfaceAddress", nullable = true) interfaceAddress = table.Column<string> (name = "InterfaceAddress", nullable = true)
name = table.Column<string> (name = "Name", nullable = false) name = table.Column<string> (name = "Name", nullable = false)
st = table.Column<string> (name = "ST", maxLength = Nullable<int> 2, nullable = false) st = table.Column<string> (name = "ST", nullable = false, maxLength = Nullable<int> 2)
}), }),
constraints = constraints =
fun table -> fun table ->
@ -121,10 +120,10 @@ type InitialDatabase () =
schema = "pt", schema = "pt",
columns = columns =
(fun table -> (fun table ->
{ timeZoneId = table.Column<string> (name = "TimeZoneId", nullable = false) { timeZoneId = table.Column<string> (name = "TimeZoneId", nullable = false)
description = table.Column<string> (name = "Description", nullable = false) description = table.Column<string> (name = "Description", nullable = false)
isActive = table.Column<bool> (name = "IsActive", nullable = false) isActive = table.Column<bool> (name = "IsActive", nullable = false)
sortOrder = table.Column<int> (name = "SortOrder", nullable = false) sortOrder = table.Column<int> (name = "SortOrder", nullable = false)
}), }),
constraints = constraints =
fun table -> fun table ->
@ -136,13 +135,13 @@ type InitialDatabase () =
schema = "pt", schema = "pt",
columns = columns =
(fun table -> (fun table ->
{ userId = table.Column<Guid> (name = "UserId", nullable = false) { userId = table.Column<Guid> (name = "UserId", nullable = false)
emailAddress = table.Column<string> (name = "EmailAddress", nullable = false) emailAddress = table.Column<string> (name = "EmailAddress", nullable = false)
firstName = table.Column<string> (name = "FirstName", nullable = false) firstName = table.Column<string> (name = "FirstName", nullable = false)
isAdmin = table.Column<bool> (name = "IsSystemAdmin", nullable = false) isAdmin = table.Column<bool> (name = "IsSystemAdmin", nullable = false)
lastName = table.Column<string> (name = "LastName", nullable = false) lastName = table.Column<string> (name = "LastName", nullable = false)
passwordHash = table.Column<string> (name = "PasswordHash", nullable = false) passwordHash = table.Column<string> (name = "PasswordHash", nullable = false)
salt = table.Column<Guid> (name = "Salt", nullable = true) salt = table.Column<Guid> (name = "Salt", nullable = true)
}), }),
constraints = constraints =
fun table -> fun table ->
@ -155,8 +154,8 @@ type InitialDatabase () =
columns = columns =
(fun table -> (fun table ->
{ smallGroupId = table.Column<Guid> (name = "SmallGroupId", nullable = false) { smallGroupId = table.Column<Guid> (name = "SmallGroupId", nullable = false)
churchId = table.Column<Guid> (name = "ChurchId", nullable = false) churchId = table.Column<Guid> (name = "ChurchId", nullable = false)
name = table.Column<string> (name = "Name", nullable = false) name = table.Column<string> (name = "Name", nullable = false)
}), }),
constraints = constraints =
fun table -> fun table ->
@ -221,10 +220,10 @@ type InitialDatabase () =
schema = "pt", schema = "pt",
columns = columns =
(fun table -> (fun table ->
{ memberId = table.Column<Guid> (name = "MemberId", nullable = false) { memberId = table.Column<Guid> (name = "MemberId", nullable = false)
email = table.Column<string> (name = "Email", nullable = false) email = table.Column<string> (name = "Email", nullable = false)
format = table.Column<string> (name = "Format", nullable = true) format = table.Column<string> (name = "Format", nullable = true)
memberName = table.Column<string> (name = "MemberName", nullable = false) memberName = table.Column<string> (name = "MemberName", nullable = false)
smallGroupId = table.Column<Guid> (name = "SmallGroupId", nullable = false) smallGroupId = table.Column<Guid> (name = "SmallGroupId", nullable = false)
}), }),
constraints = constraints =
@ -246,16 +245,15 @@ type InitialDatabase () =
columns = columns =
(fun table -> (fun table ->
{ prayerRequestId = table.Column<Guid> (name = "PrayerRequestId", nullable = false) { prayerRequestId = table.Column<Guid> (name = "PrayerRequestId", nullable = false)
doNotExpire = table.Column<bool> (name = "DoNotExpire", nullable = false) expiration = table.Column<bool> (name = "Expiration", nullable = false)
enteredDate = table.Column<DateTime> (name = "EnteredDate", nullable = false) enteredDate = table.Column<DateTime> (name = "EnteredDate", nullable = false)
isManuallyExpired = table.Column<bool> (name = "IsManuallyExpired", nullable = false) notifyChaplain = table.Column<bool> (name = "NotifyChaplain", nullable = false)
notifyChaplain = table.Column<bool> (name = "NotifyChaplain", nullable = false) requestType = table.Column<string> (name = "RequestType", nullable = false)
requestType = table.Column<string> (name = "RequestType", nullable = false) requestor = table.Column<string> (name = "Requestor", nullable = true)
requestor = table.Column<string> (name = "Requestor", nullable = true) smallGroupId = table.Column<Guid> (name = "SmallGroupId", nullable = false)
smallGroupId = table.Column<Guid> (name = "SmallGroupId", nullable = false) text = table.Column<string> (name = "Text", nullable = false)
text = table.Column<string> (name = "Text", nullable = false) updatedDate = table.Column<DateTime> (name = "UpdatedDate", nullable = false)
updatedDate = table.Column<DateTime> (name = "UpdatedDate", nullable = false) userId = table.Column<Guid> (name = "UserId", nullable = false)
userId = table.Column<Guid> (name = "UserId", nullable = false)
}), }),
constraints = constraints =
fun table -> fun table ->
@ -283,7 +281,7 @@ type InitialDatabase () =
schema = "pt", schema = "pt",
columns = columns =
(fun table -> (fun table ->
{ userId = table.Column<Guid> (name = "UserId", nullable = false) { userId = table.Column<Guid> (name = "UserId", nullable = false)
smallGroupId = table.Column<Guid> (name = "SmallGroupId", nullable = false) smallGroupId = table.Column<Guid> (name = "SmallGroupId", nullable = false)
}), }),
constraints = constraints =
@ -351,7 +349,7 @@ type InitialDatabase () =
b.Property<Guid>("smallGroupId") |> ignore b.Property<Guid>("smallGroupId") |> ignore
b.Property<int>("daysToExpire").ValueGeneratedOnAdd().HasDefaultValue(14) |> ignore b.Property<int>("daysToExpire").ValueGeneratedOnAdd().HasDefaultValue(14) |> ignore
b.Property<int>("daysToKeepNew").ValueGeneratedOnAdd().HasDefaultValue(7) |> ignore b.Property<int>("daysToKeepNew").ValueGeneratedOnAdd().HasDefaultValue(7) |> ignore
b.Property<string>("defaultEmailType").IsRequired().ValueGeneratedOnAdd().HasDefaultValue("Html") |> ignore b.Property<string>("defaultEmailType").IsRequired().ValueGeneratedOnAdd().HasDefaultValue("H") |> ignore
b.Property<string>("emailFromAddress").IsRequired().ValueGeneratedOnAdd().HasDefaultValue("prayer@djs-consulting.com") |> ignore b.Property<string>("emailFromAddress").IsRequired().ValueGeneratedOnAdd().HasDefaultValue("prayer@djs-consulting.com") |> ignore
b.Property<string>("emailFromName").IsRequired().ValueGeneratedOnAdd().HasDefaultValue("PrayerTracker") |> ignore b.Property<string>("emailFromName").IsRequired().ValueGeneratedOnAdd().HasDefaultValue("PrayerTracker") |> ignore
b.Property<string>("groupPassword").IsRequired().ValueGeneratedOnAdd().HasDefaultValue("") |> ignore b.Property<string>("groupPassword").IsRequired().ValueGeneratedOnAdd().HasDefaultValue("") |> ignore
@ -388,11 +386,10 @@ type InitialDatabase () =
typeof<PrayerRequest>, typeof<PrayerRequest>,
fun b -> fun b ->
b.Property<Guid>("prayerRequestId").ValueGeneratedOnAdd() |> ignore b.Property<Guid>("prayerRequestId").ValueGeneratedOnAdd() |> ignore
b.Property<bool>("doNotExpire") |> ignore b.Property<DateTime>("enteredDate").IsRequired() |> ignore
b.Property<DateTime>("enteredDate") |> ignore b.Property<string>("expiration").IsRequired().HasMaxLength 1 |> ignore
b.Property<bool>("isManuallyExpired") |> ignore
b.Property<bool>("notifyChaplain") |> ignore b.Property<bool>("notifyChaplain") |> ignore
b.Property<string>("requestType").IsRequired() |> ignore b.Property<string>("requestType").IsRequired().HasMaxLength 1 |> ignore
b.Property<string>("requestor") |> ignore b.Property<string>("requestor") |> ignore
b.Property<Guid>("smallGroupId") |> ignore b.Property<Guid>("smallGroupId") |> ignore
b.Property<string>("text").IsRequired() |> ignore b.Property<string>("text").IsRequired() |> ignore

View File

@ -36,7 +36,7 @@ type AppDbContextModelSnapshot () =
b.Property<Guid>("smallGroupId") |> ignore b.Property<Guid>("smallGroupId") |> ignore
b.Property<int>("daysToExpire").ValueGeneratedOnAdd().HasDefaultValue(14) |> ignore b.Property<int>("daysToExpire").ValueGeneratedOnAdd().HasDefaultValue(14) |> ignore
b.Property<int>("daysToKeepNew").ValueGeneratedOnAdd().HasDefaultValue(7) |> ignore b.Property<int>("daysToKeepNew").ValueGeneratedOnAdd().HasDefaultValue(7) |> ignore
b.Property<string>("defaultEmailType").IsRequired().ValueGeneratedOnAdd().HasDefaultValue("Html") |> ignore b.Property<string>("defaultEmailType").IsRequired().ValueGeneratedOnAdd().HasDefaultValue("H").HasMaxLength(1) |> ignore
b.Property<string>("emailFromAddress").IsRequired().ValueGeneratedOnAdd().HasDefaultValue("prayer@djs-consulting.com") |> ignore b.Property<string>("emailFromAddress").IsRequired().ValueGeneratedOnAdd().HasDefaultValue("prayer@djs-consulting.com") |> ignore
b.Property<string>("emailFromName").IsRequired().ValueGeneratedOnAdd().HasDefaultValue("PrayerTracker") |> ignore b.Property<string>("emailFromName").IsRequired().ValueGeneratedOnAdd().HasDefaultValue("PrayerTracker") |> ignore
b.Property<string>("groupPassword").IsRequired().ValueGeneratedOnAdd().HasDefaultValue("") |> ignore b.Property<string>("groupPassword").IsRequired().ValueGeneratedOnAdd().HasDefaultValue("") |> ignore
@ -73,11 +73,10 @@ type AppDbContextModelSnapshot () =
typeof<PrayerRequest>, typeof<PrayerRequest>,
fun b -> fun b ->
b.Property<Guid>("prayerRequestId").ValueGeneratedOnAdd() |> ignore b.Property<Guid>("prayerRequestId").ValueGeneratedOnAdd() |> ignore
b.Property<bool>("doNotExpire") |> ignore
b.Property<DateTime>("enteredDate") |> ignore b.Property<DateTime>("enteredDate") |> ignore
b.Property<bool>("isManuallyExpired") |> ignore b.Property<string>("expiration").IsRequired().HasMaxLength(1) |> ignore
b.Property<bool>("notifyChaplain") |> ignore b.Property<bool>("notifyChaplain") |> ignore
b.Property<string>("requestType").IsRequired() |> ignore b.Property<string>("requestType").IsRequired().HasMaxLength(1) |> ignore
b.Property<string>("requestor") |> ignore b.Property<string>("requestor") |> ignore
b.Property<Guid>("smallGroupId") |> ignore b.Property<Guid>("smallGroupId") |> ignore
b.Property<string>("text").IsRequired() |> ignore b.Property<string>("text").IsRequired() |> ignore

View File

@ -48,6 +48,52 @@ let churchTests =
} }
] ]
[<Tests>]
let emailFormatTests =
testList "EmailFormat" [
test "HtmlFormat code is correct" {
Expect.equal HtmlFormat.code "H" "The code for HtmlFormat should have been \"H\""
}
test "PlainTextFormat code is correct" {
Expect.equal PlainTextFormat.code "P" "The code for PlainTextFormat should have been \"P\""
}
test "fromCode H should return HtmlFormat" {
Expect.equal (EmailFormat.fromCode "H") HtmlFormat "\"H\" should have been converted to HtmlFormat"
}
test "fromCode P should return ShortDate" {
Expect.equal (EmailFormat.fromCode "P") PlainTextFormat "\"P\" should have been converted to PlainTextFormat"
}
test "fromCode Z should raise" {
Expect.throws (fun () -> EmailFormat.fromCode "Z" |> ignore) "An unknown code should have raised an exception"
}
]
[<Tests>]
let expirationTests =
testList "Expiration" [
test "Automatic code is correct" {
Expect.equal Automatic.code "A" "The code for Automatic should have been \"A\""
}
test "Manual code is correct" {
Expect.equal Manual.code "M" "The code for Manual should have been \"M\""
}
test "Forced code is correct" {
Expect.equal Forced.code "F" "The code for Forced should have been \"F\""
}
test "fromCode A should return Automatic" {
Expect.equal (Expiration.fromCode "A") Automatic "\"A\" should have been converted to Automatic"
}
test "fromCode M should return Manual" {
Expect.equal (Expiration.fromCode "M") Manual "\"M\" should have been converted to Manual"
}
test "fromCode F should return Forced" {
Expect.equal (Expiration.fromCode "F") Forced "\"F\" should have been converted to Forced"
}
test "fromCode V should raise" {
Expect.throws (fun () -> Expiration.fromCode "V" |> ignore) "An unknown code should have raised an exception"
}
]
[<Tests>] [<Tests>]
let listPreferencesTests = let listPreferencesTests =
testList "ListPreferences" [ testList "ListPreferences" [
@ -67,7 +113,7 @@ let listPreferencesTests =
Expect.equal mt.textFontSize 12 "The default text font size should have been 12" Expect.equal mt.textFontSize 12 "The default text font size should have been 12"
Expect.equal mt.requestSort SortByDate "The default request sort should have been by date" Expect.equal mt.requestSort SortByDate "The default request sort should have been by date"
Expect.equal mt.groupPassword "" "The default group password should have been blank" Expect.equal mt.groupPassword "" "The default group password should have been blank"
Expect.equal mt.defaultEmailType EmailType.Html "The default e-mail type should have been HTML" Expect.equal mt.defaultEmailType HtmlFormat "The default e-mail type should have been HTML"
Expect.isFalse mt.isPublic "The isPublic flag should not have been set" Expect.isFalse mt.isPublic "The isPublic flag should not have been set"
Expect.equal mt.timeZoneId "America/Denver" "The default time zone should have been America/Denver" Expect.equal mt.timeZoneId "America/Denver" "The default time zone should have been America/Denver"
Expect.equal mt.timeZone.timeZoneId "" "The default preferences should have included an empty time zone" Expect.equal mt.timeZone.timeZoneId "" "The default preferences should have included an empty time zone"
@ -96,34 +142,33 @@ let prayerRequestTests =
test "empty is as expected" { test "empty is as expected" {
let mt = PrayerRequest.empty let mt = PrayerRequest.empty
Expect.equal mt.prayerRequestId Guid.Empty "The request ID should have been an empty GUID" Expect.equal mt.prayerRequestId Guid.Empty "The request ID should have been an empty GUID"
Expect.equal mt.requestType RequestType.Current "The request type should have been Current" Expect.equal mt.requestType CurrentRequest "The request type should have been Current"
Expect.equal mt.userId Guid.Empty "The user ID should have been an empty GUID" Expect.equal mt.userId Guid.Empty "The user ID should have been an empty GUID"
Expect.equal mt.smallGroupId Guid.Empty "The small group ID should have been an empty GUID" Expect.equal mt.smallGroupId Guid.Empty "The small group ID should have been an empty GUID"
Expect.equal mt.enteredDate DateTime.MinValue "The entered date should have been the minimum" Expect.equal mt.enteredDate DateTime.MinValue "The entered date should have been the minimum"
Expect.equal mt.updatedDate DateTime.MinValue "The updated date should have been the minimum" Expect.equal mt.updatedDate DateTime.MinValue "The updated date should have been the minimum"
Expect.isNone mt.requestor "The requestor should not exist" Expect.isNone mt.requestor "The requestor should not exist"
Expect.equal mt.text "" "The request text should have been blank" Expect.equal mt.text "" "The request text should have been blank"
Expect.isFalse mt.doNotExpire "The do not expire flag should not have been set"
Expect.isFalse mt.notifyChaplain "The notify chaplain flag should not have been set" Expect.isFalse mt.notifyChaplain "The notify chaplain flag should not have been set"
Expect.isFalse mt.isManuallyExpired "The is manually expired flag should not have been set" Expect.equal mt.expiration Automatic "The expiration should have been Automatic"
Expect.equal mt.user.userId Guid.Empty "The user should have been an empty one" Expect.equal mt.user.userId Guid.Empty "The user should have been an empty one"
Expect.equal mt.smallGroup.smallGroupId Guid.Empty "The small group should have been an empty one" Expect.equal mt.smallGroup.smallGroupId Guid.Empty "The small group should have been an empty one"
} }
test "isExpired always returns false for expecting requests" { test "isExpired always returns false for expecting requests" {
let req = { PrayerRequest.empty with requestType = RequestType.Expecting } let req = { PrayerRequest.empty with requestType = Expecting }
Expect.isFalse (req.isExpired DateTime.Now 0) "An expecting request should never be considered expired" Expect.isFalse (req.isExpired DateTime.Now 0) "An expecting request should never be considered expired"
} }
test "isExpired always returns false for never-expired requests" { test "isExpired always returns false for manually-expired requests" {
let req = { PrayerRequest.empty with updatedDate = DateTime.Now.AddMonths -1; doNotExpire = true } let req = { PrayerRequest.empty with updatedDate = DateTime.Now.AddMonths -1; expiration = Manual }
Expect.isFalse (req.isExpired DateTime.Now 4) "A never-expired request should never be considered expired" Expect.isFalse (req.isExpired DateTime.Now 4) "A never-expired request should never be considered expired"
} }
test "isExpired always returns false for recurring requests" { test "isExpired always returns false for long term/recurring requests" {
let req = { PrayerRequest.empty with requestType = RequestType.Recurring } let req = { PrayerRequest.empty with requestType = LongTermRequest }
Expect.isFalse (req.isExpired DateTime.Now 0) "A recurring/long-term request should never be considered expired" Expect.isFalse (req.isExpired DateTime.Now 0) "A recurring/long-term request should never be considered expired"
} }
test "isExpired always returns true for manually expired requests" { test "isExpired always returns true for force-expired requests" {
let req = { PrayerRequest.empty with updatedDate = DateTime.Now; isManuallyExpired = true } let req = { PrayerRequest.empty with updatedDate = DateTime.Now; expiration = Forced }
Expect.isTrue (req.isExpired DateTime.Now 5) "A manually expired request should always be considered expired" Expect.isTrue (req.isExpired DateTime.Now 5) "A force-expired request should always be considered expired"
} }
test "isExpired returns false for non-expired requests" { test "isExpired returns false for non-expired requests" {
let req = { PrayerRequest.empty with updatedDate = DateTime.Now.AddDays -5. } let req = { PrayerRequest.empty with updatedDate = DateTime.Now.AddDays -5. }
@ -134,13 +179,13 @@ let prayerRequestTests =
Expect.isTrue (req.isExpired DateTime.Now 7) "A request updated 8 days ago should be considered expired" Expect.isTrue (req.isExpired DateTime.Now 7) "A request updated 8 days ago should be considered expired"
} }
test "updateRequired returns false for expired requests" { test "updateRequired returns false for expired requests" {
let req = { PrayerRequest.empty with isManuallyExpired = true } let req = { PrayerRequest.empty with expiration = Forced }
Expect.isFalse (req.updateRequired DateTime.Now 7 4) "An expired request should not require an update" Expect.isFalse (req.updateRequired DateTime.Now 7 4) "An expired request should not require an update"
} }
test "updateRequired returns false when an update is not required for an active request" { test "updateRequired returns false when an update is not required for an active request" {
let req = let req =
{ PrayerRequest.empty with { PrayerRequest.empty with
requestType = RequestType.Recurring requestType = LongTermRequest
updatedDate = DateTime.Now.AddDays -14. updatedDate = DateTime.Now.AddDays -14.
} }
Expect.isFalse (req.updateRequired DateTime.Now 7 4) Expect.isFalse (req.updateRequired DateTime.Now 7 4)
@ -149,7 +194,7 @@ let prayerRequestTests =
test "updateRequired returns true when an update is required for an active request" { test "updateRequired returns true when an update is required for an active request" {
let req = let req =
{ PrayerRequest.empty with { PrayerRequest.empty with
requestType = RequestType.Recurring requestType = LongTermRequest
updatedDate = DateTime.Now.AddDays -34. updatedDate = DateTime.Now.AddDays -34.
} }
Expect.isTrue (req.updateRequired DateTime.Now 7 4) Expect.isTrue (req.updateRequired DateTime.Now 7 4)
@ -157,6 +202,47 @@ let prayerRequestTests =
} }
] ]
[<Tests>]
let prayerRequestTypeTests =
testList "PrayerRequestType" [
test "CurrentRequest code is correct" {
Expect.equal CurrentRequest.code "C" "The code for CurrentRequest should have been \"C\""
}
test "LongTermRequest code is correct" {
Expect.equal LongTermRequest.code "L" "The code for LongTermRequest should have been \"L\""
}
test "PraiseReport code is correct" {
Expect.equal PraiseReport.code "P" "The code for PraiseReport should have been \"P\""
}
test "Expecting code is correct" {
Expect.equal Expecting.code "E" "The code for Expecting should have been \"E\""
}
test "Announcement code is correct" {
Expect.equal Announcement.code "A" "The code for Announcement should have been \"A\""
}
test "fromCode C should return CurrentRequest" {
Expect.equal (PrayerRequestType.fromCode "C") CurrentRequest
"\"C\" should have been converted to CurrentRequest"
}
test "fromCode L should return LongTermRequest" {
Expect.equal (PrayerRequestType.fromCode "L") LongTermRequest
"\"L\" should have been converted to LongTermRequest"
}
test "fromCode P should return PraiseReport" {
Expect.equal (PrayerRequestType.fromCode "P") PraiseReport "\"P\" should have been converted to PraiseReport"
}
test "fromCode E should return Expecting" {
Expect.equal (PrayerRequestType.fromCode "E") Expecting "\"E\" should have been converted to Expecting"
}
test "fromCode A should return Announcement" {
Expect.equal (PrayerRequestType.fromCode "A") Announcement "\"A\" should have been converted to Announcement"
}
test "fromCode R should raise" {
Expect.throws (fun () -> PrayerRequestType.fromCode "R" |> ignore)
"An unknown code should have raised an exception"
}
]
[<Tests>] [<Tests>]
let requestSortTests = let requestSortTests =
testList "RequestSort" [ testList "RequestSort" [

View File

@ -31,15 +31,15 @@ module ReferenceListTests =
let emailTypeListTests = let emailTypeListTests =
testList "ReferenceList.emailTypeList" [ testList "ReferenceList.emailTypeList" [
test "includes default type" { test "includes default type" {
let typs = ReferenceList.emailTypeList EmailType.Html _s let typs = ReferenceList.emailTypeList HtmlFormat _s
Expect.hasCountOf typs 3u countAll "There should have been 3 e-mail type options returned" Expect.hasCountOf typs 3u countAll "There should have been 3 e-mail type options returned"
let top = Seq.head typs let top = Seq.head typs
Expect.equal (fst top) "" "The default option should have been blank" Expect.equal (fst top) "" "The default option should have been blank"
Expect.equal (snd top).Value "Group Default (HTML Format)" "The default option label was incorrect" Expect.equal (snd top).Value "Group Default (HTML Format)" "The default option label was incorrect"
let nxt = typs |> Seq.skip 1 |> Seq.head let nxt = typs |> Seq.skip 1 |> Seq.head
Expect.equal (fst nxt) EmailType.Html "The 2nd option should have been HTML" Expect.equal (fst nxt) HtmlFormat.code "The 2nd option should have been HTML"
let lst = typs |> Seq.last let lst = typs |> Seq.last
Expect.equal (fst lst) EmailType.PlainText "The 3rd option should have been plain text" Expect.equal (fst lst) PlainTextFormat.code "The 3rd option should have been plain text"
} }
] ]
@ -49,15 +49,15 @@ module ReferenceListTests =
test "excludes immediate expiration if not required" { test "excludes immediate expiration if not required" {
let exps = ReferenceList.expirationList _s false let exps = ReferenceList.expirationList _s false
Expect.hasCountOf exps 2u countAll "There should have been 2 expiration types returned" Expect.hasCountOf exps 2u countAll "There should have been 2 expiration types returned"
Expect.exists exps (fun exp -> fst exp = "N") "The option for normal expiration was not found" Expect.exists exps (fun (exp, _) -> exp = Automatic.code) "The option for automatic expiration was not found"
Expect.exists exps (fun exp -> fst exp = "Y") "The option for \"never expire\" was not found" Expect.exists exps (fun (exp, _) -> exp = Manual.code) "The option for manual expiration was not found"
} }
test "includes immediate expiration if required" { test "includes immediate expiration if required" {
let exps = ReferenceList.expirationList _s true let exps = ReferenceList.expirationList _s true
Expect.hasCountOf exps 3u countAll "There should have been 3 expiration types returned" Expect.hasCountOf exps 3u countAll "There should have been 3 expiration types returned"
Expect.exists exps (fun exp -> fst exp = "N") "The option for normal expiration was not found" Expect.exists exps (fun (exp, _) -> exp = Automatic.code) "The option for automatic expiration was not found"
Expect.exists exps (fun exp -> fst exp = "Y") "The option for \"never expire\" was not found" Expect.exists exps (fun (exp, _) -> exp = Manual.code) "The option for manual expiration was not found"
Expect.exists exps (fun exp -> fst exp = "X") "The option for \"expire immediately\" was not found" Expect.exists exps (fun (exp, _) -> exp = Forced.code) "The option for immediate expiration was not found"
} }
] ]
@ -69,14 +69,12 @@ module ReferenceListTests =
yield! testFixture withList [ yield! testFixture withList [
yield "returns 5 types", yield "returns 5 types",
fun typs -> Expect.hasCountOf typs 5u countAll "There should have been 5 request types returned" fun typs -> Expect.hasCountOf typs 5u countAll "There should have been 5 request types returned"
yield! [ RequestType.Current; RequestType.Recurring; RequestType.Praise; RequestType.Expecting; yield! [ CurrentRequest; LongTermRequest; PraiseReport; Expecting; Announcement ]
RequestType.Announcement |> List.map (fun typ ->
] sprintf "contains \"%O\"" typ,
|> List.map (fun typ -> fun typs ->
sprintf "contains \"%s\"" typ, Expect.isSome (typs |> List.tryFind (fun x -> fst x = typ))
fun typs -> (sprintf "The \"%O\" option was not found" typ))
Expect.isSome (typs |> List.tryFind (fun x -> fst x = typ))
(sprintf "The \"%s\" option was not found" typ))
] ]
] ]
@ -232,8 +230,8 @@ let editMemberTests =
Expect.equal edit.emailType "" "The e-mail type should have been blank for group default" Expect.equal edit.emailType "" "The e-mail type should have been blank for group default"
} }
test "fromMember populates with specific format" { test "fromMember populates with specific format" {
let edit = EditMember.fromMember { Member.empty with format = Some EmailType.Html } let edit = EditMember.fromMember { Member.empty with format = Some HtmlFormat.code }
Expect.equal edit.emailType EmailType.Html "The e-mail type was not filled correctly" Expect.equal edit.emailType HtmlFormat.code "The e-mail type was not filled correctly"
} }
test "empty is as expected" { test "empty is as expected" {
let edit = EditMember.empty let edit = EditMember.empty
@ -263,7 +261,7 @@ let editPreferencesTests =
Expect.equal edit.requestSort prefs.requestSort.code "The request sort was not filled correctly" Expect.equal edit.requestSort prefs.requestSort.code "The request sort was not filled correctly"
Expect.equal edit.emailFromName prefs.emailFromName "The e-mail from name was not filled correctly" Expect.equal edit.emailFromName prefs.emailFromName "The e-mail from name was not filled correctly"
Expect.equal edit.emailFromAddress prefs.emailFromAddress "The e-mail from address was not filled correctly" Expect.equal edit.emailFromAddress prefs.emailFromAddress "The e-mail from address was not filled correctly"
Expect.equal edit.defaultEmailType prefs.defaultEmailType "The default e-mail type was not filled correctly" Expect.equal edit.defaultEmailType prefs.defaultEmailType.code "The default e-mail type was not filled correctly"
Expect.equal edit.headingLineType "Name" "The heading line color type was not derived correctly" Expect.equal edit.headingLineType "Name" "The heading line color type was not derived correctly"
Expect.equal edit.headingLineColor prefs.lineColor "The heading line color was not filled correctly" Expect.equal edit.headingLineColor prefs.lineColor "The heading line color was not filled correctly"
Expect.equal edit.headingTextType "Name" "The heading text color type was not derived correctly" Expect.equal edit.headingTextType "Name" "The heading text color type was not derived correctly"
@ -303,39 +301,29 @@ let editRequestTests =
test "empty is as expected" { test "empty is as expected" {
let mt = EditRequest.empty let mt = EditRequest.empty
Expect.equal mt.requestId Guid.Empty "The request ID should be an empty GUID" Expect.equal mt.requestId Guid.Empty "The request ID should be an empty GUID"
Expect.equal mt.requestType "" "The request type should have been blank" Expect.equal mt.requestType CurrentRequest.code "The request type should have been \"Current\""
Expect.isNone mt.enteredDate "The entered date should have been None" Expect.isNone mt.enteredDate "The entered date should have been None"
Expect.isNone mt.skipDateUpdate "The \"skip date update\" flag should have been None" Expect.isNone mt.skipDateUpdate "The \"skip date update\" flag should have been None"
Expect.isNone mt.requestor "The requestor should have been None" Expect.isNone mt.requestor "The requestor should have been None"
Expect.equal mt.expiration "N" "The expiration should have been \"N\"" Expect.equal mt.expiration Automatic.code "The expiration should have been \"A\" (Automatic)"
Expect.equal mt.text "" "The text should have been blank" Expect.equal mt.text "" "The text should have been blank"
} }
test "fromRequest succeeds when a request has the do-not-expire flag set" { test "fromRequest succeeds" {
let req = let req =
{ PrayerRequest.empty with { PrayerRequest.empty with
prayerRequestId = Guid.NewGuid () prayerRequestId = Guid.NewGuid ()
requestType = RequestType.Current requestType = CurrentRequest
requestor = Some "Me" requestor = Some "Me"
doNotExpire = true expiration = Manual
text = "the text" text = "the text"
} }
let edit = EditRequest.fromRequest req let edit = EditRequest.fromRequest req
Expect.equal edit.requestId req.prayerRequestId "The request ID was not filled correctly" Expect.equal edit.requestId req.prayerRequestId "The request ID was not filled correctly"
Expect.equal edit.requestType req.requestType "The request type was not filled correctly" Expect.equal edit.requestType req.requestType.code "The request type was not filled correctly"
Expect.equal edit.requestor req.requestor "The requestor was not filled correctly" Expect.equal edit.requestor req.requestor "The requestor was not filled correctly"
Expect.equal edit.expiration "Y" "The expiration should have been \"Y\" since the do-not-expire flag was set" Expect.equal edit.expiration Manual.code "The expiration was not filled correctly"
Expect.equal edit.text req.text "The text was not filled correctly" Expect.equal edit.text req.text "The text was not filled correctly"
} }
test "fromRequest succeeds when a request has the do-not-expire flag unset" {
let req =
{ PrayerRequest.empty with
requestor = None
doNotExpire = false
}
let edit = EditRequest.fromRequest req
Expect.equal edit.requestor req.requestor "The requestor was not filled correctly"
Expect.equal edit.expiration "N" "The expiration should have been \"N\" since the do-not-expire flag was not set"
}
test "isNew works for a new request" { test "isNew works for a new request" {
Expect.isTrue (EditRequest.empty.isNew ()) "An empty GUID should be flagged as a new request" Expect.isTrue (EditRequest.empty.isNew ()) "An empty GUID should be flagged as a new request"
} }
@ -469,19 +457,19 @@ let requestListTests =
let withRequestList f () = let withRequestList f () =
{ requests = [ { requests = [
{ PrayerRequest.empty with { PrayerRequest.empty with
requestType = RequestType.Current requestType = CurrentRequest
requestor = Some "Zeb" requestor = Some "Zeb"
text = "zyx" text = "zyx"
updatedDate = DateTime.Today updatedDate = DateTime.Today
} }
{ PrayerRequest.empty with { PrayerRequest.empty with
requestType = RequestType.Current requestType = CurrentRequest
requestor = Some "Aaron" requestor = Some "Aaron"
text = "abc" text = "abc"
updatedDate = DateTime.Today - TimeSpan.FromDays 9. updatedDate = DateTime.Today - TimeSpan.FromDays 9.
} }
{ PrayerRequest.empty with { PrayerRequest.empty with
requestType = RequestType.Praise requestType = PraiseReport
text = "nmo" text = "nmo"
updatedDate = DateTime.Today updatedDate = DateTime.Today
} }
@ -566,19 +554,19 @@ let requestListTests =
Expect.stringContains text " + nmo\n \n" "Last request not found" Expect.stringContains text " + nmo\n \n" "Last request not found"
"isNew succeeds for both old and new requests", "isNew succeeds for both old and new requests",
fun reqList -> fun reqList ->
let reqs = reqList.requestsInCategory RequestType.Current let reqs = reqList.requestsInCategory CurrentRequest
Expect.hasCountOf reqs 2u countAll "There should have been two requests" Expect.hasCountOf reqs 2u countAll "There should have been two requests"
Expect.isTrue (reqList.isNew (List.head reqs)) "The first request should have been new" Expect.isTrue (reqList.isNew (List.head reqs)) "The first request should have been new"
Expect.isFalse (reqList.isNew (List.last reqs)) "The second request should not have been new" Expect.isFalse (reqList.isNew (List.last reqs)) "The second request should not have been new"
"requestsInCategory succeeds when requests exist", "requestsInCategory succeeds when requests exist",
fun reqList -> fun reqList ->
let reqs = reqList.requestsInCategory RequestType.Current let reqs = reqList.requestsInCategory CurrentRequest
Expect.hasCountOf reqs 2u countAll "There should have been two requests" Expect.hasCountOf reqs 2u countAll "There should have been two requests"
let first = List.head reqs let first = List.head reqs
Expect.equal first.text "zyx" "The requests should be sorted by updated date descending" Expect.equal first.text "zyx" "The requests should be sorted by updated date descending"
"requestsInCategory succeeds when requests do not exist", "requestsInCategory succeeds when requests do not exist",
fun reqList -> fun reqList ->
Expect.isEmpty (reqList.requestsInCategory "ABC") "There should have been no category \"ABC\" requests" Expect.isEmpty (reqList.requestsInCategory Announcement) "There should have been no \"Announcement\" requests"
"requestsInCategory succeeds and sorts by requestor", "requestsInCategory succeeds and sorts by requestor",
fun reqList -> fun reqList ->
let newList = let newList =
@ -588,7 +576,7 @@ let requestListTests =
preferences = { reqList.listGroup.preferences with requestSort = SortByRequestor } preferences = { reqList.listGroup.preferences with requestSort = SortByRequestor }
} }
} }
let reqs = newList.requestsInCategory RequestType.Current let reqs = newList.requestsInCategory CurrentRequest
Expect.hasCountOf reqs 2u countAll "There should have been two requests" Expect.hasCountOf reqs 2u countAll "There should have been two requests"
let first = List.head reqs let first = List.head reqs
Expect.equal first.text "abc" "The requests should be sorted by requestor" Expect.equal first.text "abc" "The requests should be sorted by requestor"

View File

@ -23,7 +23,7 @@ let edit (m : EditRequest) today ctx vi =
label [ _for "requestType" ] [ locStr s.["Request Type"] ] label [ _for "requestType" ] [ locStr s.["Request Type"] ]
ReferenceList.requestTypeList s ReferenceList.requestTypeList s
|> Seq.ofList |> Seq.ofList
|> Seq.map (fun item -> fst item, (snd item).Value) |> Seq.map (fun (typ, desc) -> typ.code, desc.Value)
|> selectList "requestType" m.requestType [ _required; _autofocus ] |> selectList "requestType" m.requestType [ _required; _autofocus ]
] ]
yield div [ _class "pt-field" ] [ yield div [ _class "pt-field" ] [

View File

@ -45,7 +45,7 @@ let announcement isAdmin ctx vi =
label [ _for "requestType" ] [ locStr s.["Request Type"] ] label [ _for "requestType" ] [ locStr s.["Request Type"] ]
reqTypes reqTypes
|> Seq.ofList |> Seq.ofList
|> Seq.map (fun item -> fst item, (snd item).Value) |> Seq.map (fun (typ, desc) -> typ.code, desc.Value)
|> selectList "requestType" "Announcement" [] |> selectList "requestType" "Announcement" []
] ]
] ]
@ -407,7 +407,9 @@ let preferences (m : EditPreferences) (tzs : TimeZone list) ctx vi =
label [ _for "defaultEmailType" ] [ locStr s.["E-mail Format"] ] label [ _for "defaultEmailType" ] [ locStr s.["E-mail Format"] ]
seq { seq {
yield "", selectDefault s.["Select"].Value yield "", selectDefault s.["Select"].Value
yield! ReferenceList.emailTypeList "" s |> Seq.skip 1 |> Seq.map (fun typ -> fst typ, (snd typ).Value) yield! ReferenceList.emailTypeList HtmlFormat s
|> Seq.skip 1
|> Seq.map (fun typ -> fst typ, (snd typ).Value)
} }
|> selectList "defaultEmailType" m.defaultEmailType [ _required ] |> selectList "defaultEmailType" m.defaultEmailType [ _required ]
] ]

View File

@ -22,30 +22,28 @@ module ReferenceList =
// Localize the default type // Localize the default type
let defaultType = let defaultType =
match def with match def with
| EmailType.Html -> s.["HTML Format"].Value | HtmlFormat -> s.["HTML Format"].Value
| EmailType.PlainText -> s.["Plain-Text Format"].Value | PlainTextFormat -> s.["Plain-Text Format"].Value
| EmailType.AttachedPdf -> s.["Attached PDF"].Value
| _ -> ""
seq { seq {
yield "", LocalizedString ("", sprintf "%s (%s)" s.["Group Default"].Value defaultType) yield "", LocalizedString ("", sprintf "%s (%s)" s.["Group Default"].Value defaultType)
yield EmailType.Html, s.["HTML Format"] yield HtmlFormat.code, s.["HTML Format"]
yield EmailType.PlainText, s.["Plain-Text Format"] yield PlainTextFormat.code, s.["Plain-Text Format"]
} }
/// A list of expiration options /// A list of expiration options
let expirationList (s : IStringLocalizer) includeExpireNow = let expirationList (s : IStringLocalizer) includeExpireNow =
[ yield "N", s.["Expire Normally"] [ yield Automatic.code, s.["Expire Normally"]
yield "Y", s.["Request Never Expires"] yield Manual.code, s.["Request Never Expires"]
match includeExpireNow with true -> yield "X", s.["Expire Immediately"] | false -> () match includeExpireNow with true -> yield Forced.code, s.["Expire Immediately"] | false -> ()
] ]
/// A list of request types /// A list of request types
let requestTypeList (s : IStringLocalizer) = let requestTypeList (s : IStringLocalizer) =
[ RequestType.Current, s.["Current Requests"] [ CurrentRequest, s.["Current Requests"]
RequestType.Recurring, s.["Long-Term Requests"] LongTermRequest, s.["Long-Term Requests"]
RequestType.Praise, s.["Praise Reports"] PraiseReport, s.["Praise Reports"]
RequestType.Expecting, s.["Expecting"] Expecting, s.["Expecting"]
RequestType.Announcement, s.["Announcements"] Announcement, s.["Announcements"]
] ]
@ -292,7 +290,7 @@ with
requestSort = prefs.requestSort.code requestSort = prefs.requestSort.code
emailFromName = prefs.emailFromName emailFromName = prefs.emailFromName
emailFromAddress = prefs.emailFromAddress emailFromAddress = prefs.emailFromAddress
defaultEmailType = prefs.defaultEmailType defaultEmailType = prefs.defaultEmailType.code
headingLineType = setType prefs.lineColor headingLineType = setType prefs.lineColor
headingLineColor = prefs.lineColor headingLineColor = prefs.lineColor
headingTextType = setType prefs.headingColor headingTextType = setType prefs.headingColor
@ -325,7 +323,7 @@ with
requestSort = RequestSort.fromCode this.requestSort requestSort = RequestSort.fromCode this.requestSort
emailFromName = this.emailFromName emailFromName = this.emailFromName
emailFromAddress = this.emailFromAddress emailFromAddress = this.emailFromAddress
defaultEmailType = this.defaultEmailType defaultEmailType = EmailFormat.fromCode this.defaultEmailType
lineColor = this.headingLineColor lineColor = this.headingLineColor
headingColor = this.headingTextColor headingColor = this.headingTextColor
listFonts = this.listFonts listFonts = this.listFonts
@ -362,20 +360,20 @@ with
/// An empty instance to use for new requests /// An empty instance to use for new requests
static member empty = static member empty =
{ requestId = Guid.Empty { requestId = Guid.Empty
requestType = "" requestType = CurrentRequest.code
enteredDate = None enteredDate = None
skipDateUpdate = None skipDateUpdate = None
requestor = None requestor = None
expiration = "N" expiration = Automatic.code
text = "" text = ""
} }
/// Create an instance from an existing request /// Create an instance from an existing request
static member fromRequest req = static member fromRequest req =
{ EditRequest.empty with { EditRequest.empty with
requestId = req.prayerRequestId requestId = req.prayerRequestId
requestType = req.requestType requestType = req.requestType.code
requestor = req.requestor requestor = req.requestor
expiration = match req.doNotExpire with true -> "Y" | false -> "N" expiration = req.expiration.code
text = req.text text = req.text
} }
/// Is this a new request? /// Is this a new request?
@ -516,7 +514,7 @@ type Overview =
{ /// The total number of active requests { /// The total number of active requests
totalActiveReqs : int totalActiveReqs : int
/// The numbers of active requests by category /// The numbers of active requests by category
activeReqsByCat : Map<string, int> activeReqsByCat : Map<PrayerRequestType, int>
/// A count of all requests /// A count of all requests
allReqs : int allReqs : int
/// A count of all members /// A count of all members

View File

@ -67,18 +67,15 @@ let sendEmails (client : SmtpClient) (recipients : Member list) grp subj html te
let plainTextMsg = createTextMessage grp subj text s let plainTextMsg = createTextMessage grp subj text s
for mbr in recipients do for mbr in recipients do
let emailType = match mbr.format with Some f -> f | None -> grp.preferences.defaultEmailType let emailType = match mbr.format with Some f -> EmailFormat.fromCode f | None -> grp.preferences.defaultEmailType
let emailTo = MailboxAddress (mbr.memberName, mbr.email) let emailTo = MailboxAddress (mbr.memberName, mbr.email)
match emailType with match emailType with
| EmailType.Html -> | HtmlFormat ->
htmlMsg.To.Add emailTo htmlMsg.To.Add emailTo
do! client.SendAsync htmlMsg do! client.SendAsync htmlMsg
htmlMsg.To.Clear () htmlMsg.To.Clear ()
| EmailType.PlainText -> | PlainTextFormat ->
plainTextMsg.To.Add emailTo plainTextMsg.To.Add emailTo
do! client.SendAsync plainTextMsg do! client.SendAsync plainTextMsg
plainTextMsg.To.Clear () plainTextMsg.To.Clear ()
| EmailType.AttachedPdf ->
raise <| NotImplementedException "Attached PDF format has not been implemented"
| _ -> invalidOp <| sprintf "Unknown e-mail type %s passed" emailType
} }

View File

@ -136,7 +136,7 @@ let expire reqId : HttpHandler =
| Ok r -> | Ok r ->
let db = ctx.dbContext () let db = ctx.dbContext ()
let s = Views.I18N.localizer.Force () let s = Views.I18N.localizer.Force ()
db.UpdateEntry { r with isManuallyExpired = true } db.UpdateEntry { r with expiration = Forced }
let! _ = db.SaveChangesAsync () let! _ = db.SaveChangesAsync ()
addInfo ctx s.["Successfully {0} prayer request", s.["Expired"].Value.ToLower ()] addInfo ctx s.["Successfully {0} prayer request", s.["Expired"].Value.ToLower ()]
return! redirectTo false "/prayer-requests" next ctx return! redirectTo false "/prayer-requests" next ctx
@ -247,7 +247,7 @@ let restore reqId : HttpHandler =
| Ok r -> | Ok r ->
let db = ctx.dbContext () let db = ctx.dbContext ()
let s = Views.I18N.localizer.Force () let s = Views.I18N.localizer.Force ()
db.UpdateEntry { r with isManuallyExpired = false; updatedDate = DateTime.Now } db.UpdateEntry { r with expiration = Automatic; updatedDate = DateTime.Now }
let! _ = db.SaveChangesAsync () let! _ = db.SaveChangesAsync ()
addInfo ctx s.["Successfully {0} prayer request", s.["Restored"].Value.ToLower ()] addInfo ctx s.["Successfully {0} prayer request", s.["Restored"].Value.ToLower ()]
return! redirectTo false "/prayer-requests" next ctx return! redirectTo false "/prayer-requests" next ctx
@ -273,11 +273,10 @@ let save : HttpHandler =
| Some pr -> | Some pr ->
let upd8 = let upd8 =
{ pr with { pr with
requestType = m.requestType requestType = PrayerRequestType.fromCode m.requestType
requestor = m.requestor requestor = m.requestor
text = ckEditorToText m.text text = ckEditorToText m.text
doNotExpire = m.expiration = "Y" expiration = Expiration.fromCode m.expiration
isManuallyExpired = m.expiration = "X"
} }
let grp = currentGroup ctx let grp = currentGroup ctx
let now = grp.localDateNow (ctx.GetService<IClock> ()) let now = grp.localDateNow (ctx.GetService<IClock> ())

View File

@ -387,7 +387,7 @@ let sendAnnouncement : HttpHandler =
prayerRequestId = Guid.NewGuid () prayerRequestId = Guid.NewGuid ()
smallGroupId = grp.smallGroupId smallGroupId = grp.smallGroupId
userId = usr.userId userId = usr.userId
requestType = Option.get m.requestType requestType = (Option.get >> PrayerRequestType.fromCode) m.requestType
text = requestText text = requestText
enteredDate = now enteredDate = now
updatedDate = now updatedDate = now

View File

@ -4,3 +4,24 @@ create index "IX_PrayerRequest_Requestor_TRGM" on "PrayerRequest" using GIN (COA
create index "IX_PrayerRequest_Text_TRGM" on "PrayerRequest" using GIN ("Text" gin_trgm_ops); create index "IX_PrayerRequest_Text_TRGM" on "PrayerRequest" using GIN ("Text" gin_trgm_ops);
alter table "ListPreference" add column "PageSize" int not null default 100; alter table "ListPreference" add column "PageSize" int not null default 100;
alter table "ListPreference" add column "AsOfDateDisplay" varchar(1) not null default 'N'; alter table "ListPreference" add column "AsOfDateDisplay" varchar(1) not null default 'N';
/* RequestType to 1 character code */
update "PrayerRequest" set "RequestType" = 'C' where "RequestType" = 'Current';
update "PrayerRequest" set "RequestType" = 'L' where "RequestType" = 'Recurring';
update "PrayerRequest" set "RequestType" = 'P' where "RequestType" = 'Praise';
update "PrayerRequest" set "RequestType" = 'E' where "RequestType" = 'Expecting';
update "PrayerRequest" set "RequestType" = 'A' where "RequestType" = 'Announcement';
alter table "PrayerRequest" alter column "RequestType" set data type varchar(1);
/* Change expiration to a 1-character code field */
alter table "PrayerRequest" add column "Expiration" varchar(1);
update "PrayerRequest" set "Expiration" = case when "IsManuallyExpired" then 'F' when "DoNotExpire" then 'M' else 'A' end;
alter table "PrayerRequest" alter column "Expiration" set not null;
alter table "PrayerRequest" drop column "DoNotExpire";
alter table "PrayerRequest" drop column "IsManuallyExpired";
/* Change e-mail type to 1-character code field in list preferences and members */
update "ListPreference" set "DefaultEmailType" = 'H' where "DefaultEmailType" = 'Html';
update "ListPreference" set "DefaultEmailType" = 'P' where "DefaultEmailType" = 'Text';
alter table "ListPreference" alter column "DefaultEmailType" set default 'H';
alter table "ListPreference" alter column "DefaultEmailType" set data type varchar(1);
update "Member" set "Format" = 'H' where "Format" = 'Html';
update "Member" set "Format" = 'P' where "Format" = 'Text';
alter table "Member" alter column "Format" set data type varchar(1);