1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103module rec Duets.Entities.Concert
open Aether
open Duets.Entities
type TicketPriceError =
| PriceBelowZero
| PriceTooHigh
module Timeline =
let empty =
{ ScheduledEvents = List.empty
PastEvents = List.empty }
/// Adds the given scheduled concert to the timeline, placing it sorted
/// by its date so that the timeline is always ordered by upcoming events.
let addScheduled
(event: ScheduledConcert)
(scheduledEvents: ScheduledConcert list)
=
match scheduledEvents with
| [] -> [ event ]
| head :: tail ->
let headConcert = Concert.fromScheduled head
let eventConcert = Concert.fromScheduled event
if headConcert.Date < eventConcert.Date then
head :: (addScheduled event tail)
else
event :: scheduledEvents
/// Adds the given past concert to the timeline, placing it sorted by its
/// date so that the timeline is always ordered by the closest event.
let addPast (event: PastConcert) (pastEvents: PastConcert list) =
match pastEvents with
| [] -> [ event ]
| head :: tail ->
let headConcert = Concert.fromPast head
let eventConcert = Concert.fromPast event
if headConcert.Date <= eventConcert.Date then
event :: head :: tail
else
head :: (addPast event tail)
module Ongoing =
/// Returns the number of times that an event was performed.
let timesDoneEvent ongoingConcert event =
Optic.get Lenses.Concerts.Ongoing.events_ ongoingConcert
|> List.filter (fun performedEvent ->
match performedEvent with
| PlaySong(playedSong, _) ->
match event with
| PlaySong(song, _) -> playedSong = song
| _ -> false
| _ -> performedEvent = event)
|> List.length
|> (*) 1<times>
/// Returns whether the given song has been previously played in the concert
/// or not.
let hasPlayedSong ongoingConcert song =
timesDoneEvent ongoingConcert (PlaySong(song, Energetic)) > 0<times>
/// Returns whether the band has accumulated enough points during the concert
/// for people to be interested in an encore and not just leave immediately
/// the moment you leave the stage.
let canPerformEncore ongoingConcert =
let timesPerformedEncores =
timesDoneEvent ongoingConcert PerformedEncore
let points = Optic.get Lenses.Concerts.Ongoing.points_ ongoingConcert
points > 50<quality> && timesPerformedEncores = 0<times>
/// Creates a concert from the given parameter.
let create date dayMoment cityId venueId ticketPrice participationType =
let ticketAmount = ticketPrice |> Amount.fromDecimal
{ Id = Identity.create ()
CityId = cityId
VenueId = venueId
Date = date
DayMoment = dayMoment
TicketPrice = ticketAmount
TicketsSold = 0
ParticipationType = participationType }
/// Validates that the ticket price is not below zero or a too high number.
let validatePrice ticketPrice =
if ticketPrice < 0m then Error PriceBelowZero
else if ticketPrice > 10000m then Error PriceTooHigh
else Ok ticketPrice
/// Returns the inner concert inside a past concert.
let fromPast (concert: PastConcert) : Concert =
match concert with
| PerformedConcert(concert, _) -> concert
| FailedConcert(concert, _) -> concert
/// Returns the inner concert inside a scheduled concert.
let fromScheduled (ScheduledConcert(concert, _)) : Concert = concert